diff --git a/frontend/react/src/ui/panels/unitcontrolmenu.tsx b/frontend/react/src/ui/panels/unitcontrolmenu.tsx
index 5ce2e20a..b7ab0704 100644
--- a/frontend/react/src/ui/panels/unitcontrolmenu.tsx
+++ b/frontend/react/src/ui/panels/unitcontrolmenu.tsx
@@ -55,7 +55,7 @@ export function UnitControlMenu() {
updateData();
})
- /* When all units are selected clean the view */
+ /* When all units are deselected clean the view */
document.addEventListener("clearSelection", () => {
setOpen(false);
setSelectedUnits([])
diff --git a/frontend/react/src/ui/panels/unitmousecontrolbar.tsx b/frontend/react/src/ui/panels/unitmousecontrolbar.tsx
new file mode 100644
index 00000000..7812044e
--- /dev/null
+++ b/frontend/react/src/ui/panels/unitmousecontrolbar.tsx
@@ -0,0 +1,55 @@
+import React, { useEffect, useState } from 'react';
+import { Unit } from '../../unit/unit';
+import { ContextActionSet } from '../../unit/contextactionset';
+import { OlStateButton } from '../components/olstatebutton';
+import { faAccessibleIcon } from '@fortawesome/free-brands-svg-icons';
+import { faCamera } from '@fortawesome/free-solid-svg-icons';
+import { getApp } from '../../olympusapp';
+
+export function UnitMouseControlBar(props: {
+
+}) {
+ var [open, setOpen] = useState(false);
+ var [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
+ var [contextActionsSet, setContextActionsSet] = useState(new ContextActionSet());
+
+ /* When a unit is selected, open the menu */
+ document.addEventListener("unitsSelection", (ev: CustomEventInit) => {
+ setOpen(true);
+ setSelectedUnits(ev.detail as Unit[]);
+
+ updateData();
+ })
+
+ /* When a unit is deselected, refresh the view */
+ document.addEventListener("unitDeselection", (ev: CustomEventInit) => {
+ /* TODO add delay to avoid doing it too many times */
+ updateData();
+ })
+
+ /* When all units are deselected clean the view */
+ document.addEventListener("clearSelection", () => {
+ setOpen(false);
+ setSelectedUnits([]);
+ updateData();
+ })
+
+ /* Update the current values of the shown data */
+ function updateData() {
+ var newContextActionSet = new ContextActionSet();
+
+ getApp().getUnitsManager().getSelectedUnits().forEach((unit: Unit) => {
+ unit.appendContextActions(newContextActionSet);
+ })
+
+ setContextActionsSet(newContextActionSet);
+ }
+
+ return
+ {
+ Object.values(contextActionsSet.getContextActions()).map((contextAction) => {
+ return
{}} />
+ })
+ }
+
+}
\ No newline at end of file
diff --git a/frontend/react/src/ui/ui.tsx b/frontend/react/src/ui/ui.tsx
index 23f34707..c6ef52e2 100644
--- a/frontend/react/src/ui/ui.tsx
+++ b/frontend/react/src/ui/ui.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react'
+import React, { useEffect, useState } from 'react'
import './ui.css'
import { EventsProvider } from '../eventscontext'
@@ -16,6 +16,7 @@ import { getApp, setupApp } from '../olympusapp'
import { LoginModal } from './modals/login'
import { sha256 } from 'js-sha256'
import { MiniMapPanel } from './panels/minimappanel'
+import { UnitMouseControlBar } from './panels/unitmousecontrolbar'
export type OlympusState = {
mainMenuVisible: boolean,
@@ -29,7 +30,7 @@ export type OlympusState = {
}
export function UI() {
- var [loginModalVisible, setLoginModalVisible] = useState(true);
+ var [loginModalVisible, setLoginModalVisible] = useState(false /* Temporary during devel */);
var [mainMenuVisible, setMainMenuVisible] = useState(false);
var [spawnMenuVisible, setSpawnMenuVisible] = useState(false);
var [unitControlMenuVisible, setUnitControlMenuVisible] = useState(false);
@@ -91,6 +92,15 @@ export function UI() {
setLoginModalVisible(false);
}
+ /* Temporary during devel */
+ useEffect(() => {
+ window.setTimeout(() => {
+ checkPassword("admin");
+ connect("devel");
+ }, 1000)
+ }, [])
+
+
return (
diff --git a/frontend/react/src/unit/contextaction.ts b/frontend/react/src/unit/contextaction.ts
index dd3a4b6b..a6087d9c 100644
--- a/frontend/react/src/unit/contextaction.ts
+++ b/frontend/react/src/unit/contextaction.ts
@@ -1,3 +1,4 @@
+import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { Unit } from "./unit";
export interface ContextActionOptions {
@@ -11,13 +12,15 @@ export class ContextAction {
#callback: CallableFunction | null = null;
#units: Unit[] = [];
#hideContextAfterExecution: boolean = true
+ #icon: IconDefinition;
#options: ContextActionOptions;
- constructor(id: string, label: string, description: string, callback: CallableFunction, hideContextAfterExecution: boolean = true, options: ContextActionOptions) {
+ constructor(id: string, label: string, description: string, icon: IconDefinition, callback: CallableFunction, hideContextAfterExecution: boolean = true, options: ContextActionOptions) {
this.#id = id;
this.#label = label;
this.#description = description;
this.#callback = callback;
+ this.#icon = icon;
this.#hideContextAfterExecution = hideContextAfterExecution;
this.#options = {
"isScenic": false,
@@ -49,6 +52,10 @@ export class ContextAction {
return this.#callback;
}
+ getIcon() {
+ return this.#icon;
+ }
+
executeCallback() {
if (this.#callback)
this.#callback(this.#units);
diff --git a/frontend/react/src/unit/contextactionset.ts b/frontend/react/src/unit/contextactionset.ts
index d061cd1d..833ec3a3 100644
--- a/frontend/react/src/unit/contextactionset.ts
+++ b/frontend/react/src/unit/contextactionset.ts
@@ -1,18 +1,16 @@
+import { LatLng } from "leaflet";
import { ContextAction, ContextActionOptions } from "./contextaction";
import { Unit } from "./unit";
+import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
export class ContextActionSet {
#contextActions: {[key: string]: ContextAction} = {};
- constructor() {
-
- }
-
- addContextAction(unit: Unit, id: string, label: string, description: string, callback: CallableFunction, hideContextAfterExecution: boolean = true, options?:ContextActionOptions) {
+ addContextAction(unit: Unit, id: string, label: string, description: string, icon: IconDefinition, callback: (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => void, hideContextAfterExecution: boolean = true, options?:ContextActionOptions) {
options = options || {};
if (!(id in this.#contextActions)) {
- this.#contextActions[id] = new ContextAction(id, label, description, callback, hideContextAfterExecution, options);
+ this.#contextActions[id] = new ContextAction(id, label, description, icon, callback, hideContextAfterExecution, options);
}
this.#contextActions[id].addUnit(unit);
}
@@ -20,6 +18,4 @@ export class ContextActionSet {
getContextActions() {
return this.#contextActions;
}
-
-
}
\ No newline at end of file
diff --git a/frontend/react/src/unit/unit.ts b/frontend/react/src/unit/unit.ts
index 5c587393..2113e6da 100644
--- a/frontend/react/src/unit/unit.ts
+++ b/frontend/react/src/unit/unit.ts
@@ -15,6 +15,8 @@ import { RangeCircle } from "../map/rangecircle";
import { Group } from './group';
import { ContextActionSet } from './contextactionset';
import * as turf from "@turf/turf";
+import { olIconsDiamond, olIconsEchelonLh, olIconsEchelonRh, olIconsFollow, olIconsFront, olIconsGroupGround, olIconsLandAtPoint, olIconsLineAbreast, olIconsTrail, olStatesAttack, olStatesRefuel } from '../ui/components/olicons';
+import { faArrowDown, faExclamation, faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
var pathIcon = new Icon({
iconUrl: '/resources/theme/images/markers/marker-icon.png',
@@ -230,7 +232,7 @@ export abstract class Unit extends CustomMarker {
/** Get the actions that this unit can perform
*
*/
- abstract appendContextActions(contextActionSet: ContextActionSet, targetUnit: Unit | null, targetPosition: LatLng | null): void;
+ abstract appendContextActions(contextActionSet: ContextActionSet): void;
/**
*
@@ -998,14 +1000,14 @@ export abstract class Unit extends CustomMarker {
showFollowOptions(units: Unit[]) {
var contextActionSet = new ContextActionSet();
- contextActionSet.addContextAction(this, 'trail', "Trail", "Follow unit in trail formation", () => this.applyFollowOptions('trail', units));
- contextActionSet.addContextAction(this, 'echelon-lh', "Echelon (LH)", "Follow unit in echelon left formation", () => this.applyFollowOptions('echelon-lh', units));
- contextActionSet.addContextAction(this, 'echelon-rh', "Echelon (RH)", "Follow unit in echelon right formation", () => this.applyFollowOptions('echelon-rh', units));
- contextActionSet.addContextAction(this, 'line-abreast-lh', "Line abreast (LH)", "Follow unit in line abreast left formation", () => this.applyFollowOptions('line-abreast-lh', units));
- contextActionSet.addContextAction(this, 'line-abreast-rh', "Line abreast (RH)", "Follow unit in line abreast right formation", () => this.applyFollowOptions('line-abreast-rh', units));
- contextActionSet.addContextAction(this, 'front', "Front", "Fly in front of unit", () => this.applyFollowOptions('front', units));
- contextActionSet.addContextAction(this, 'diamond', "Diamond", "Follow unit in diamond formation", () => this.applyFollowOptions('diamond', units));
- contextActionSet.addContextAction(this, 'custom', "Custom", "Set a custom formation position", () => this.applyFollowOptions('custom', units));
+ contextActionSet.addContextAction(this, 'trail', "Trail", "Follow unit in trail formation", olIconsTrail, () => this.applyFollowOptions('trail', units));
+ contextActionSet.addContextAction(this, 'echelon-lh', "Echelon (LH)", "Follow unit in echelon left formation", olIconsEchelonLh, () => this.applyFollowOptions('echelon-lh', units));
+ contextActionSet.addContextAction(this, 'echelon-rh', "Echelon (RH)", "Follow unit in echelon right formation", olIconsEchelonRh, () => this.applyFollowOptions('echelon-rh', units));
+ contextActionSet.addContextAction(this, 'line-abreast-lh', "Line abreast (LH)", "Follow unit in line abreast left formation", olIconsLineAbreast, () => this.applyFollowOptions('line-abreast-lh', units));
+ contextActionSet.addContextAction(this, 'line-abreast-rh', "Line abreast (RH)", "Follow unit in line abreast right formation", olIconsLineAbreast, () => this.applyFollowOptions('line-abreast-rh', units));
+ contextActionSet.addContextAction(this, 'front', "Front", "Fly in front of unit", olIconsFront, () => this.applyFollowOptions('front', units));
+ contextActionSet.addContextAction(this, 'diamond', "Diamond", "Follow unit in diamond formation", olIconsDiamond, () => this.applyFollowOptions('diamond', units));
+ contextActionSet.addContextAction(this, 'custom', "Custom", "Set a custom formation position", faExclamation, () => this.applyFollowOptions('custom', units));
//getApp().getMap().getUnitContextMenu().setContextActions(contextActionSet);
getApp().getMap().showUnitContextMenu();
@@ -1091,7 +1093,7 @@ export abstract class Unit extends CustomMarker {
units.push(this);
units.forEach((unit: Unit) => {
- unit.appendContextActions(contextActionSet, this, null);
+ unit.appendContextActions(contextActionSet);
})
if (Object.keys(contextActionSet.getContextActions()).length > 0) {
@@ -1495,24 +1497,13 @@ export abstract class AirUnit extends Unit {
};
}
- appendContextActions(contextActionSet: ContextActionSet, targetUnit: Unit | null, targetPosition: LatLng | null) {
- if (targetUnit !== null) {
- if (targetUnit != this) {
- contextActionSet.addContextAction(this, "attack", "Attack unit", "Attack the unit using A/A or A/G weapons", (units: Unit[]) => { getApp().getUnitsManager().attackUnit(targetUnit.ID, units) });
- contextActionSet.addContextAction(this, "follow", "Follow unit", "Follow this unit in formation", (units: Unit[]) => { targetUnit.showFollowOptions(units); }, false); // Don't hide the context menu after the execution (to show the follow options)
- }
- if (targetUnit.getSelected()) {
- contextActionSet.addContextAction(this, "refuel", "Refuel", "Refuel units at the nearest AAR Tanker. If no tanker is available the unit will RTB", (units: Unit[]) => { getApp().getUnitsManager().refuel(units) });
- }
- if (getApp().getUnitsManager().getSelectedUnits().length == 1 && targetUnit === this) {
- contextActionSet.addContextAction(this, "center-map", "Center map", "Center the map on the unit and follow it", () => { getApp().getMap().centerOnUnit(this.ID); });
- }
- }
-
- if (targetPosition !== null) {
- contextActionSet.addContextAction(this, "bomb", "Precision bombing", "Precision bombing of a specific point", (units: Unit[]) => { getApp().getUnitsManager().bombPoint(targetPosition, units) });
- contextActionSet.addContextAction(this, "carpet-bomb", "Carpet bombing", "Carpet bombing close to a point", (units: Unit[]) => { getApp().getUnitsManager().carpetBomb(targetPosition, units) });
- }
+ appendContextActions(contextActionSet: ContextActionSet) {
+ contextActionSet.addContextAction(this, "attack", "Attack unit", "Attack the unit using A/A or A/G weapons", olStatesAttack, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().attackUnit(targetUnit.ID, units) });
+ contextActionSet.addContextAction(this, "follow", "Follow unit", "Follow this unit in formation", olIconsFollow, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { targetUnit.showFollowOptions(units); }, false); // Don't hide the context menu after the execution (to show the follow options)
+ contextActionSet.addContextAction(this, "refuel", "Refuel", "Refuel units at the nearest AAR Tanker. If no tanker is available the unit will RTB", olStatesRefuel, (units: Unit[]) => { getApp().getUnitsManager().refuel(units) });
+ contextActionSet.addContextAction(this, "center-map", "Center map", "Center the map on the unit and follow it", faQuestionCircle, () => { getApp().getMap().centerOnUnit(this.ID); });
+ contextActionSet.addContextAction(this, "bomb", "Precision bombing", "Precision bombing of a specific point", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().bombPoint(targetPosition, units) });
+ contextActionSet.addContextAction(this, "carpet-bomb", "Carpet bombing", "Carpet bombing close to a point", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().carpetBomb(targetPosition, units) });
}
}
@@ -1525,12 +1516,8 @@ export class Aircraft extends AirUnit {
return "Aircraft";
}
- appendContextActions(contextActionSet: ContextActionSet, targetUnit: Unit | null, targetPosition: LatLng | null) {
- super.appendContextActions(contextActionSet, targetUnit, targetPosition);
-
- if (targetPosition === null && this.getSelected()) {
- contextActionSet.addContextAction(this, "refuel", "Refuel", "Refuel units at the nearest AAR Tanker. If no tanker is available the unit will RTB", (units: Unit[]) => { getApp().getUnitsManager().refuel(units) });
- }
+ appendContextActions(contextActionSet: ContextActionSet) {
+ super.appendContextActions(contextActionSet);
}
getMarkerCategory() {
@@ -1551,11 +1538,9 @@ export class Helicopter extends AirUnit {
return "Helicopter";
}
- appendContextActions(contextActionSet: ContextActionSet, targetUnit: Unit | null, targetPosition: LatLng | null) {
- super.appendContextActions(contextActionSet, targetUnit, targetPosition);
-
- if (targetPosition !== null)
- contextActionSet.addContextAction(this, "land-at-point", "Land here", "land at this precise location", (units: Unit[]) => { getApp().getUnitsManager().landAtPoint(targetPosition, units) });
+ appendContextActions(contextActionSet: ContextActionSet) {
+ super.appendContextActions(contextActionSet);
+ contextActionSet.addContextAction(this, "land-at-point", "Land here", "Land at this precise location", olIconsLandAtPoint, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().landAtPoint(targetPosition, units) });
}
getMarkerCategory() {
@@ -1589,34 +1574,23 @@ export class GroundUnit extends Unit {
};
}
- appendContextActions(contextActionSet: ContextActionSet, targetUnit: Unit | null, targetPosition: LatLng | null) {
- contextActionSet.addContextAction(this, "group-ground", "Group ground units", "Create a group of ground units", (units: Unit[]) => { getApp().getUnitsManager().createGroup(units) });
-
- if (targetUnit !== null) {
- if (targetUnit != this) {
- contextActionSet.addContextAction(this, "attack", "Attack unit", "Attack the unit using A/A or A/G weapons", (units: Unit[]) => { getApp().getUnitsManager().attackUnit(targetUnit.ID, units) });
- }
-
- if (getApp().getUnitsManager().getSelectedUnits().length == 1 && targetUnit === this) {
- contextActionSet.addContextAction(this, "center-map", "Center map", "Center the map on the unit and follow it", () => { getApp().getMap().centerOnUnit(this.ID); });
- }
+ appendContextActions(contextActionSet: ContextActionSet) {
+ contextActionSet.addContextAction(this, "group-ground", "Group ground units", "Create a group of ground units", olIconsGroupGround, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().createGroup(units) });
+ contextActionSet.addContextAction(this, "attack", "Attack unit", "Attack the unit using A/A or A/G weapons", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().attackUnit(targetUnit.ID, units) });
+ contextActionSet.addContextAction(this, "center-map", "Center map", "Center the map on the unit and follow it", faQuestionCircle, () => { getApp().getMap().centerOnUnit(this.ID); });
+
+ if (this.canTargetPoint()) {
+ contextActionSet.addContextAction(this, "fire-at-area", "Fire at area", "Fire at a specific area on the ground", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().fireAtArea(targetPosition, units) });
+ contextActionSet.addContextAction(this, "simulate-fire-fight", "Simulate fire fight", "Simulate a fire fight by shooting randomly in a certain large area.\nWARNING: works correctly only on neutral units, blue or red units will aim", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().simulateFireFight(targetPosition, units) });
}
- if (targetPosition !== null) {
- if (this.canTargetPoint()) {
- contextActionSet.addContextAction(this, "fire-at-area", "Fire at area", "Fire at a specific area on the ground", (units: Unit[]) => { getApp().getUnitsManager().fireAtArea(targetPosition, units) });
- contextActionSet.addContextAction(this, "simulate-fire-fight", "Simulate fire fight", "Simulate a fire fight by shooting randomly in a certain large area.\nWARNING: works correctly only on neutral units, blue or red units will aim", (units: Unit[]) => { getApp().getUnitsManager().simulateFireFight(targetPosition, units) });
- }
- }
- else {
- if (this.canAAA()) {
- contextActionSet.addContextAction(this, "scenic-aaa", "Scenic AAA", "Shoot AAA in the air without aiming at any target, when an enemy unit gets close enough.\nWARNING: works correctly only on neutral units, blue or red units will aim", (units: Unit[]) => { getApp().getUnitsManager().scenicAAA(units) }, undefined, {
- "isScenic": true
- });
- contextActionSet.addContextAction(this, "miss-aaa", "Dynamic accuracy AAA", "Shoot AAA towards the closest enemy unit, but don't aim precisely.\nWARNING: works correctly only on neutral units, blue or red units will aim", (units: Unit[]) => { getApp().getUnitsManager().missOnPurpose(units) }, undefined, {
- "isScenic": true
- });
- }
+ if (this.canAAA()) {
+ contextActionSet.addContextAction(this, "scenic-aaa", "Scenic AAA", "Shoot AAA in the air without aiming at any target, when an enemy unit gets close enough.\nWARNING: works correctly only on neutral units, blue or red units will aim", faQuestionCircle, (units: Unit[]) => { getApp().getUnitsManager().scenicAAA(units) }, undefined, {
+ "isScenic": true
+ });
+ contextActionSet.addContextAction(this, "miss-aaa", "Dynamic accuracy AAA", "Shoot AAA towards the closest enemy unit, but don't aim precisely.\nWARNING: works correctly only on neutral units, blue or red units will aim", faQuestionCircle, (units: Unit[]) => { getApp().getUnitsManager().missOnPurpose(units) }, undefined, {
+ "isScenic": true
+ });
}
}
@@ -1688,21 +1662,11 @@ export class NavyUnit extends Unit {
};
}
- appendContextActions(contextActionSet: ContextActionSet, targetUnit: Unit | null, targetPosition: LatLng | null) {
- contextActionSet.addContextAction(this, "group-navy", "Group navy units", "Create a group of navy units", (units: Unit[]) => { getApp().getUnitsManager().createGroup(units) });
-
- if (targetUnit !== null) {
- if (targetUnit != this) {
- contextActionSet.addContextAction(this, "attack", "Attack unit", "Attack the unit using A/A or A/G weapons", (units: Unit[]) => { getApp().getUnitsManager().attackUnit(targetUnit.ID, units) });
- }
- if (getApp().getUnitsManager().getSelectedUnits().length == 1 && targetUnit === this) {
- contextActionSet.addContextAction(this, "center-map", "Center map", "Center the map on the unit and follow it", () => { getApp().getMap().centerOnUnit(this.ID); });
- }
- }
-
- if (targetPosition !== null) {
- contextActionSet.addContextAction(this, "fire-at-area", "Fire at area", "Fire at a specific area on the ground", (units: Unit[]) => { getApp().getUnitsManager().fireAtArea(targetPosition, units) });
- }
+ appendContextActions(contextActionSet: ContextActionSet) {
+ contextActionSet.addContextAction(this, "group-navy", "Group navy units", "Create a group of navy units", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().createGroup(units) });
+ contextActionSet.addContextAction(this, "attack", "Attack unit", "Attack the unit using A/A or A/G weapons", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().attackUnit(targetUnit.ID, units) });
+ contextActionSet.addContextAction(this, "center-map", "Center map", "Center the map on the unit and follow it", faQuestionCircle, () => { getApp().getMap().centerOnUnit(this.ID); });
+ contextActionSet.addContextAction(this, "fire-at-area", "Fire at area", "Fire at a specific area on the ground", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().fireAtArea(targetPosition, units) });
}
getCategory() {
diff --git a/scripts/python/map_generator/.gitignore b/scripts/python/map_generator/.gitignore
index 6efe1baa..87d6c496 100644
--- a/scripts/python/map_generator/.gitignore
+++ b/scripts/python/map_generator/.gitignore
@@ -1,3 +1,3 @@
.venv
tiles
-screenshots
\ No newline at end of file
+screenshots