diff --git a/client/demo.js b/client/demo.js
index 0860c08a..f344e58f 100644
--- a/client/demo.js
+++ b/client/demo.js
@@ -535,24 +535,49 @@ class DemoDataGenerator {
};
airbases(req, res){
- var ret = {airbases: {}};
+ var ret = {airbases: {
+ ["0"]: {
+ callsign: "Neutral",
+ lat: 37.3,
+ lng: -115.8,
+ coalition: "neutral"
+ },
+ ["1"]: {
+ callsign: "Red",
+ lat: 37.3,
+ lng: -115.75,
+ coalition: "red"
+ },
+ ["2"]: {
+ callsign: "Blue",
+ lat: 37.3,
+ lng: -115.7,
+ coalition: "blue"
+ }
+ }};
res.send(JSON.stringify(ret));
};
bullseyes(req, res){
- var ret = {bullseyes: {}};
+ var ret = {bullseyes: {
+ "0": {
+ lat: 37.25,
+ lng: -115.8
+ },
+ "1": {
+ lat: 37.25,
+ lng: -115.75
+ },
+ "2": {
+ lat: 37.25,
+ lng: -115.7
+ }
+ }};
res.send(JSON.stringify(ret));
};
generateRandomUnitsDemoData(unitsNumber)
{
- //var units = {};
- //for (let i = 0; i < unitsNumber; i++)
- //{
- // units[String(i)] = JSON.parse(JSON.stringify(DEMO_UNIT_DATA));
- // units[String(i)].flightData.latitude += (Math.random() - 0.5) * 0.3;
- // units[String(i)].flightData.longitude += (Math.random() - 0.5) * 0.3;
- //}
return {"units": DEMO_UNIT_DATA};
}
}
diff --git a/client/public/stylesheets/airbases.css b/client/public/stylesheets/airbases.css
index eedff840..5ded6a48 100644
--- a/client/public/stylesheets/airbases.css
+++ b/client/public/stylesheets/airbases.css
@@ -1,41 +1,44 @@
-.airbase-marker-container {
- height: 60px;
- width: 60px;
- left: -30px;
- top: -30px;
- border: 1px transparent solid;
+:root {
+ --airbase-marker-height: 63px;
+ --airbase-marker-width: 63px;
+}
+
+[data-object|="airbase"] {
+ align-items: center;
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ position: relative;
+}
+
+[data-hide-airbase] #map-container [data-object|="airbase"] {
+ display: none;
+}
+
+/******************************
+Marker
+******************************/
+
+[data-object|="airbase"] .airbase {
+ background-color: transparent;
+ background-repeat: no-repeat;
+ background-size: cover;
position: absolute;
+ transform-origin: center;
+ z-index: 3;
}
-.airbase-marker-image {
- height: 60px;
- width: 60px;
- left: 0px;
- top: 0px;
- display: block;
- position: absolute;
- filter: drop-shadow(1px 1px 0 white) drop-shadow(1px -1px 0 white) drop-shadow(-1px 1px 0 white) drop-shadow(-1px -1px 0 white);
- opacity: 0.8;
+/* Airbase */
+[data-object|="airbase"] .airbase-marker {
+ background-image: var(--airbase-marker-neutral-url);
+ height: var(--airbase-marker-height);
+ width: var(--airbase-marker-width);
}
-.blue.airbase-marker-image {
- filter: invert(40%) sepia(94%) saturate(2477%) hue-rotate(197deg) brightness(92%) contrast(91%) drop-shadow(1px 1px #FFFA) drop-shadow(1px -1px #FFFA) drop-shadow(-1px 1px 0px #FFFA) drop-shadow(-1px -1px #FFFA);
+[data-object|="airbase"][data-coalition="red"] .airbase-marker {
+ background-image: var(--airbase-marker-red-url);
}
-.red.airbase-marker-image {
- filter:invert(32%) sepia(91%) saturate(5128%) hue-rotate(349deg) brightness(97%) contrast(97%) drop-shadow(1px 1px #FFFA) drop-shadow(1px -1px #FFFA) drop-shadow(-1px 1px 0px #FFFA) drop-shadow(-1px -1px #FFFA);
-}
-
-.neutral.airbase-marker-image {
- filter: invert(71%) sepia(12%) saturate(9%) hue-rotate(319deg) brightness(92%) contrast(96%) drop-shadow(1px 1px #000A) drop-shadow(1px -1px #000A) drop-shadow(-1px 1px 0px #000A) drop-shadow(-1px -1px #000A);
-}
-
-.airbase-marker-name {
- bottom: -20px;
- position: absolute;
- text-align: center;
- font: 800 14px Arial;
- white-space: nowrap;
- -webkit-text-fill-color: white;
- -webkit-text-stroke: 1px;
-}
+[data-object|="airbase"][data-coalition="blue"] .airbase-marker {
+ background-image: var(--airbase-marker-blue-url);
+}
\ No newline at end of file
diff --git a/client/public/stylesheets/contextmenu.css b/client/public/stylesheets/contextmenus.css
similarity index 87%
rename from client/public/stylesheets/contextmenu.css
rename to client/public/stylesheets/contextmenus.css
index e29a39f9..5c41b05c 100644
--- a/client/public/stylesheets/contextmenu.css
+++ b/client/public/stylesheets/contextmenus.css
@@ -1,8 +1,5 @@
-#contextmenu {
+#map-contextmenu {
position: absolute;
-}
-
-#contextmenu {
display: flex;
flex-direction: column;
row-gap: 5px;
@@ -38,7 +35,7 @@
margin-right: 10px;
}
-#contextmenu>div:nth-child(2){
+#map-contextmenu>div:nth-child(2){
display: flex;
flex-direction: row;
justify-content: space-between;
@@ -46,22 +43,22 @@
padding-right: 0px;
}
-#contextmenu>ul{
+#map-contextmenu>ul{
max-height: 200px;
overflow-x: hidden;
overflow-y: auto;
}
-#contextmenu .ol-panel {
+#map-contextmenu .ol-panel {
width: 100%;
border-radius: var(--border-radius-sm);
}
-#contextmenu ul {
+#map-contextmenu ul {
margin: 0px;
}
-#contextmenu>div:nth-child(n+3){
+#map-contextmenu>div:nth-child(n+3){
display: flex;
flex-direction: column;
justify-content: space-between;
@@ -69,7 +66,7 @@
row-gap: 5px;
}
-#contextmenu .ol-select-container{
+#map-contextmenu .ol-select-container{
width: 100%;
flex:0 0 auto;
align-self: stretch;
@@ -205,4 +202,24 @@
[data-smoke-color="green"]::before{ background-color: green; }
[data-smoke-color="orange"]::before{ background-color: orange; }
+/* Unit context menu */
+#unit-contextmenu {
+ position: absolute;
+ display: flex;
+ flex-direction: column;
+ row-gap: 5px;
+ width: 150px;
+ height: fit-content;
+ z-index: 1000;
+}
+/* Airbase context menu */
+#airbase-contextmenu {
+ position: absolute;
+ display: flex;
+ flex-direction: column;
+ row-gap: 5px;
+ width: 230px;
+ height: fit-content;
+ z-index: 1000;
+}
diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css
index e22b7d82..597d8ab6 100644
--- a/client/public/stylesheets/olympus.css
+++ b/client/public/stylesheets/olympus.css
@@ -1,6 +1,6 @@
@import url("layout.css");
@import url("airbases.css");
-@import url("contextmenu.css");
+@import url("contextmenus.css");
@import url("units.css");
/* Variables definitions */
diff --git a/client/public/themes/olympus/images/icon_airbase_blue.svg b/client/public/themes/olympus/images/icon_airbase_blue.svg
new file mode 100644
index 00000000..0800974c
--- /dev/null
+++ b/client/public/themes/olympus/images/icon_airbase_blue.svg
@@ -0,0 +1,10 @@
+
diff --git a/client/public/themes/olympus/images/icon_airbase_neutral.svg b/client/public/themes/olympus/images/icon_airbase_neutral.svg
new file mode 100644
index 00000000..69713bd5
--- /dev/null
+++ b/client/public/themes/olympus/images/icon_airbase_neutral.svg
@@ -0,0 +1,10 @@
+
diff --git a/client/public/themes/olympus/images/icon_airbase_red.svg b/client/public/themes/olympus/images/icon_airbase_red.svg
new file mode 100644
index 00000000..45d55abd
--- /dev/null
+++ b/client/public/themes/olympus/images/icon_airbase_red.svg
@@ -0,0 +1,10 @@
+
diff --git a/client/public/themes/olympus/olympus.css b/client/public/themes/olympus/olympus.css
index 741a00bb..48d9ca93 100644
--- a/client/public/themes/olympus/olympus.css
+++ b/client/public/themes/olympus/olympus.css
@@ -206,4 +206,12 @@
--spawn-aircraft-url: url( "/themes/olympus/images/spawn_aircraft.svg" );
--spawn-ground-url: url( "/themes/olympus/images/spawn_ground.svg" );
--spawn-smoke-url: url( "/themes/olympus/images/spawn_smoke.svg" );
+
+ /*** Airbase ***/
+ --airbase-marker-height: 63px;
+ --airbase-marker-width: 63px;
+
+ --airbase-marker-blue-url: url( "/themes/olympus/images/icon_airbase_blue.svg" );
+ --airbase-marker-neutral-url: url( "/themes/olympus/images/icon_airbase_neutral.svg" );
+ --airbase-marker-red-url: url( "/themes/olympus/images/icon_airbase_red.svg" );
}
\ No newline at end of file
diff --git a/client/src/controls/airbasecontextmenu.ts b/client/src/controls/airbasecontextmenu.ts
new file mode 100644
index 00000000..f5dae337
--- /dev/null
+++ b/client/src/controls/airbasecontextmenu.ts
@@ -0,0 +1,9 @@
+import { ContextMenu } from "./contextmenu";
+
+export class AirbaseContextMenu extends ContextMenu {
+ constructor(id: string)
+ {
+ super(id);
+
+ }
+}
\ No newline at end of file
diff --git a/client/src/controls/contextmenu.ts b/client/src/controls/contextmenu.ts
index 0eca9462..e0cbf644 100644
--- a/client/src/controls/contextmenu.ts
+++ b/client/src/controls/contextmenu.ts
@@ -1,74 +1,16 @@
import { LatLng } from "leaflet";
-import { getActiveCoalition, setActiveCoalition } from "..";
-import { ContextMenuOption } from "../@types/dom";
-import { ClickEvent } from "../map/map";
-import { spawnAircraft, spawnGroundUnit } from "../server/server";
-import { aircraftDatabase } from "../units/aircraftdatabase";
-import { groundUnitsDatabase } from "../units/groundunitsdatabase";
-import { Dropdown } from "./dropdown";
-
-export interface SpawnOptions {
- role: string;
- type: string;
- latlng: LatLng;
- coalition: string;
- loadout: string | null;
- airbaseName: string | null;
-}
export class ContextMenu {
#container: HTMLElement | null;
#latlng: LatLng = new LatLng(0, 0);
- #aircraftRoleDropdown: Dropdown;
- #aircraftTypeDropdown: Dropdown;
- #aircraftLoadoutDropdown: Dropdown;
- #groundUnitRoleDropdown: Dropdown;
- #groundUnitTypeDropdown: Dropdown;
- #spawnOptions: SpawnOptions = {role: "", type: "", latlng: this.#latlng, loadout: null, coalition: "blue", airbaseName: null};
- constructor(id: string,) {
+ constructor(id: string) {
this.#container = document.getElementById(id);
- this.#container?.querySelector("#context-menu-switch")?.addEventListener('change', (e) => this.#onSwitch(e));
-
- this.#aircraftRoleDropdown = new Dropdown("aircraft-role-options", (role: string) => this.#setAircraftRole(role));
- this.#aircraftTypeDropdown = new Dropdown("aircraft-type-options", (type: string) => this.#setAircraftType(type));
- this.#aircraftLoadoutDropdown = new Dropdown("loadout-options", (loadout: string) => this.#setAircraftLoadout(loadout));
- this.#groundUnitRoleDropdown = new Dropdown("ground-unit-role-options", (role: string) => this.#setGroundUnitRole(role));
- this.#groundUnitTypeDropdown = new Dropdown("ground-unit-type-options", (type: string) => this.#setGroundUnitType(type));
-
- document.addEventListener("contextMenuShow", (e: any) => {
- this.#container?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", e.detail.type !== "aircraft");
- this.#container?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", e.detail.type === "aircraft");
- this.#container?.querySelector("#ground-unit-spawn-menu")?.classList.toggle("hide", e.detail.type !== "ground-unit");
- this.#container?.querySelector("#ground-unit-spawn-button")?.classList.toggle("is-open", e.detail.type === "ground-unit");
- this.#container?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", e.detail.type !== "smoke");
- this.#container?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", e.detail.type === "smoke");
-
- this.#resetAircraftRole();
- this.#resetAircraftType();
- this.#resetGroundUnitRole();
- this.#resetGroundUnitType();
- })
-
- document.addEventListener("contextMenuDeployAircraft", () => {
- this.hide();
- this.#spawnOptions.coalition = getActiveCoalition();
- if (this.#spawnOptions)
- spawnAircraft(this.#spawnOptions);
- })
-
- document.addEventListener("contextMenuDeployGroundUnit", () => {
- this.hide();
- this.#spawnOptions.coalition = getActiveCoalition();
- if (this.#spawnOptions)
- spawnGroundUnit(this.#spawnOptions);
- })
-
this.hide();
}
show(x: number, y: number, latlng: LatLng) {
- this.#spawnOptions.latlng = latlng;
+ this.#latlng = latlng;
this.#container?.classList.toggle("hide", false);
if (this.#container != null) {
if (x + this.#container.offsetWidth < window.innerWidth)
@@ -87,117 +29,13 @@ export class ContextMenu {
this.#container?.classList.toggle("hide", true);
}
- #onSwitch(e: any) {
- if (this.#container != null) {
- if (e.srcElement.checked)
- setActiveCoalition("red");
- else
- setActiveCoalition("blue");
- }
- }
-
- /********* Aircraft spawn menu *********/
- #setAircraftRole(role: string)
+ getContainer()
{
- if (this.#spawnOptions != null)
- {
- this.#spawnOptions.role = role;
- this.#resetAircraftRole();
- this.#aircraftTypeDropdown.setOptions(aircraftDatabase.getLabelsByRole(role));
- this.#aircraftTypeDropdown.selectValue(0);
- }
+ return this.#container;
}
- #resetAircraftRole() {
- (this.#container?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
- (this.#container?.querySelector("#loadout-list")).replaceChildren();
- this.#aircraftRoleDropdown.reset();
- this.#aircraftTypeDropdown.reset();
- this.#aircraftRoleDropdown.setOptions(aircraftDatabase.getRoles());
- }
-
- #setAircraftType(label: string)
+ getLatLng()
{
- if (this.#spawnOptions != null)
- {
- this.#resetAircraftType();
- var type = aircraftDatabase.getNameByLabel(label);
- if (type != null)
- {
- this.#spawnOptions.type = type;
- this.#aircraftLoadoutDropdown.setOptions(aircraftDatabase.getLoadoutNamesByRole(type, this.#spawnOptions.role));
- this.#aircraftLoadoutDropdown.selectValue(0);
- var image = (this.#container?.querySelector("#unit-image"));
- image.src = `images/units/${aircraftDatabase.getByLabel(label)?.filename}`;
- image.classList.toggle("hide", false);
- }
- }
- }
-
- #resetAircraftType() {
- (this.#container?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
- (this.#container?.querySelector("#loadout-list")).replaceChildren();
- this.#aircraftLoadoutDropdown.reset();
- (this.#container?.querySelector("#unit-image")).classList.toggle("hide", true);
- }
-
- #setAircraftLoadout(loadoutName: string)
- {
- if (this.#spawnOptions != null)
- {
- var loadout = aircraftDatabase.getLoadoutsByName(this.#spawnOptions.type, loadoutName);
- if (loadout)
- {
- this.#spawnOptions.loadout = loadout.code;
- (this.#container?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
- var items = loadout.items.map((item: any) => {return `${item.quantity}x ${item.name}`;});
- items.length == 0? items.push("Empty loadout"): "";
- (this.#container?.querySelector("#loadout-list")).replaceChildren(
- ...items.map((item: any) => {
- var div = document.createElement('div');
- div.innerText = item;
- return div;
- })
- )
- }
- }
- }
-
- /********* Ground unit spawn menu *********/
- #setGroundUnitRole(role: string)
- {
- if (this.#spawnOptions != null)
- {
- this.#spawnOptions.role = role;
- this.#resetGroundUnitRole();
- this.#groundUnitTypeDropdown.setOptions(groundUnitsDatabase.getLabelsByRole(role));
- this.#groundUnitTypeDropdown.selectValue(0);
- }
- }
-
- #resetGroundUnitRole() {
- (this.#container?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
- (this.#container?.querySelector("#loadout-list")).replaceChildren();
- this.#groundUnitRoleDropdown.reset();
- this.#groundUnitTypeDropdown.reset();
- this.#groundUnitRoleDropdown.setOptions(groundUnitsDatabase.getRoles());
- }
-
- #setGroundUnitType(label: string)
- {
- if (this.#spawnOptions != null)
- {
- this.#resetGroundUnitType();
- var type = groundUnitsDatabase.getNameByLabel(label);
- if (type != null)
- {
- this.#spawnOptions.type = type;
- (this.#container?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
- }
- }
- }
-
- #resetGroundUnitType() {
- (this.#container?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
+ return this.#latlng;
}
}
\ No newline at end of file
diff --git a/client/src/controls/mapcontextmenu.ts b/client/src/controls/mapcontextmenu.ts
new file mode 100644
index 00000000..fea0bc64
--- /dev/null
+++ b/client/src/controls/mapcontextmenu.ts
@@ -0,0 +1,185 @@
+import { LatLng } from "leaflet";
+import { getActiveCoalition, setActiveCoalition } from "..";
+import { spawnAircraft, spawnGroundUnit } from "../server/server";
+import { aircraftDatabase } from "../units/aircraftdatabase";
+import { groundUnitsDatabase } from "../units/groundunitsdatabase";
+import { ContextMenu } from "./contextmenu";
+import { Dropdown } from "./dropdown";
+
+export interface SpawnOptions {
+ role: string;
+ type: string;
+ latlng: LatLng;
+ coalition: string;
+ loadout: string | null;
+ airbaseName: string | null;
+}
+
+export class MapContextMenu extends ContextMenu {
+ #aircraftRoleDropdown: Dropdown;
+ #aircraftTypeDropdown: Dropdown;
+ #aircraftLoadoutDropdown: Dropdown;
+ #groundUnitRoleDropdown: Dropdown;
+ #groundUnitTypeDropdown: Dropdown;
+ #spawnOptions: SpawnOptions = {role: "", type: "", latlng: new LatLng(0, 0), loadout: null, coalition: "blue", airbaseName: null};
+
+ constructor(id: string) {
+ super(id);
+ this.getContainer()?.querySelector("#context-menu-switch")?.addEventListener('change', (e) => this.#onSwitch(e));
+
+ this.#aircraftRoleDropdown = new Dropdown("aircraft-role-options", (role: string) => this.#setAircraftRole(role));
+ this.#aircraftTypeDropdown = new Dropdown("aircraft-type-options", (type: string) => this.#setAircraftType(type));
+ this.#aircraftLoadoutDropdown = new Dropdown("loadout-options", (loadout: string) => this.#setAircraftLoadout(loadout));
+ this.#groundUnitRoleDropdown = new Dropdown("ground-unit-role-options", (role: string) => this.#setGroundUnitRole(role));
+ this.#groundUnitTypeDropdown = new Dropdown("ground-unit-type-options", (type: string) => this.#setGroundUnitType(type));
+
+ document.addEventListener("contextMenuShow", (e: any) => {
+ this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", e.detail.type !== "aircraft");
+ this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", e.detail.type === "aircraft");
+ this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.classList.toggle("hide", e.detail.type !== "ground-unit");
+ this.getContainer()?.querySelector("#ground-unit-spawn-button")?.classList.toggle("is-open", e.detail.type === "ground-unit");
+ this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", e.detail.type !== "smoke");
+ this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", e.detail.type === "smoke");
+
+ this.#resetAircraftRole();
+ this.#resetAircraftType();
+ this.#resetGroundUnitRole();
+ this.#resetGroundUnitType();
+ })
+
+ document.addEventListener("contextMenuDeployAircraft", () => {
+ this.hide();
+ this.#spawnOptions.coalition = getActiveCoalition();
+ if (this.#spawnOptions)
+ spawnAircraft(this.#spawnOptions);
+ })
+
+ document.addEventListener("contextMenuDeployGroundUnit", () => {
+ this.hide();
+ this.#spawnOptions.coalition = getActiveCoalition();
+ if (this.#spawnOptions)
+ spawnGroundUnit(this.#spawnOptions);
+ })
+
+ this.hide();
+ }
+
+ show(x: number, y: number, latlng: LatLng) {
+ super.show(x, y, latlng);
+ this.#spawnOptions.latlng = latlng;
+ }
+
+ #onSwitch(e: any) {
+ if (this.getContainer() != null) {
+ if (e.srcElement.checked)
+ setActiveCoalition("red");
+ else
+ setActiveCoalition("blue");
+ }
+ }
+
+ /********* Aircraft spawn menu *********/
+ #setAircraftRole(role: string)
+ {
+ if (this.#spawnOptions != null)
+ {
+ this.#spawnOptions.role = role;
+ this.#resetAircraftRole();
+ this.#aircraftTypeDropdown.setOptions(aircraftDatabase.getLabelsByRole(role));
+ this.#aircraftTypeDropdown.selectValue(0);
+ }
+ }
+
+ #resetAircraftRole() {
+ (this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
+ (this.getContainer()?.querySelector("#loadout-list")).replaceChildren();
+ this.#aircraftRoleDropdown.reset();
+ this.#aircraftTypeDropdown.reset();
+ this.#aircraftRoleDropdown.setOptions(aircraftDatabase.getRoles());
+ }
+
+ #setAircraftType(label: string)
+ {
+ if (this.#spawnOptions != null)
+ {
+ this.#resetAircraftType();
+ var type = aircraftDatabase.getNameByLabel(label);
+ if (type != null)
+ {
+ this.#spawnOptions.type = type;
+ this.#aircraftLoadoutDropdown.setOptions(aircraftDatabase.getLoadoutNamesByRole(type, this.#spawnOptions.role));
+ this.#aircraftLoadoutDropdown.selectValue(0);
+ var image = (this.getContainer()?.querySelector("#unit-image"));
+ image.src = `images/units/${aircraftDatabase.getByLabel(label)?.filename}`;
+ image.classList.toggle("hide", false);
+ }
+ }
+ }
+
+ #resetAircraftType() {
+ (this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
+ (this.getContainer()?.querySelector("#loadout-list")).replaceChildren();
+ this.#aircraftLoadoutDropdown.reset();
+ (this.getContainer()?.querySelector("#unit-image")).classList.toggle("hide", true);
+ }
+
+ #setAircraftLoadout(loadoutName: string)
+ {
+ if (this.#spawnOptions != null)
+ {
+ var loadout = aircraftDatabase.getLoadoutsByName(this.#spawnOptions.type, loadoutName);
+ if (loadout)
+ {
+ this.#spawnOptions.loadout = loadout.code;
+ (this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
+ var items = loadout.items.map((item: any) => {return `${item.quantity}x ${item.name}`;});
+ items.length == 0? items.push("Empty loadout"): "";
+ (this.getContainer()?.querySelector("#loadout-list")).replaceChildren(
+ ...items.map((item: any) => {
+ var div = document.createElement('div');
+ div.innerText = item;
+ return div;
+ })
+ )
+ }
+ }
+ }
+
+ /********* Ground unit spawn menu *********/
+ #setGroundUnitRole(role: string)
+ {
+ if (this.#spawnOptions != null)
+ {
+ this.#spawnOptions.role = role;
+ this.#resetGroundUnitRole();
+ this.#groundUnitTypeDropdown.setOptions(groundUnitsDatabase.getLabelsByRole(role));
+ this.#groundUnitTypeDropdown.selectValue(0);
+ }
+ }
+
+ #resetGroundUnitRole() {
+ (this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
+ (this.getContainer()?.querySelector("#loadout-list")).replaceChildren();
+ this.#groundUnitRoleDropdown.reset();
+ this.#groundUnitTypeDropdown.reset();
+ this.#groundUnitRoleDropdown.setOptions(groundUnitsDatabase.getRoles());
+ }
+
+ #setGroundUnitType(label: string)
+ {
+ if (this.#spawnOptions != null)
+ {
+ this.#resetGroundUnitType();
+ var type = groundUnitsDatabase.getNameByLabel(label);
+ if (type != null)
+ {
+ this.#spawnOptions.type = type;
+ (this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
+ }
+ }
+ }
+
+ #resetGroundUnitType() {
+ (this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
+ }
+}
\ No newline at end of file
diff --git a/client/src/controls/unitcontextmenu.ts b/client/src/controls/unitcontextmenu.ts
new file mode 100644
index 00000000..2579c3f0
--- /dev/null
+++ b/client/src/controls/unitcontextmenu.ts
@@ -0,0 +1,18 @@
+import { ContextMenu } from "./contextmenu";
+
+export class UnitContextMenu extends ContextMenu {
+ constructor(id: string) {
+ super(id);
+ }
+
+ setOptions(options: string[], callback: CallableFunction)
+ {
+ this.getContainer()?.replaceChildren(...options.map((option: string) =>
+ {
+ var button = document.createElement("button");
+ button.innerText = option;
+ button.addEventListener("click", () => callback(option));
+ return (button);
+ }));
+ }
+}
\ No newline at end of file
diff --git a/client/src/index.ts b/client/src/index.ts
index 46893fe3..c0084e7f 100644
--- a/client/src/index.ts
+++ b/client/src/index.ts
@@ -1,7 +1,6 @@
import { Map } from "./map/map"
import { UnitsManager } from "./units/unitsmanager";
import { UnitInfoPanel } from "./panels/unitinfopanel";
-import { ContextMenu } from "./controls/contextmenu";
import { ConnectionStatusPanel } from "./panels/connectionstatuspanel";
import { MissionHandler } from "./missionhandler/missionhandler";
import { UnitControlPanel } from "./panels/unitcontrolpanel";
@@ -13,7 +12,6 @@ import { LogPanel } from "./panels/logpanel";
import { getAirbases, getBulllseye as getBulllseyes, getUnits, toggleDemoEnabled } from "./server/server";
var map: Map;
-var contextMenu: ContextMenu;
var unitsManager: UnitsManager;
var missionHandler: MissionHandler;
@@ -43,9 +41,6 @@ function setup() {
unitsManager = new UnitsManager();
missionHandler = new MissionHandler();
- /* Context menus */
- contextMenu = new ContextMenu("contextmenu");
-
/* Panels */
unitInfoPanel = new UnitInfoPanel("unit-info-panel");
unitControlPanel = new UnitControlPanel("unit-control-panel");
@@ -204,10 +199,6 @@ export function getMissionData() {
return missionHandler;
}
-export function getContextMenu() {
- return contextMenu;
-}
-
export function getUnitsManager() {
return unitsManager;
}
diff --git a/client/src/map/map.ts b/client/src/map/map.ts
index c7199ef7..0ebd8526 100644
--- a/client/src/map/map.ts
+++ b/client/src/map/map.ts
@@ -1,24 +1,15 @@
import * as L from "leaflet"
-import { getContextMenu, getUnitsManager } from "..";
+import { getUnitsManager } from "..";
import { BoxSelect } from "./boxselect";
-import { SpawnOptions } from "../controls/contextmenu";
+import { MapContextMenu, SpawnOptions } from "../controls/mapcontextmenu";
+import { UnitContextMenu } from "../controls/unitcontextmenu";
+import { AirbaseContextMenu } from "../controls/airbasecontextmenu";
export const IDLE = "IDLE";
export const MOVE_UNIT = "MOVE_UNIT";
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
-export interface ClickEvent {
- x: number;
- y: number;
- latlng: L.LatLng;
-}
-
-export interface SpawnEvent extends ClickEvent {
- airbaseName: string | null;
- coalitionID: number | null;
-}
-
export class Map extends L.Map {
#state: string;
#layer: L.TileLayer | null = null;
@@ -26,6 +17,10 @@ export class Map extends L.Map {
#leftClickTimer: number = 0;
#lastMousePosition: L.Point = new L.Point(0, 0);
+ #mapContextMenu: MapContextMenu = new MapContextMenu("map-contextmenu");
+ #unitContextMenu: UnitContextMenu = new UnitContextMenu("unit-contextmenu");
+ #airbaseContextMenu: AirbaseContextMenu = new AirbaseContextMenu("airbase-contextmenu");
+
constructor(ID: string) {
/* Init the leaflet map */
//@ts-ignore
@@ -36,7 +31,6 @@ export class Map extends L.Map {
/* Init the state machine */
this.#state = IDLE;
-
/* Register event handles */
this.on("click", (e: any) => this.#onClick(e));
@@ -112,19 +106,62 @@ export class Map extends L.Map {
return this.#state;
}
- /* Context Menu */
- showContextMenu(e: any, spawnOptions: SpawnOptions | null = null) {
+ /* Context Menus */
+ hideAllContextMenus()
+ {
+ this.hideMapContextMenu();
+ this.hideUnitContextMenu();
+ this.hideAirbaseContextMenu();
+ }
+
+ showMapContextMenu(e: any) {
+ this.hideAllContextMenus();
var x = e.originalEvent.x;
var y = e.originalEvent.y;
- getContextMenu()?.show(x, y, e.latlng);
+ this.#mapContextMenu.show(x, y, e.latlng);
document.dispatchEvent(new CustomEvent("mapContextMenu"));
}
- hideContextMenu() {
- getContextMenu()?.hide();
+ hideMapContextMenu() {
+ this.#mapContextMenu.hide();
document.dispatchEvent(new CustomEvent("mapContextMenu"));
}
+ getMapContextMenu(){
+ return this.#mapContextMenu;
+ }
+
+ showUnitContextMenu(e: any) {
+ this.hideAllContextMenus();
+ var x = e.originalEvent.x;
+ var y = e.originalEvent.y;
+ this.#unitContextMenu.show(x, y, e.latlng);
+ }
+
+ getUnitContextMenu(){
+ return this.#unitContextMenu;
+ }
+
+ hideUnitContextMenu() {
+ this.#unitContextMenu.hide();
+ }
+
+ showAirbaseContextMenu(e: any) {
+ this.hideAllContextMenus();
+ var x = e.originalEvent.x;
+ var y = e.originalEvent.y;
+ this.#airbaseContextMenu.show(x, y, e.latlng);
+ }
+
+ getAirbaseContextMenu(){
+ return this.#airbaseContextMenu;
+ }
+
+ hideAirbaseContextMenu() {
+ this.#airbaseContextMenu.hide();
+ }
+
+ /* Mouse coordinates */
getMousePosition() {
return this.#lastMousePosition;
}
@@ -134,7 +171,7 @@ export class Map extends L.Map {
}
/* Spawn from air base */
- spawnFromAirbase(e: SpawnEvent)
+ spawnFromAirbase(e: any)
{
//this.#aircraftSpawnMenu(e);
}
@@ -142,14 +179,13 @@ export class Map extends L.Map {
/* Event handlers */
#onClick(e: any) {
if (!this.#preventLeftClick) {
- this.hideContextMenu();
+ this.hideAllContextMenus();
if (this.#state === IDLE) {
}
else if (this.#state === MOVE_UNIT) {
this.setState(IDLE);
getUnitsManager().deselectAllUnits();
- this.hideContextMenu();
}
}
}
@@ -159,10 +195,10 @@ export class Map extends L.Map {
}
#onContextMenu(e: any) {
- this.hideContextMenu();
+ this.hideMapContextMenu();
if (this.#state === IDLE) {
if (this.#state == IDLE) {
- this.showContextMenu(e);
+ this.showMapContextMenu(e);
}
}
else if (this.#state === MOVE_UNIT) {
diff --git a/client/src/missionhandler/airbase.ts b/client/src/missionhandler/airbase.ts
index 593312df..6bbd88dd 100644
--- a/client/src/missionhandler/airbase.ts
+++ b/client/src/missionhandler/airbase.ts
@@ -10,41 +10,28 @@ export interface AirbaseOptions
export class Airbase extends L.Marker
{
#name: string = "";
- #coalitionID: number = -1;
+ #coalition: string = "";
constructor(options: AirbaseOptions)
{
super(options.position, { riseOnHover: true });
this.#name = options.name;
-
var icon = new L.DivIcon({
- html: `
-
-
-
- ${options.name}
- |
-
-
`,
- className: 'airbase-marker'}); // Set the marker, className must be set to avoid white square
+ html: ` `,
+ className: 'leaflet-airbase-marker',
+ iconSize: [63, 63]
+ }); // Set the marker, className must be set to avoid white square
this.setIcon(icon);
+
}
- setCoalitionID(coalitionID: number)
+ setCoalition(coalition: string)
{
- this.#coalitionID = coalitionID;
- var element = this.getElement();
- if (element != null)
- {
- var img = element.querySelector("#icon");
- if (img != null)
- {
- img.classList.toggle("blue", this.#coalitionID == 2);
- img.classList.toggle("red", this.#coalitionID == 1);
- img.classList.toggle("neutral", this.#coalitionID == 0);
- }
- }
+ this.#coalition = coalition;
+ this.getElement()?.setAttribute("data-coalition", this.#coalition);
}
getName()
@@ -52,8 +39,8 @@ export class Airbase extends L.Marker
return this.#name;
}
- getCoalitionID()
+ getCoalition()
{
- return this.#coalitionID;
+ return this.#coalition;
}
}
diff --git a/client/src/missionhandler/missionhandler.ts b/client/src/missionhandler/missionhandler.ts
index 193b69a9..a655b4ce 100644
--- a/client/src/missionhandler/missionhandler.ts
+++ b/client/src/missionhandler/missionhandler.ts
@@ -1,6 +1,5 @@
import { Marker, LatLng, Icon } from "leaflet";
import { getMap, getUnitsManager } from "..";
-import { SpawnEvent } from "../map/map";
import { Airbase } from "./airbase";
var bullseyeIcons = [
@@ -71,7 +70,8 @@ export class MissionHandler
}
else
{
- this.#airbasesMarkers[idx].setCoalitionID(airbase.coalition);
+ this.#airbasesMarkers[idx].setLatLng(new LatLng(airbase.lat, airbase.lng));
+ this.#airbasesMarkers[idx].setCoalition(airbase.coalition);
}
}
}
@@ -84,20 +84,21 @@ export class MissionHandler
else
options = ["Spawn unit"];
+ getMap().showAirbaseContextMenu(e);
//getMap().showContextMenu(e.originalEvent, e.sourceTarget.getName(),
// options.map((option) => {return {tooltip: option, src: "", callback: (label: string) => {this.#onAirbaseOptionSelection(e, label)}}}, false)
//)
}
#onAirbaseOptionSelection(e: any, option: string) {
- if (option === "Spawn unit") {
- var spawnEvent: SpawnEvent = {x: e.originalEvent.x, y: e.originalEvent.y, latlng: e.latlng, airbaseName: e.sourceTarget.getName(), coalitionID: e.sourceTarget.getCoalitionID()};
- getMap().spawnFromAirbase(spawnEvent);
- }
- else if (option === "Land here")
- {
- getMap().hideContextMenu();
- getUnitsManager().selectedUnitsLandAt(e.latlng);
- }
+ //if (option === "Spawn unit") {
+ // var spawnEvent: SpawnEvent = {x: e.originalEvent.x, y: e.originalEvent.y, latlng: e.latlng, airbaseName: e.sourceTarget.getName(), coalitionID: e.sourceTarget.getCoalitionID()};
+ // getMap().spawnFromAirbase(spawnEvent);
+ //}
+ //else if (option === "Land here")
+ //{
+ // getMap().hideContextMenu();
+ // getUnitsManager().selectedUnitsLandAt(e.latlng);
+ //}
}
}
\ No newline at end of file
diff --git a/client/src/server/server.ts b/client/src/server/server.ts
index 2d7f83b0..e591ea72 100644
--- a/client/src/server/server.ts
+++ b/client/src/server/server.ts
@@ -1,6 +1,6 @@
import * as L from 'leaflet'
import { setConnected } from '..';
-import { SpawnOptions } from '../controls/contextmenu';
+import { SpawnOptions } from '../controls/mapcontextmenu';
/* Edit here to change server address */
const REST_ADDRESS = "http://localhost:30000/olympus";
diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts
index bfe59e14..7ec28d2e 100644
--- a/client/src/units/unit.ts
+++ b/client/src/units/unit.ts
@@ -91,7 +91,7 @@ export class Unit extends Marker {
var icon = new DivIcon({
html: html,
- className: 'ol-unit-marker',
+ className: 'leaflet-unit-marker',
iconAnchor: [0, 0]
});
this.setIcon(icon);
@@ -338,14 +338,16 @@ export class Unit extends Marker {
#onContextMenu(e: any) {
var options = [
- 'Attack',
- 'Follow'
+ 'Attack'
]
- //getMap().showContextMenu(e.originalEvent, "Action: " + this.getData().unitName, options.map((option: string) => {return {tooltip: option, src: "", callback: (action: string) => this.#executeAction(action)}}));
+ getMap().showUnitContextMenu(e);
+ getMap().getUnitContextMenu().setOptions(options, (option: string) => {
+ getMap().hideUnitContextMenu();
+ this.#executeAction(option);
+ });
}
#executeAction(action: string) {
- getMap().hideContextMenu();
if (action === "Attack")
getUnitsManager().selectedUnitsAttackUnit(this.ID);
}
diff --git a/client/views/contextmenu.ejs b/client/views/contextmenus.ejs
similarity index 95%
rename from client/views/contextmenu.ejs
rename to client/views/contextmenus.ejs
index 09115f68..08ce62aa 100644
--- a/client/views/contextmenu.ejs
+++ b/client/views/contextmenus.ejs
@@ -1,4 +1,4 @@
-