From ee08d9d48e03edff8f7091e6e35b8a8065ad84d0 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Fri, 28 Apr 2023 17:32:28 +0200 Subject: [PATCH 01/20] Started work on better follow functionality And other minor fixes --- client/demo.js | 7 +- client/public/images/airbase.png | Bin 8704 -> 0 bytes client/public/stylesheets/units.css | 12 +- .../olympus/images/icon_airbase_blue.svg | 91 +- .../olympus/images/icon_airbase_neutral.svg | 91 +- .../olympus/images/icon_airbase_red.svg | 91 +- .../themes/olympus/images/task_tanker.svg | 1256 +++++++++++++++++ client/src/units/unit.ts | 105 +- client/src/units/unitsmanager.ts | 26 +- 9 files changed, 1576 insertions(+), 103 deletions(-) delete mode 100644 client/public/images/airbase.png create mode 100644 client/public/themes/olympus/images/task_tanker.svg diff --git a/client/demo.js b/client/demo.js index c30f7f7b..2c113e9f 100644 --- a/client/demo.js +++ b/client/demo.js @@ -2,7 +2,7 @@ const DEMO_UNIT_DATA = { ["1"]:{ baseData: { - AI: true, + AI: false, name: "KC-135", unitName: "Olympus 1-1", groupName: "Group 1", @@ -18,7 +18,7 @@ const DEMO_UNIT_DATA = { }, missionData: { fuel: 50, - flags: {human: true}, + flags: {Human: false}, ammo: [ { count: 4, @@ -47,6 +47,7 @@ const DEMO_UNIT_DATA = { }, taskData: { currentTask: "Holding", + currentState: "Idle", activePath: undefined, targetSpeed: 400, targetAltitude: 3000, @@ -67,7 +68,7 @@ const DEMO_UNIT_DATA = { }, ["2"]:{ baseData: { - AI: false, + AI: true, name: "KC-135", unitName: "Olympus 1-2", groupName: "Group 1", diff --git a/client/public/images/airbase.png b/client/public/images/airbase.png deleted file mode 100644 index 874174d63b1993217ac45d2b6171e60966dc61b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8704 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 diff --git a/client/public/stylesheets/units.css b/client/public/stylesheets/units.css index 0685381f..ddd1bd77 100644 --- a/client/public/stylesheets/units.css +++ b/client/public/stylesheets/units.css @@ -392,8 +392,8 @@ padding: var( --unit-aircraft-ammo-radius ); } - [data-object|="unit"] .unit-summary { + pointer-events: none; column-gap: 6px; color:white; display:flex; @@ -438,14 +438,14 @@ -[data-object|="unit"][data-pilot|="ai"]:hover .unit-ammo, -[data-object|="unit"][data-pilot|="ai"]:hover .unit-fuel { +[data-object|="unit"]:hover .unit-ammo, +[data-object|="unit"]:hover .unit-fuel { display:flex; } [data-object|="unit"][data-is-in-hotgroup] .unit-hotgroup, -[data-object|="unit"][data-pilot|="ai"][data-is-selected] .unit-ammo, -[data-object|="unit"][data-pilot|="ai"][data-is-selected] .unit-fuel, +[data-object|="unit"][data-is-selected] .unit-ammo, +[data-object|="unit"][data-is-selected] .unit-fuel, [data-object|="unit"][data-is-selected] .unit-selected-spotlight { display:flex; } @@ -501,7 +501,7 @@ } } -[data-object|="unit"][data-pilot|="ai"][data-has-low-fuel] .unit-fuel { +[data-object|="unit"][data-has-low-fuel] .unit-fuel { animation: pulse 1.5s linear infinite; } diff --git a/client/public/themes/olympus/images/icon_airbase_blue.svg b/client/public/themes/olympus/images/icon_airbase_blue.svg index 0800974c..d1fcf84c 100644 --- a/client/public/themes/olympus/images/icon_airbase_blue.svg +++ b/client/public/themes/olympus/images/icon_airbase_blue.svg @@ -1,10 +1,83 @@ - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + diff --git a/client/public/themes/olympus/images/icon_airbase_neutral.svg b/client/public/themes/olympus/images/icon_airbase_neutral.svg index 69713bd5..43222171 100644 --- a/client/public/themes/olympus/images/icon_airbase_neutral.svg +++ b/client/public/themes/olympus/images/icon_airbase_neutral.svg @@ -1,10 +1,83 @@ - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + diff --git a/client/public/themes/olympus/images/icon_airbase_red.svg b/client/public/themes/olympus/images/icon_airbase_red.svg index 45d55abd..d95872f1 100644 --- a/client/public/themes/olympus/images/icon_airbase_red.svg +++ b/client/public/themes/olympus/images/icon_airbase_red.svg @@ -1,10 +1,83 @@ - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + diff --git a/client/public/themes/olympus/images/task_tanker.svg b/client/public/themes/olympus/images/task_tanker.svg new file mode 100644 index 00000000..32ee5980 --- /dev/null +++ b/client/public/themes/olympus/images/task_tanker.svg @@ -0,0 +1,1256 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index b8ad84b2..fcb02761 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -71,6 +71,7 @@ export class Unit extends Marker { #selectable: boolean; #selected: boolean = false; + #hovered: boolean = false; #hidden: boolean = false; #preventClick: boolean = false; @@ -81,7 +82,6 @@ export class Unit extends Marker { #miniMapMarker: CircleMarker | null = null; #timer: number = 0; - #forceUpdate: boolean = false; static getConstructor(type: string) { if (type === "GroundUnit") return GroundUnit; @@ -102,6 +102,8 @@ export class Unit extends Marker { this.on('click', (e) => this.#onClick(e)); this.on('dblclick', (e) => this.#onDoubleClick(e)); this.on('contextmenu', (e) => this.#onContextMenu(e)); + this.on('mouseover', () => { this.#hovered = true;}) + this.on('mouseout', () => { this.#hovered = false;}) this.#pathPolyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1 }); this.#pathPolyline.addTo(getMap()); @@ -123,7 +125,8 @@ export class Unit extends Marker { var icon = new DivIcon({ html: this.getMarkerHTML(), className: 'leaflet-unit-marker', - iconAnchor: [0, 0] + iconAnchor: [25, 0], + iconSize: [50, 50], }); this.setIcon(icon); } @@ -143,13 +146,11 @@ export class Unit extends Marker { } setData(data: UpdateData) { - var updateMarker = false; - - if ((data.flightData.latitude != undefined && data.flightData.longitude != undefined && (this.getFlightData().latitude != data.flightData.latitude || this.getFlightData().longitude != data.flightData.longitude)) - || (data.flightData.heading != undefined && this.getFlightData().heading != data.flightData.heading) - || (data.baseData.alive != undefined && this.getBaseData().alive != data.baseData.alive) - || this.#forceUpdate || !getMap().hasLayer(this)) - updateMarker = true; + /* Check if data has changed comparing new values to old values */ + const positionChanged = (data.flightData.latitude != undefined && data.flightData.longitude != undefined && (this.getFlightData().latitude != data.flightData.latitude || this.getFlightData().longitude != data.flightData.longitude)); + const headingChanged = (data.flightData.heading != undefined && this.getFlightData().heading != data.flightData.heading); + const aliveChanged = (data.baseData.alive != undefined && this.getBaseData().alive != data.baseData.alive); + var updateMarker = (positionChanged || headingChanged || aliveChanged || !getMap().hasLayer(this)) if (data.baseData != undefined) { @@ -331,21 +332,17 @@ export class Unit extends Marker { } attackUnit(targetID: number) { + /* Units can't attack themselves */ if (this.ID != targetID) { attackUnit(this.ID, targetID); } - else { - // TODO: show a message - } } followUnit(targetID: number, offset: {"x": number, "y": number, "z": number}) { + /* Units can't follow themselves */ if (this.ID != targetID) { followUnit(this.ID, targetID, offset); } - else { - // TODO: show a message - } } landAt(latlng: LatLng) { @@ -399,7 +396,7 @@ export class Unit extends Marker { if (!e.originalEvent.ctrlKey) { getUnitsManager().deselectAllUnits(); } - this.setSelected(true); + this.setSelected(!this.getSelected()); } } this.#preventClick = false; @@ -416,12 +413,11 @@ export class Unit extends Marker { options["Center"] = `
Center map
`; - if (getUnitsManager().getSelectedUnits().length > 0 && !(getUnitsManager().getSelectedUnits().includes(this))) + if (getUnitsManager().getSelectedUnits().length > 0 && !(getUnitsManager().getSelectedUnits().length == 1 && (getUnitsManager().getSelectedUnits().includes(this)))) { - options = { - 'Attack': `
Attack
`, - 'Follow': `
Follow
`, - } + options['Attack'] = `
Attack
`; + if (getUnitsManager().getSelectedUnitsType() === "Aircraft") + options['Follow'] = `
Follow
`; } else if ((getUnitsManager().getSelectedUnits().length > 0 && (getUnitsManager().getSelectedUnits().includes(this))) || getUnitsManager().getSelectedUnits().length == 0) { @@ -487,30 +483,12 @@ export class Unit extends Marker { // Z: left-right, positive right var offset = {"x": 0, "y": 0, "z": 0}; - if (action == "Trail") - { - offset.x = -50; offset.y = -30; offset.z = 0; - } - else if (action == "Echelon (LH)") - { - offset.x = -50; offset.y = -10; offset.z = -50; - } - else if (action == "Echelon (RH)") - { - offset.x = -50; offset.y = -10; offset.z = 50; - } - else if (action == "Line abreast (RH)") - { - offset.x = 0; offset.y = 0; offset.z = 50; - } - else if (action == "Line abreast (LH)") - { - offset.x = 0; offset.y = 0; offset.z = -50; - } - else if (action == "Front") - { - offset.x = 100; offset.y = 0; offset.z = 0; - } + if (action == "Trail") { offset.x = -50; offset.y = -30; offset.z = 0; } + else if (action == "Echelon (LH)") { offset.x = -50; offset.y = -10; offset.z = -50; } + else if (action == "Echelon (RH)") { offset.x = -50; offset.y = -10; offset.z = 50; } + else if (action == "Line abreast (RH)") { offset.x = 0; offset.y = 0; offset.z = 50; } + else if (action == "Line abreast (LH)") { offset.x = 0; offset.y = 0; offset.z = -50; } + else if (action == "Front") { offset.x = 100; offset.y = 0; offset.z = 0; } getUnitsManager().selectedUnitsFollowUnit(this.ID, offset); } } @@ -518,6 +496,7 @@ export class Unit extends Marker { #updateMarker() { this.updateVisibility(); + /* Draw the minimap marker */ if (this.getBaseData().alive ) { if (this.#miniMapMarker == null) @@ -544,47 +523,47 @@ export class Unit extends Marker { } } + /* Draw the marker */ if (!this.getHidden()) { this.setLatLng(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude)); + var element = this.getElement(); if (element != null) { + /* Draw the velocity vector */ element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.getFlightData().speed / 5}px;`); - element.querySelector(".unit")?.setAttribute("data-pilot", this.getMissionData().flags.human? "human": "ai"); + /* Set fuel data */ element.querySelector(".unit-fuel-level")?.setAttribute("style", `width: ${this.getMissionData().fuel}%`); element.querySelector(".unit")?.toggleAttribute("data-has-low-fuel", this.getMissionData().fuel < 20); + /* Set dead/alive flag */ element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.getBaseData().alive); - if (this.getMissionData().flags.Human) // Unit is human + /* Set current unit state */ + if (this.getMissionData().flags.Human) // Unit is human element.querySelector(".unit")?.setAttribute("data-state", "human"); - else if (!this.getBaseData().AI) // Unit is under DCS control (no Olympus) + else if (!this.getBaseData().AI) // Unit is under DCS control (not Olympus) element.querySelector(".unit")?.setAttribute("data-state", "dcs"); - else // Unit is under Olympus control + else // Unit is under Olympus control element.querySelector(".unit")?.setAttribute("data-state", this.getTaskData().currentState.toLowerCase()); - var unitAltitudeDiv = element.querySelector(".unit-altitude"); - if (unitAltitudeDiv != null) - unitAltitudeDiv.innerHTML = "FL" + String(Math.floor(this.getFlightData().altitude / 0.3048 / 1000)); - - var unitSpeedDiv = element.querySelector(".unit-speed"); - if (unitSpeedDiv != null) - unitSpeedDiv.innerHTML = String(Math.floor(this.getFlightData().speed * 1.94384 ) ); + /* Set altitude and speed */ + if (element.querySelector(".unit-altitude")) + ( element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(this.getFlightData().altitude / 0.3048 / 1000)); + if (element.querySelector(".unit-speed")) + ( element.querySelector(".unit-speed")).innerHTML = String(Math.floor(this.getFlightData().speed * 1.94384 ) ); + /* Rotate elements according to heading */ element.querySelectorAll( "[data-rotate-to-heading]" ).forEach( el => { const headingDeg = rad2deg( this.getFlightData().heading ); let currentStyle = el.getAttribute( "style" ) || ""; el.setAttribute( "style", currentStyle + `transform:rotate(${headingDeg}deg);` ); }); - - - } + /* Set vertical offset for altitude stacking */ var pos = getMap().latLngToLayerPoint(this.getLatLng()).round(); - this.setZIndexOffset(1000 + Math.floor(this.getFlightData().altitude) - pos.y); + this.setZIndexOffset(1000 + Math.floor(this.getFlightData().altitude) - pos.y + (this.#hovered || this.#selected? 5000: 0)); } - - this.#forceUpdate = false; } #drawPath() { @@ -643,7 +622,7 @@ export class Unit extends Marker { color = "#00FF00"; else color = "#FFFFFF"; - var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 1, smoothFactor: 1 }); + var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1 }); targetPolyline.addTo(getMap()); this.#targetsPolylines.push(targetPolyline) } diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index 7c6086d5..c4b67c54 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -191,8 +191,17 @@ export class UnitsManager { selectedUnitsAddDestination(latlng: L.LatLng) { var selectedUnits = this.getSelectedUnits(); for (let idx in selectedUnits) { - var commandedUnit = selectedUnits[idx]; - commandedUnit.addDestination(latlng); + const unit = selectedUnits[idx]; + if (unit.getTaskData().currentState === "Follow") + { + const leader = this.getUnitByID(unit.getFormationData().leaderID) + if (leader && leader.getSelected()) + leader.addDestination(latlng); + else + unit.addDestination(latlng); + } + else + unit.addDestination(latlng); } this.#showActionMessage(selectedUnits, " new destination added"); } @@ -200,8 +209,17 @@ export class UnitsManager { selectedUnitsClearDestinations() { var selectedUnits = this.getSelectedUnits(); for (let idx in selectedUnits) { - var commandedUnit = selectedUnits[idx]; - commandedUnit.clearDestinations(); + const unit = selectedUnits[idx]; + if (unit.getTaskData().currentState === "Follow") + { + const leader = this.getUnitByID(unit.getFormationData().leaderID) + if (leader && leader.getSelected()) + leader.clearDestinations(); + else + unit.clearDestinations(); + } + else + unit.clearDestinations(); } } From 640f764354b2dcea3e4f662fcc48504ce68293af Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Mon, 1 May 2023 10:37:40 +0200 Subject: [PATCH 02/20] Implemented as described in issue --- client/src/@types/unit.d.ts | 4 -- client/src/units/helicopterdatabase.ts | 51 ---------------- client/src/units/unit.ts | 22 +------ client/src/units/unitsmanager.ts | 30 +-------- src/core/include/airunit.h | 1 - src/core/include/unit.h | 20 ++---- src/core/src/airunit.cpp | 33 +++------- src/core/src/scheduler.cpp | 48 +++------------ src/core/src/unit.cpp | 84 +++++++++++--------------- 9 files changed, 59 insertions(+), 234 deletions(-) diff --git a/client/src/@types/unit.d.ts b/client/src/@types/unit.d.ts index 9e621fde..6a114825 100644 --- a/client/src/@types/unit.d.ts +++ b/client/src/@types/unit.d.ts @@ -29,11 +29,7 @@ interface MissionData { } interface FormationData { - formation: string; - isLeader: boolean; - isWingman: boolean; leaderID: number; - wingmenIDs: number[]; } interface TaskData { diff --git a/client/src/units/helicopterdatabase.ts b/client/src/units/helicopterdatabase.ts index 32312440..d7fc9996 100644 --- a/client/src/units/helicopterdatabase.ts +++ b/client/src/units/helicopterdatabase.ts @@ -381,57 +381,6 @@ export class HelicopterDatabase extends UnitDatabase { ], "filename": "ah-1.png" }, - "AH-1W": { - "name": "AH-1W", - "label": "AH-1W Cobra", - "shortLabel": "AH1", - "loadouts": [ - { - "fuel": 1, - "items": [ - { - "name": "BGM-71 TOW", - "quantity": 8 - }, - { - "name": "Hydra-70 WP", - "quantity": 38 - } - ], - "roles": [ - "CAS" - ], - "code": "8xBGM-71, 38xHYDRA-70 WP", - "name": "TOW / Hydra" - }, - { - "fuel": 1, - "items": [ - { - "name": "Hydra-70", - "quantity": 76 - } - ], - "roles": [ - "CAS" - ], - "code": "76xHYDRA-70", - "name": "Hydra" - }, - { - "fuel": 1, - "items": [ - - ], - "roles": [ - "" - ], - "code": "", - "name": "Empty Loadout" - } - ], - "filename": "ah-1.png" - }, "Mi-26": { "name": "Mi-26", "label": "Mi-26 Halo", diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index fcb02761..07c0c0e6 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -40,11 +40,7 @@ export class Unit extends Marker { coalition: "", }, formationData: { - formation: "", - isLeader: false, - isWingman: false, - leaderID: 0, - wingmenIDs: [], + leaderID: 0 }, taskData: { currentState: "IDLE", @@ -315,22 +311,6 @@ export class Unit extends Marker { return getUnitsManager().getUnitByID(this.getFormationData().leaderID); } - getFormation() { - return [this].concat(this.getWingmen()) - } - - getWingmen() { - var wingmen: Unit[] = []; - if (this.getFormationData().wingmenIDs != undefined) { - for (let ID of this.getFormationData().wingmenIDs) { - var unit = getUnitsManager().getUnitByID(ID) - if (unit) - wingmen.push(unit); - } - } - return wingmen; - } - attackUnit(targetID: number) { /* Units can't attack themselves */ if (this.ID != targetID) { diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index c4b67c54..dc1e4069 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -119,35 +119,7 @@ export class UnitsManager { this.#units[ID].setSelected(false); } } - - getSelectedLeaders() { - var leaders: Unit[] = []; - for (let idx in this.getSelectedUnits()) - { - var unit = this.getSelectedUnits()[idx]; - if (unit.getFormationData().isLeader) - leaders.push(unit); - else if (unit.getFormationData().isWingman) - { - var leader = unit.getLeader(); - if (leader && !leaders.includes(leader)) - leaders.push(leader); - } - } - return leaders; - } - - getSelectedSingletons() { - var singletons: Unit[] = []; - for (let idx in this.getSelectedUnits()) - { - var unit = this.getSelectedUnits()[idx]; - if (!unit.getFormationData().isLeader && !unit.getFormationData().isWingman) - singletons.push(unit); - } - return singletons; - } - + getSelectedUnitsType () { if (this.getSelectedUnits().length == 0) return undefined; diff --git a/src/core/include/airunit.h b/src/core/include/airunit.h index a2aeb7a6..a46e9909 100644 --- a/src/core/include/airunit.h +++ b/src/core/include/airunit.h @@ -23,7 +23,6 @@ protected: virtual void setState(int newState); bool isDestinationReached(); bool setActiveDestination(); - void createHoldingPattern(); bool updateActivePath(bool looping); void goToDestination(wstring enrouteTask = L"nil"); }; \ No newline at end of file diff --git a/src/core/include/unit.h b/src/core/include/unit.h index 441c3e75..b9fde024 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -79,17 +79,9 @@ public: json::value getFlags() { return flags; } /********** Formation data **********/ - void setIsLeader(bool newIsLeader); - void setIsWingman(bool newIsWingman); - void setLeader(Unit* newLeader); - void setWingmen(vector newWingmen); - void setFormation(wstring newFormation) { formation = newFormation; addMeasure(L"formation", json::value(formation));} + void setLeaderID(int newLeaderID) { leaderID = newLeaderID; addMeasure(L"leaderID", json::value(newLeaderID)); } void setFormationOffset(Offset formationOffset); - bool getIsLeader() { return isLeader; } - bool getIsWingman() { return isWingman; } - Unit* getLeader() { return leader; } - vector getWingmen() { return wingmen; } - wstring getFormation() { return formation; } + int getLeaderID() { return leaderID; } Offset getFormationoffset() { return formationOffset; } /********** Task data **********/ @@ -177,11 +169,7 @@ protected: json::value flags = json::value::null(); /********** Formation data **********/ - bool isLeader = false; - bool isWingman = false; - wstring formation = L""; - Unit *leader = nullptr; - vector wingmen; + int leaderID = NULL; Offset formationOffset = Offset(NULL); /********** Task data **********/ @@ -215,7 +203,9 @@ protected: /********** Functions **********/ virtual wstring getCategory() { return L"No category"; }; wstring getTargetName(); + wstring getLeaderName(); bool isTargetAlive(); + bool isLeaderAlive(); virtual void AIloop() = 0; void addMeasure(wstring key, json::value value); }; diff --git a/src/core/src/airunit.cpp b/src/core/src/airunit.cpp index cf3d2369..1faf5118 100644 --- a/src/core/src/airunit.cpp +++ b/src/core/src/airunit.cpp @@ -22,7 +22,7 @@ void AirUnit::setState(int newState) { if (state != newState) { - /* Perform any action required when LEAVING a certain state */ + /************ Perform any action required when LEAVING a certain state ************/ switch (state) { case State::IDLE: { break; @@ -35,6 +35,7 @@ void AirUnit::setState(int newState) break; } case State::FOLLOW: { + setLeaderID(NULL); break; } case State::LAND: { @@ -47,7 +48,7 @@ void AirUnit::setState(int newState) break; } - /* Perform any action required when ENTERING a certain state */ + /************ Perform any action required when ENTERING a certain state ************/ switch (newState) { case State::IDLE: { clearActivePath(); @@ -135,23 +136,6 @@ bool AirUnit::setActiveDestination() } } -void AirUnit::createHoldingPattern() -{ - /* Air units must ALWAYS have a destination or they will RTB and become uncontrollable */ - clearActivePath(); - Coords point1; - Coords point2; - Coords point3; - Geodesic::WGS84().Direct(latitude, longitude, 45, 10000, point1.lat, point1.lng); - Geodesic::WGS84().Direct(point1.lat, point1.lng, 135, 10000, point2.lat, point2.lng); - Geodesic::WGS84().Direct(point2.lat, point2.lng, 225, 10000, point3.lat, point3.lng); - pushActivePathBack(point1); - pushActivePathBack(point2); - pushActivePathBack(point3); - pushActivePathBack(Coords(latitude, longitude)); - log(unitName + L" holding pattern created"); -} - bool AirUnit::updateActivePath(bool looping) { if (activePath.size() > 0) @@ -282,22 +266,22 @@ void AirUnit::AIloop() clearActivePath(); activeDestination = Coords(NULL); - /* If the target is not alive (either not set or was destroyed) go back to IDLE */ - if (!isTargetAlive()) { + /* If the leader is not alive (either not set or was destroyed) go back to IDLE */ + if (!isLeaderAlive()) { setState(State::IDLE); break; } currentTask = L"Following " + getTargetName(); - Unit* target = unitsManager->getUnit(targetID); + Unit* leader = unitsManager->getUnit(leaderID); if (!hasTask) { - if (target != nullptr && target->getAlive() && formationOffset != NULL) + if (leader != nullptr && leader->getAlive() && formationOffset != NULL) { std::wostringstream taskSS; taskSS << "{" << "id = 'FollowUnit'" << ", " - << "leaderID = " << target->getID() << "," + << "leaderID = " << leader->getID() << "," << "offset = {" << "x = " << formationOffset.x << "," << "y = " << formationOffset.y << "," @@ -332,5 +316,6 @@ void AirUnit::AIloop() default: break; } + addMeasure(L"currentTask", json::value(currentTask)); } diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp index 318945f4..2bd4c98c 100644 --- a/src/core/src/scheduler.cpp +++ b/src/core/src/scheduler.cpp @@ -145,30 +145,30 @@ void Scheduler::handleRequest(wstring key, json::value value) else if (key.compare(L"followUnit") == 0) { int ID = value[L"ID"].as_integer(); - int targetID = value[L"targetID"].as_integer(); + int leaderID = value[L"targetID"].as_integer(); int offsetX = value[L"offsetX"].as_integer(); int offsetY = value[L"offsetY"].as_integer(); int offsetZ = value[L"offsetZ"].as_integer(); Unit* unit = unitsManager->getUnit(ID); - Unit* target = unitsManager->getUnit(targetID); + Unit* leader = unitsManager->getUnit(leaderID); wstring unitName; - wstring targetName; + wstring leaderName; if (unit != nullptr) unitName = unit->getUnitName(); else return; - if (target != nullptr) - targetName = target->getUnitName(); + if (leader != nullptr) + leaderName = leader->getUnitName(); else return; - log(L"Unit " + unitName + L" following unit " + targetName); + log(L"Unit " + unitName + L" following unit " + leaderName); unit->setFormationOffset(Offset(offsetX, offsetY, offsetZ)); - unit->setTargetID(targetID); + unit->setLeaderID(leaderID); unit->setState(State::FOLLOW); } else if (key.compare(L"changeSpeed") == 0) @@ -208,40 +208,6 @@ void Scheduler::handleRequest(wstring key, json::value value) command = dynamic_cast(new Clone(ID, loc)); log(L"Cloning unit " + to_wstring(ID)); } - else if (key.compare(L"setLeader") == 0) - { - int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); - bool isLeader = value[L"isLeader"].as_bool(); - if (isLeader) - { - json::value wingmenIDs = value[L"wingmenIDs"]; - vector wingmen; - if (unit != nullptr) - { - for (auto itr = wingmenIDs.as_array().begin(); itr != wingmenIDs.as_array().end(); itr++) - { - Unit* wingman = unitsManager->getUnit(itr->as_integer()); - if (wingman != nullptr) - wingmen.push_back(wingman); - } - unit->setFormation(L"Line abreast"); - unit->setIsLeader(true); - unit->setWingmen(wingmen); - log(L"Setting " + unit->getName() + L" as formation leader"); - } - } - else { - unit->setIsLeader(false); - } - } - else if (key.compare(L"setFormation") == 0) - { - int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); - wstring formation = value[L"formation"].as_string(); - unit->setFormation(formation); - } else if (key.compare(L"setROE") == 0) { int ID = value[L"ID"].as_integer(); diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index bf848269..f442e044 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -20,6 +20,14 @@ Unit::Unit(json::value json, int ID) : { log("Creating unit with ID: " + to_string(ID)); addMeasure(L"currentState", json::value(L"Idle")); + + addMeasure(L"TACANChannel", json::value(TACANChannel)); + addMeasure(L"TACANXY", json::value(TACANXY)); + addMeasure(L"TACANCallsign", json::value(TACANCallsign)); + + addMeasure(L"radioFrequency", json::value(radioFrequency)); + addMeasure(L"radioCallsign", json::value(radioCallsign)); + addMeasure(L"radioCallsignNumber", json::value(radioCallsignNumber)); } Unit::~Unit() @@ -127,10 +135,10 @@ json::value Unit::getData(long long time) /********** Formation data **********/ json[L"formationData"] = json::value::object(); - for (auto key : { L"isLeader", L"isWingman", L"formation", L"wingmenIDs", L"leaderID" }) + for (auto key : { L"leaderID" }) { if (measures.find(key) != measures.end() && measures[key]->getTime() > time) - json[L"missionData"][key] = measures[key]->getValue(); + json[L"formationData"][key] = measures[key]->getValue(); } /********** Task data **********/ @@ -154,12 +162,9 @@ json::value Unit::getData(long long time) void Unit::setActivePath(list newPath) { - if (state != State::WINGMAN && state != State::FOLLOW) - { - activePath = newPath; - resetActiveDestination(); - } - + activePath = newPath; + resetActiveDestination(); + auto path = json::value::object(); if (activePath.size() > 0) { int count = 1; @@ -223,22 +228,6 @@ int Unit::getCoalitionID() return 2; } -void Unit::setLeader(Unit* newLeader) -{ - leader = newLeader; - if (leader != nullptr) - addMeasure(L"leaderID", json::value(leader->getID())); -} - -void Unit::setWingmen(vector newWingmen) { - wingmen = newWingmen; - auto wingmenIDs = json::value::object(); - int i = 0; - for (auto itr = wingmen.begin(); itr != wingmen.end(); itr++) - wingmenIDs[i++] = (*itr)->getID(); - addMeasure(L"wingmen", wingmenIDs); -} - wstring Unit::getTargetName() { if (isTargetAlive()) @@ -262,6 +251,29 @@ bool Unit::isTargetAlive() return false; } +wstring Unit::getLeaderName() +{ + if (isLeaderAlive()) + { + Unit* leader = unitsManager->getUnit(leaderID); + if (leader != nullptr) + return leader->getUnitName(); + } + return L""; +} + +bool Unit::isLeaderAlive() +{ + if (leaderID == NULL) + return false; + + Unit* leader = unitsManager->getUnit(leaderID); + if (leader != nullptr) + return leader->alive; + else + return false; +} + void Unit::resetActiveDestination() { activeDestination = Coords(NULL); @@ -273,30 +285,6 @@ void Unit::resetTask() scheduler->appendCommand(command); } -void Unit::setIsLeader(bool newIsLeader) { - isLeader = newIsLeader; - if (!isLeader) { - for (auto wingman : wingmen) - { - wingman->setFormation(L""); - wingman->setIsWingman(false); - wingman->setLeader(nullptr); - } - } - addMeasure(L"isLeader", json::value(newIsLeader)); -} - -void Unit::setIsWingman(bool newIsWingman) -{ - isWingman = newIsWingman; - if (isWingman) - setState(State::WINGMAN); - else - setState(State::IDLE); - - addMeasure(L"isWingman", json::value(isWingman)); -} - void Unit::setFormationOffset(Offset newFormationOffset) { formationOffset = newFormationOffset; From a23bdd4b330acd745b87f9e4972ff5aec0cb8f4b Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Mon, 1 May 2023 21:51:46 +0200 Subject: [PATCH 03/20] Multiple map panning fixes and improvements --- client/debug.bat | 2 +- client/public/stylesheets/units.css | 372 +++++++++++++------------ client/src/index.ts | 34 ++- client/src/map/boxselect.ts | 2 +- client/src/map/map.ts | 206 ++++++++------ client/src/units/unit.ts | 4 +- client/views/connectionstatuspanel.ejs | 2 +- client/views/contextmenus.ejs | 6 +- client/views/dialogs.ejs | 6 +- client/views/mouseinfopanel.ejs | 2 +- client/views/navbar.ejs | 2 +- client/views/popups.ejs | 2 +- client/views/unitcontrolpanel.ejs | 2 +- client/views/unitdatatable.ejs | 2 +- client/views/unitinfopanel.ejs | 2 +- 15 files changed, 357 insertions(+), 289 deletions(-) diff --git a/client/debug.bat b/client/debug.bat index be565780..f57b8be0 100644 --- a/client/debug.bat +++ b/client/debug.bat @@ -1,2 +1,2 @@ start cmd /k "npm run start" -start cmd /k "watchify .\src\index.ts --debug -p [ tsify --noImplicitAny ] -o .\public\javascripts\bundle.js" +start cmd /k "watchify .\src\index.ts --debug -o .\public\javascripts\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ] diff --git a/client/public/stylesheets/units.css b/client/public/stylesheets/units.css index ddd1bd77..1822effd 100644 --- a/client/public/stylesheets/units.css +++ b/client/public/stylesheets/units.css @@ -1,46 +1,48 @@ :root { - /* Generic marker settings */ - --unit-centre-x: calc( var( --unit-width ) / 2 ); - --unit-centre-y: calc( var( --unit-height ) / 2 ); - - --unit-hotgroup-height: 10px; - --unit-hotgroup-width: var( --unit-hotgroup-height ); - - - /* Air units' marker settings */ - --unit-aircraft-label-x: calc( var( --unit-centre-x ) - ( var( --unit-aircraft-width ) / 2 ) + ( var( --unit-stroke-width ) / 2 ) ); - --unit-aircraft-label-y: calc( var( --unit-centre-y ) - ( var( --unit-aircraft-height ) / 2 ) + ( var( --unit-stroke-width ) / 2 ) ); + /* Generic marker settings */ + --unit-centre-x: calc(var(--unit-width) / 2); + --unit-centre-y: calc(var(--unit-height) / 2); + + --unit-hotgroup-height: 10px; + --unit-hotgroup-width: var(--unit-hotgroup-height); + + + /* Air units' marker settings */ + --unit-aircraft-label-x: calc(var(--unit-centre-x) - (var(--unit-aircraft-width) / 2) + (var(--unit-stroke-width) / 2)); + --unit-aircraft-label-y: calc(var(--unit-centre-y) - (var(--unit-aircraft-height) / 2) + (var(--unit-stroke-width) / 2)); } [data-object|="unit"] { align-items: center; - cursor:pointer; - display:flex; + cursor: pointer; + display: flex; justify-content: center; - position:relative; + position: relative; + height: 100%; + width: 100%; } [data-object|="unit"] .unit-selected-spotlight { - background-color: var( --unit-spotlight-fill ); + background-color: var(--unit-spotlight-fill); border-radius: 50%; - display:none; - padding: var( --unit-spotlight-radius ); + display: none; + padding: var(--unit-spotlight-radius); position: absolute; - z-index:1; + z-index: 1; } [data-object|="unit"] .unit-vvi { align-self: center; - background:var( --secondary-gunmetal-grey ); - display:flex; + background: var(--secondary-gunmetal-grey); + display: flex; justify-self: center; transform-origin: bottom; - translate:0 -50%; - padding-bottom: calc( ( var( --unit-aircraft-width ) / 2 ) + var( --unit-stroke-width ) ); - position:absolute; - width: var( --unit-aircraft-vvi-width ); + translate: 0 -50%; + padding-bottom: calc((var(--unit-aircraft-width) / 2) + var(--unit-stroke-width)); + position: absolute; + width: var(--unit-aircraft-vvi-width); z-index: 3; } @@ -48,32 +50,32 @@ [data-object|="unit"] .unit-hotgroup { align-content: center; background-color: black; - border-radius: var( --border-radius-xs ); - display:none; - height: var( --unit-hotgroup-height ); + border-radius: var(--border-radius-xs); + display: none; + height: var(--unit-hotgroup-height); justify-content: center; - position:absolute; - transform: rotate( -45deg ); - translate:0 -275%; - width: var( --unit-hotgroup-width ); + position: absolute; + transform: rotate(-45deg); + translate: 0 -275%; + width: var(--unit-hotgroup-width); z-index: 5; } [data-object|="unit"] .unit-hotgroup-id { background-color: transparent; - color:white; + color: white; font-size: 9px; font-weight: bolder; - transform:rotate( 45deg ); + transform: rotate(45deg); } [data-object|="unit"] .unit-marker-border { - border-radius: var( --border-radius-sm ); - display:none; - height: calc( var( --unit-aircraft-height ) + ( var( --unit-label-border-width ) * 2 ) ); - position:absolute; - width: calc( var( --unit-aircraft-width ) + ( var( --unit-label-border-width ) * 2 ) ); - z-index:2; + border-radius: var(--border-radius-sm); + display: none; + height: calc(var(--unit-aircraft-height) + (var(--unit-label-border-width) * 2)); + position: absolute; + width: calc(var(--unit-aircraft-width) + (var(--unit-label-border-width) * 2)); + z-index: 2; } @@ -85,9 +87,9 @@ background-color: transparent; background-repeat: no-repeat; background-size: cover; - position:absolute; + position: absolute; transform-origin: center; - z-index:3; + z-index: 3; } @@ -95,43 +97,43 @@ /* Air */ [data-object|="unit-aircraft"] .unit-marker { - background-image: var( --unit-aircraft-marker-neutral-url ); - height: var( --unit-aircraft-marker-height ); - width: var( --unit-aircraft-marker-width ); + background-image: var(--unit-aircraft-marker-neutral-url); + height: var(--unit-aircraft-marker-height); + width: var(--unit-aircraft-marker-width); } [data-object|="unit-aircraft"]:hover .unit-marker { - background-image: var( --unit-aircraft-marker-neutral-hover-url ); + background-image: var(--unit-aircraft-marker-neutral-hover-url); } [data-object|="unit-aircraft"][data-is-selected] .unit-marker { - background-image: var( --unit-aircraft-marker-neutral-selected-url ); + background-image: var(--unit-aircraft-marker-neutral-selected-url); } [data-object|="unit-aircraft"][data-coalition="blue"] .unit-marker { - background-image: var( --unit-aircraft-marker-blue-url ); + background-image: var(--unit-aircraft-marker-blue-url); } [data-object|="unit-aircraft"][data-coalition="blue"]:hover .unit-marker { - background-image: var( --unit-aircraft-marker-blue-hover-url ); + background-image: var(--unit-aircraft-marker-blue-hover-url); } [data-object|="unit-aircraft"][data-coalition="blue"][data-is-selected] .unit-marker { - background-image: var( --unit-aircraft-marker-blue-selected-url ); + background-image: var(--unit-aircraft-marker-blue-selected-url); } [data-object|="unit-aircraft"][data-coalition="red"] .unit-marker { - background-image: var( --unit-aircraft-marker-red-url ); + background-image: var(--unit-aircraft-marker-red-url); } [data-object|="unit-aircraft"][data-coalition="red"]:hover .unit-marker { - background-image: var( --unit-aircraft-marker-red-hover-url ); + background-image: var(--unit-aircraft-marker-red-hover-url); } [data-object|="unit-aircraft"][data-coalition="red"][data-is-selected] .unit-marker { - background-image: var( --unit-aircraft-marker-red-selected-url ); + background-image: var(--unit-aircraft-marker-red-selected-url); } @@ -140,194 +142,195 @@ /* Ground vehicles (not SAMs) */ [data-object|="unit-groundunit"] .unit-marker { - background-image: var( --unit-groundunit-marker-neutral-url ); - height: var( --unit-groundunit-marker-height ); - width: var( --unit-groundunit-marker-width ); + background-image: var(--unit-groundunit-marker-neutral-url); + height: var(--unit-groundunit-marker-height); + width: var(--unit-groundunit-marker-width); } [data-object|="unit-groundunit"]:hover .unit-marker { - background-image: var( --unit-groundunit-marker-neutral-hover-url ); + background-image: var(--unit-groundunit-marker-neutral-hover-url); } [data-object|="unit-groundunit"][data-is-selected] .unit-marker { - background-image: var( --unit-groundunit-marker-neutral-selected-url ); + background-image: var(--unit-groundunit-marker-neutral-selected-url); } [data-object|="unit-groundunit"][data-coalition="blue"] .unit-marker { - background-image: var( --unit-groundunit-marker-blue-url ); + background-image: var(--unit-groundunit-marker-blue-url); } [data-object|="unit-groundunit"][data-coalition="blue"]:hover .unit-marker { - background-image: var( --unit-groundunit-marker-blue-hover-url ); + background-image: var(--unit-groundunit-marker-blue-hover-url); } [data-object|="unit-groundunit"][data-coalition="blue"][data-is-selected] .unit-marker { - background-image: var( --unit-groundunit-marker-blue-selected-url ); + background-image: var(--unit-groundunit-marker-blue-selected-url); } [data-object|="unit-groundunit"][data-coalition="red"] .unit-marker { - background-image: var( --unit-groundunit-marker-red-url ); + background-image: var(--unit-groundunit-marker-red-url); } [data-object|="unit-groundunit"][data-coalition="red"]:hover .unit-marker { - background-image: var( --unit-groundunit-marker-red-hover-url ); + background-image: var(--unit-groundunit-marker-red-hover-url); } [data-object|="unit-groundunit"][data-coalition="red"][data-is-selected] .unit-marker { - background-image: var( --unit-groundunit-marker-red-selected-url ); + background-image: var(--unit-groundunit-marker-red-selected-url); } /* SAMs */ [data-object|="unit-sam"] .unit-selected-spotlight { - translate:0 2px; + translate: 0 2px; } [data-object|="unit-sam"] .unit-marker { - background-image: var( --unit-sam-marker-neutral-url ); - height: var( --unit-sam-marker-height ); - width: var( --unit-sam-marker-width ); + background-image: var(--unit-sam-marker-neutral-url); + height: var(--unit-sam-marker-height); + width: var(--unit-sam-marker-width); } [data-object|="unit-sam"]:hover .unit-marker { - background-image: var( --unit-sam-marker-neutral-hover-url ); + background-image: var(--unit-sam-marker-neutral-hover-url); } [data-object|="unit-sam"][data-is-selected] .unit-marker { - background-image: var( --unit-sam-marker-neutral-selected-url ); + background-image: var(--unit-sam-marker-neutral-selected-url); } [data-object|="unit-sam"][data-coalition="blue"] .unit-marker { - background-image: var( --unit-sam-marker-blue-url ); + background-image: var(--unit-sam-marker-blue-url); } [data-object|="unit-sam"][data-coalition="blue"]:hover .unit-marker { - background-image: var( --unit-sam-marker-blue-hover-url ); + background-image: var(--unit-sam-marker-blue-hover-url); } [data-object|="unit-sam"][data-coalition="blue"][data-is-selected] .unit-marker { - background-image: var( --unit-sam-marker-blue-selected-url ); + background-image: var(--unit-sam-marker-blue-selected-url); } [data-object|="unit-sam"][data-coalition="red"] .unit-marker { - background-image: var( --unit-sam-marker-red-url ); + background-image: var(--unit-sam-marker-red-url); } [data-object|="unit-sam"][data-coalition="red"]:hover .unit-marker { - background-image: var( --unit-sam-marker-red-hover-url ); + background-image: var(--unit-sam-marker-red-hover-url); } [data-object|="unit-sam"][data-coalition="red"][data-is-selected] .unit-marker { - background-image: var( --unit-sam-marker-red-selected-url ); + background-image: var(--unit-sam-marker-red-selected-url); } /* navyunit */ [data-object|="unit-navyunit"] .unit-selected-spotlight { - translate:0 -2px; + translate: 0 -2px; } [data-object|="unit-navyunit"] .unit-marker { - background-image: var( --unit-navyunit-marker-neutral-url ); - height: var( --unit-navyunit-marker-height ); - width: var( --unit-navyunit-marker-width ); + background-image: var(--unit-navyunit-marker-neutral-url); + height: var(--unit-navyunit-marker-height); + width: var(--unit-navyunit-marker-width); } [data-object|="unit-navyunit"]:hover .unit-marker { - background-image: var( --unit-navyunit-marker-neutral-hover-url ); + background-image: var(--unit-navyunit-marker-neutral-hover-url); } [data-object|="unit-navyunit"][data-is-selected] .unit-marker { - background-image: var( --unit-navyunit-marker-neutral-selected-url ); + background-image: var(--unit-navyunit-marker-neutral-selected-url); } [data-object|="unit-navyunit"][data-coalition="blue"] .unit-marker { - background-image: var( --unit-navyunit-marker-blue-url ); + background-image: var(--unit-navyunit-marker-blue-url); } [data-object|="unit-navyunit"][data-coalition="blue"]:hover .unit-marker { - background-image: var( --unit-navyunit-marker-blue-hover-url ); + background-image: var(--unit-navyunit-marker-blue-hover-url); } [data-object|="unit-navyunit"][data-coalition="blue"][data-is-selected] .unit-marker { - background-image: var( --unit-navyunit-marker-blue-selected-url ); + background-image: var(--unit-navyunit-marker-blue-selected-url); } [data-object|="unit-navyunit"][data-coalition="red"] .unit-marker { - background-image: var( --unit-navyunit-marker-red-url ); + background-image: var(--unit-navyunit-marker-red-url); } [data-object|="unit-navyunit"][data-coalition="red"]:hover .unit-marker { - background-image: var( --unit-navyunit-marker-red-hover-url ); + background-image: var(--unit-navyunit-marker-red-hover-url); } [data-object|="unit-navyunit"][data-coalition="red"][data-is-selected] .unit-marker { - background-image: var( --unit-navyunit-marker-red-selected-url ); + background-image: var(--unit-navyunit-marker-red-selected-url); } /* Building */ [data-object|="unit-building"] .unit-marker { - background-image: var( --unit-building-marker-neutral-url ); - height: var( --unit-building-marker-height ); - width: var( --unit-building-marker-width ); + background-image: var(--unit-building-marker-neutral-url); + height: var(--unit-building-marker-height); + width: var(--unit-building-marker-width); } [data-object|="unit-building"][data-coalition="blue"] .unit-marker { - background-image: var( --unit-building-marker-blue-url ); + background-image: var(--unit-building-marker-blue-url); } [data-object|="unit-building"][data-coalition="red"] .unit-marker { - background-image: var( --unit-building-marker-red-url ); + background-image: var(--unit-building-marker-red-url); } /* Weapons */ -[data-object|="unit-missile"], [data-object|="unit-bomb"] { +[data-object|="unit-missile"], +[data-object|="unit-bomb"] { cursor: default; } [data-object|="unit-missile"] .unit-marker { - background-image: var( --unit-missile-marker-neutral-url ); - height: var( --unit-missile-marker-height ); - width: var( --unit-missile-marker-width ); + background-image: var(--unit-missile-marker-neutral-url); + height: var(--unit-missile-marker-height); + width: var(--unit-missile-marker-width); } [data-object|="unit-missile"][data-coalition="blue"] .unit-marker { - background-image: var( --unit-missile-marker-blue-url ); + background-image: var(--unit-missile-marker-blue-url); } [data-object|="unit-missile"][data-coalition="red"] .unit-marker { - background-image: var( --unit-missile-marker-red-url ); + background-image: var(--unit-missile-marker-red-url); } [data-object|="unit-bomb"] .unit-marker { - background-image: var( --unit-bomb-marker-neutral-url ); - height: var( --unit-bomb-marker-height ); - width: var( --unit-bomb-marker-width ); + background-image: var(--unit-bomb-marker-neutral-url); + height: var(--unit-bomb-marker-height); + width: var(--unit-bomb-marker-width); } [data-object|="unit-bomb"][data-coalition="blue"] .unit-marker { - background-image: var( --unit-bomb-marker-blue-url ); + background-image: var(--unit-bomb-marker-blue-url); } [data-object|="unit-bomb"][data-coalition="red"] .unit-marker { - background-image: var( --unit-bomb-marker-red-url ); + background-image: var(--unit-bomb-marker-red-url); } @@ -336,12 +339,12 @@ ********************************************/ [data-object|="unit"] .unit-short-label { - color: var( --secondary-gunmetal-grey ); + color: var(--secondary-gunmetal-grey); font-size: var(--unit-font-size); font-weight: var(--unit-font-weight); line-height: normal; position: absolute; - z-index:10; + z-index: 10; } [data-object|="unit-groundunit"] .unit-short-label { @@ -349,81 +352,81 @@ } [data-object|="unit-sam"] .unit-short-label { - translate:0 50%; + translate: 0 50%; } [data-object|="unit-navyunit"] .unit-short-label { - translate:0 -50%; + translate: 0 -50%; } [data-object|="unit"] .unit-fuel { - background:white; - border: var( --unit-aircraft-fuel-border-width ) solid var( --secondary-dark-steel ); - border-radius: var( --border-radius-sm ); - display:none; - height: var( --unit-aircraft-fuel-height ); + background: white; + border: var(--unit-aircraft-fuel-border-width) solid var(--secondary-dark-steel); + border-radius: var(--border-radius-sm); + display: none; + height: var(--unit-aircraft-fuel-height); position: absolute; - translate:var( --unit-aircraft-fuel-x ) var( --unit-aircraft-fuel-y ); - width: var( --unit-aircraft-fuel-width ); + translate: var(--unit-aircraft-fuel-x) var(--unit-aircraft-fuel-y); + width: var(--unit-aircraft-fuel-width); z-index: 5; } [data-object|="unit"] .unit-fuel-level { - background-color: var( --secondary-light-grey ); - height:100%; - width:100%; + background-color: var(--secondary-light-grey); + height: 100%; + width: 100%; } [data-object|="unit"] .unit-ammo { - column-gap: var( --unit-aircraft-ammo-spacing ); - display:none; - height:fit-content; - position:absolute; - translate:var( --unit-aircraft-ammo-x ) var( --unit-aircraft-ammo-y ); - width:fit-content; + column-gap: var(--unit-aircraft-ammo-spacing); + display: none; + height: fit-content; + position: absolute; + translate: var(--unit-aircraft-ammo-x) var(--unit-aircraft-ammo-y); + width: fit-content; } -[data-object|="unit"] .unit-ammo > * { +[data-object|="unit"] .unit-ammo>* { background-color: white; - border: var( --unit-aircraft-ammo-border-width ) solid var( --secondary-dark-steel ); + border: var(--unit-aircraft-ammo-border-width) solid var(--secondary-dark-steel); border-radius: 50%; - padding: var( --unit-aircraft-ammo-radius ); + padding: var(--unit-aircraft-ammo-radius); } [data-object|="unit"] .unit-summary { pointer-events: none; column-gap: 6px; - color:white; - display:flex; + color: white; + display: flex; flex-wrap: wrap; - font-size:11px; + font-size: 11px; font-weight: bold; justify-content: right; line-height: 12px; - position:absolute; + position: absolute; row-gap: 1px; text-shadow: - -1px -1px 0 #000, + -1px -1px 0 #000, 1px -1px 0 #000, - -1px 1px 0 #000, - 1px 1px 0 #000; + -1px 1px 0 #000, + 1px 1px 0 #000; translate: -60px 0; - width:fit-content; - z-index:20; + width: fit-content; + z-index: 20; } [data-hide-labels] [data-object|="unit"] .unit-summary { - display:none; + display: none; } -[data-object|="unit"] .unit-summary > * { - padding:1px; +[data-object|="unit"] .unit-summary>* { + padding: 1px; } [data-object|="unit"] .unit-summary .unit-callsign { - color:white; + color: white; overflow: hidden; text-align: right; transform-origin: right; @@ -433,35 +436,35 @@ [data-object|="unit"] .unit-summary .unit-callsign:hover { direction: rtl; - overflow:visible; + overflow: visible; } [data-object|="unit"]:hover .unit-ammo, [data-object|="unit"]:hover .unit-fuel { - display:flex; + display: flex; } [data-object|="unit"][data-is-in-hotgroup] .unit-hotgroup, [data-object|="unit"][data-is-selected] .unit-ammo, [data-object|="unit"][data-is-selected] .unit-fuel, [data-object|="unit"][data-is-selected] .unit-selected-spotlight { - display:flex; + display: flex; } [data-object|="unit"][data-has-fox-1] .unit-ammo-fox-1, [data-object|="unit"][data-has-fox-2] .unit-ammo-fox-2, [data-object|="unit"][data-has-fox-3] .unit-ammo-fox-3, [data-object|="unit"][data-has-other-ammo] .unit-ammo-other { - background-color: var( --secondary-gunmetal-grey ); + background-color: var(--secondary-gunmetal-grey); } [data-object|="unit"][data-coalition="blue"][data-is-selected] .unit-short-label { - color: var( --secondary-blue-text ); + color: var(--secondary-blue-text); } [data-object|="unit"][data-coalition="blue"] .unit-fuel-level, @@ -469,16 +472,16 @@ [data-object|="unit"][data-coalition="blue"][data-has-fox-2] .unit-ammo-fox-2, [data-object|="unit"][data-coalition="blue"][data-has-fox-3] .unit-ammo-fox-3, [data-object|="unit"][data-coalition="blue"][data-has-other-ammo] .unit-ammo-other { - background-color: var( --primary-blue ); + background-color: var(--primary-blue); } [data-object|="unit"][data-coalition="blue"] .unit-vvi { - background-color: var( --secondary-blue-outline ); + background-color: var(--secondary-blue-outline); } [data-object|="unit"][data-coalition="red"][data-is-selected] .unit-short-label { - color: var( --secondary-red-text ); + color: var(--secondary-red-text); } [data-object|="unit"][data-coalition="red"] .unit-fuel-level, @@ -486,18 +489,18 @@ [data-object|="unit"][data-coalition="red"][data-has-fox-2] .unit-ammo-fox-2, [data-object|="unit"][data-coalition="red"][data-has-fox-3] .unit-ammo-fox-3, [data-object|="unit"][data-coalition="red"][data-has-other-ammo] .unit-ammo-other { - background-color: var( --primary-red ); + background-color: var(--primary-red); } [data-object|="unit"][data-coalition="blue"] .unit-vvi { - background-color: var( --secondary-red-outline ); + background-color: var(--secondary-red-outline); } @keyframes pulse { 50% { - opacity: 0; + opacity: 0; } } @@ -508,83 +511,82 @@ [data-object|="unit"] .unit-state { background-repeat: no-repeat; - position:absolute; - height:var( --unit-aircraft-state-height ); - width:var( --unit-aircraft-state-width ); + position: absolute; + height: var(--unit-aircraft-state-height); + width: var(--unit-aircraft-state-width); z-index: 10; } [data-object|="unit"][data-state="rtb"] .unit-state { - background-image: var( --unit-aircraft-state-rtb ); + background-image: var(--unit-aircraft-state-rtb); } [data-object|="unit"][data-state="land"] .unit-state { - background-image: var( --unit-aircraft-state-rtb ); + background-image: var(--unit-aircraft-state-rtb); } [data-object|="unit"][data-state="idle"] .unit-state { - background-image: var( --unit-aircraft-state-idle ); + background-image: var(--unit-aircraft-state-idle); } [data-object|="unit"][data-state="attack"] .unit-state { - background-image: var( --unit-aircraft-state-attack ); + background-image: var(--unit-aircraft-state-attack); } [data-object|="unit"][data-state="follow"] .unit-state { - background-image: var( --unit-aircraft-state-follow ); + background-image: var(--unit-aircraft-state-follow); } [data-object|="unit"][data-state="refuel"] .unit-state { - background-image: var( --unit-aircraft-state-refuel ); + background-image: var(--unit-aircraft-state-refuel); } [data-object|="unit"][data-state="human"] .unit-state { - background-image: var( --unit-aircraft-state-human ); + background-image: var(--unit-aircraft-state-human); } [data-object|="unit"][data-state="dcs"] .unit-state { - background-image: var( --unit-aircraft-state-dcs ); + background-image: var(--unit-aircraft-state-dcs); } /*** DEAD ***/ -[data-object|="unit-aircraft"][ data-is-dead ] { +[data-object|="unit-aircraft"][ data-is-dead] { cursor: default; } -[data-object|="unit-aircraft"][ data-is-dead ] .unit-marker { - background-image: var( --unit-aircraft-marker-neutral-dead-url ); +[data-object|="unit-aircraft"][ data-is-dead] .unit-marker { + background-image: var(--unit-aircraft-marker-neutral-dead-url); background-position: 50% 50%; background-size: auto 32px; } -[data-object|="unit-aircraft"][ data-is-dead ][data-coalition="blue"] .unit-marker { - background-image: var( --unit-aircraft-marker-blue-dead-url ); +[data-object|="unit-aircraft"][ data-is-dead][data-coalition="blue"] .unit-marker { + background-image: var(--unit-aircraft-marker-blue-dead-url); } -[data-object|="unit-aircraft"][ data-is-dead ][data-coalition="red"] .unit-marker { - background-image: var( --unit-aircraft-marker-red-dead-url ); +[data-object|="unit-aircraft"][ data-is-dead][data-coalition="red"] .unit-marker { + background-image: var(--unit-aircraft-marker-red-dead-url); } -[data-object|="unit-aircraft"][ data-is-dead ] .unit-selected-spotlight, -[data-object|="unit-aircraft"][ data-is-dead ] .unit-short-label, -[data-object|="unit-aircraft"][ data-is-dead ] .unit-vvi, -[data-object|="unit-aircraft"][ data-is-dead ] .unit-hotgroup, -[data-object|="unit-aircraft"][ data-is-dead ] .unit-hotgroup-id, -[data-object|="unit-aircraft"][ data-is-dead ] .unit-state, -[data-object|="unit-aircraft"][ data-is-dead ] .unit-fuel, -[data-object|="unit-aircraft"][ data-is-dead ] .unit-ammo, -[data-object|="unit-aircraft"][ data-is-dead ]:hover .unit-fuel, -[data-object|="unit-aircraft"][ data-is-dead ]:hover .unit-ammo { - display:none !important; +[data-object|="unit-aircraft"][ data-is-dead] .unit-selected-spotlight, +[data-object|="unit-aircraft"][ data-is-dead] .unit-short-label, +[data-object|="unit-aircraft"][ data-is-dead] .unit-vvi, +[data-object|="unit-aircraft"][ data-is-dead] .unit-hotgroup, +[data-object|="unit-aircraft"][ data-is-dead] .unit-hotgroup-id, +[data-object|="unit-aircraft"][ data-is-dead] .unit-state, +[data-object|="unit-aircraft"][ data-is-dead] .unit-fuel, +[data-object|="unit-aircraft"][ data-is-dead] .unit-ammo, +[data-object|="unit-aircraft"][ data-is-dead]:hover .unit-fuel, +[data-object|="unit-aircraft"][ data-is-dead]:hover .unit-ammo { + display: none !important; } -[data-object|="unit-aircraft"][ data-is-dead ] .unit-summary > * { - display:none; -} - -[data-object|="unit-aircraft"][ data-is-dead ] .unit-summary .unit-callsign { - display:block; +[data-object|="unit-aircraft"][ data-is-dead] .unit-summary>* { + display: none; } +[data-object|="unit-aircraft"][ data-is-dead] .unit-summary .unit-callsign { + display: block; +} \ No newline at end of file diff --git a/client/src/index.ts b/client/src/index.ts index 2c817671..d5f41186 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -192,7 +192,7 @@ function setupEvents() { case "KeyL": document.body.toggleAttribute("data-hide-labels"); break; - case "KeyD": + case "KeyT": toggleDemoEnabled(); break; case "Quote": @@ -201,6 +201,37 @@ function setupEvents() { case "Space": setPaused(!getPaused()); break; + case "KeyW": + case "KeyA": + case "KeyS": + case "KeyD": + case "ArrowLeft": + case "ArrowRight": + case "ArrowUp": + case "ArrowDown": + getMap().handleMapPanning(ev); + break; + } + }); + + /* Keydown events */ + document.addEventListener("keydown", ev => { + + if ( keyEventWasInInput( ev ) ) { + return; + } + + switch (ev.code) { + case "KeyW": + case "KeyA": + case "KeyS": + case "KeyD": + case "ArrowLeft": + case "ArrowRight": + case "ArrowUp": + case "ArrowDown": + getMap().handleMapPanning(ev); + break; } }); @@ -231,7 +262,6 @@ function setupEvents() { el.classList.toggle( "hide" ); }) }); - } export function getMap() { diff --git a/client/src/map/boxselect.ts b/client/src/map/boxselect.ts index 421509ef..6161fa88 100644 --- a/client/src/map/boxselect.ts +++ b/client/src/map/boxselect.ts @@ -45,7 +45,7 @@ export var BoxSelect = Handler.extend({ }, _onMouseDown: function (e: any) { - if (((e.which !== 1) && (e.button !== 0))) { return false; } + if ((e.which !== 1 && e.button !== 0) || !e.shiftKey) { return false; } // Clear the deferred resetState if it hasn't executed yet, otherwise it // will interrupt the interaction and orphan a box element in the container. diff --git a/client/src/map/map.ts b/client/src/map/map.ts index be0c7031..46f7b0ae 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -19,8 +19,7 @@ export const MOVE_UNIT = "MOVE_UNIT"; L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect); export class ClickableMiniMap extends MiniMap { - constructor(layer: L.TileLayer | L.LayerGroup, options?: MiniMapOptions) - { + constructor(layer: L.TileLayer | L.LayerGroup, options?: MiniMapOptions) { super(layer, options); } @@ -34,7 +33,11 @@ export class Map extends L.Map { #state: string; #layer: L.TileLayer | null = null; #preventLeftClick: boolean = false; - #leftClickTimer: any = 0; + #leftClickTimer: number = 0; + #deafultPanDelta: number = 100; + #panInterval: number | null = null; + #panDeltaX: number; + #panDeltaY: number; #lastMousePosition: L.Point = new L.Point(0, 0); #centerUnit: Unit | null = null; #miniMap: ClickableMiniMap | null = null; @@ -49,7 +52,7 @@ export class Map extends L.Map { constructor(ID: string) { /* Init the leaflet map */ //@ts-ignore - super(ID, { doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true, zoomAnimation: true, maxBoundsViscosity: 1.0, minZoom: 7 }); + super(ID, { doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true, zoomAnimation: true, maxBoundsViscosity: 1.0, minZoom: 7, keyboard: true, keyboardPanDelta: 0 }); this.setView([37.23, -115.8], 10); this.setLayer("ArcGIS Satellite"); @@ -57,59 +60,59 @@ export class Map extends L.Map { /* Minimap */ /* Draw the limits of the maps in the minimap*/ var latlngs = [[ // NTTR - new L.LatLng(39.7982463, -119.985425 ), - new L.LatLng(34.4037128, -119.7806729), - new L.LatLng(34.3483316, -112.4529351), - new L.LatLng(39.7372411, -112.1130805), - new L.LatLng(39.7982463, -119.985425 ) - ], - [ // Syria - new L.LatLng(37.3630556, 29.2686111), - new L.LatLng(31.8472222, 29.8975), - new L.LatLng(32.1358333, 42.1502778), - new L.LatLng(37.7177778, 42.3716667), - new L.LatLng(37.3630556, 29.2686111) - ], - [ // Caucasus - new L.LatLng(39.6170191, 27.634935), - new L.LatLng(38.8735863, 47.1423108), - new L.LatLng(47.3907982, 49.3101946), - new L.LatLng(48.3955879, 26.7753625), - new L.LatLng(39.6170191, 27.634935) - ], - [ // Persian Gulf - new L.LatLng(32.9355285, 46.5623682), - new L.LatLng(21.729393, 47.572675), - new L.LatLng(21.8501348, 63.9734737), - new L.LatLng(33.131584, 64.7313594), - new L.LatLng(32.9355285, 46.5623682) - ], - [ // Marianas - new L.LatLng(22.09, 135.0572222), - new L.LatLng(10.5777778, 135.7477778), - new L.LatLng(10.7725, 149.3918333), - new L.LatLng(22.5127778, 149.5427778), - new L.LatLng(22.09, 135.0572222) - ] - ]; + new L.LatLng(39.7982463, -119.985425), + new L.LatLng(34.4037128, -119.7806729), + new L.LatLng(34.3483316, -112.4529351), + new L.LatLng(39.7372411, -112.1130805), + new L.LatLng(39.7982463, -119.985425) + ], + [ // Syria + new L.LatLng(37.3630556, 29.2686111), + new L.LatLng(31.8472222, 29.8975), + new L.LatLng(32.1358333, 42.1502778), + new L.LatLng(37.7177778, 42.3716667), + new L.LatLng(37.3630556, 29.2686111) + ], + [ // Caucasus + new L.LatLng(39.6170191, 27.634935), + new L.LatLng(38.8735863, 47.1423108), + new L.LatLng(47.3907982, 49.3101946), + new L.LatLng(48.3955879, 26.7753625), + new L.LatLng(39.6170191, 27.634935) + ], + [ // Persian Gulf + new L.LatLng(32.9355285, 46.5623682), + new L.LatLng(21.729393, 47.572675), + new L.LatLng(21.8501348, 63.9734737), + new L.LatLng(33.131584, 64.7313594), + new L.LatLng(32.9355285, 46.5623682) + ], + [ // Marianas + new L.LatLng(22.09, 135.0572222), + new L.LatLng(10.5777778, 135.7477778), + new L.LatLng(10.7725, 149.3918333), + new L.LatLng(22.5127778, 149.5427778), + new L.LatLng(22.09, 135.0572222) + ] + ]; var minimapLayer = new L.TileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { minZoom: 0, maxZoom: 13 }); this.#miniMapLayerGroup = new L.LayerGroup([minimapLayer]); - var miniMapPolyline = new L.Polyline(latlngs, {color: '#202831'}); + var miniMapPolyline = new L.Polyline(latlngs, { color: '#202831' }); miniMapPolyline.addTo(this.#miniMapLayerGroup); - + /* Scale */ //@ts-ignore TODO more hacking because the module is provided as a pure javascript module only - L.control.scalenautic({position: "topright", maxWidth: 300, nautic: true, metric: true, imperial: false}).addTo(this); + L.control.scalenautic({ position: "topright", maxWidth: 300, nautic: true, metric: true, imperial: false }).addTo(this); /* Init the state machine */ this.#state = IDLE; /* Register event handles */ this.on("click", (e: any) => this.#onClick(e)); - this.on("dblclick", (e: any) => this.#onDoubleClick(e)); - this.on("zoomstart", (e: any) => this.#onZoom(e)); - this.on("drag", (e: any) => this.centerOnUnit(null)); + this.on("dblclick", (e: any) => this.#onDoubleClick(e)); + this.on("zoomstart", (e: any) => this.#onZoom(e)); + this.on("drag", (e: any) => this.centerOnUnit(null)); this.on("contextmenu", (e: any) => this.#onContextMenu(e)); this.on('selectionend', (e: any) => this.#onSelectionEnd(e)); this.on('mousedown', (e: any) => this.#onMouseDown(e)); @@ -121,7 +124,7 @@ export class Map extends L.Map { document.body.toggleAttribute("data-hide-" + ev.detail.coalition); Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); }); - + document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => { document.body.toggleAttribute("data-hide-" + ev.detail.category); Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); @@ -131,8 +134,15 @@ export class Map extends L.Map { if (this.#centerUnit != null && ev.detail == this.#centerUnit) this.#panToUnit(this.#centerUnit); }); - + this.#mapSourceDropdown = new Dropdown("map-type", (layerName: string) => this.setLayer(layerName), this.getLayers()) + + this.#panDeltaX = 0; + this.#panDeltaY = 0; + + this.#panInterval = window.setInterval(() => { + this.panBy(new L.Point(this.#panDeltaX, this.#panDeltaY)); + }, 20); } setLayer(layerName: string) { @@ -187,10 +197,10 @@ export class Map extends L.Map { setState(state: string) { this.#state = state; if (this.#state === IDLE) { - L.DomUtil.removeClass(this.getContainer(),'crosshair-cursor-enabled'); + L.DomUtil.removeClass(this.getContainer(), 'crosshair-cursor-enabled'); } else if (this.#state === MOVE_UNIT) { - L.DomUtil.addClass(this.getContainer(),'crosshair-cursor-enabled'); + L.DomUtil.addClass(this.getContainer(), 'crosshair-cursor-enabled'); } document.dispatchEvent(new CustomEvent("mapStateChanged")); } @@ -200,8 +210,7 @@ export class Map extends L.Map { } /* Context Menus */ - hideAllContextMenus() - { + hideAllContextMenus() { this.hideMapContextMenu(); this.hideUnitContextMenu(); this.hideAirbaseContextMenu(); @@ -220,7 +229,7 @@ export class Map extends L.Map { document.dispatchEvent(new CustomEvent("mapContextMenu")); } - getMapContextMenu(){ + getMapContextMenu() { return this.#mapContextMenu; } @@ -231,7 +240,7 @@ export class Map extends L.Map { this.#unitContextMenu.show(x, y, e.latlng); } - getUnitContextMenu(){ + getUnitContextMenu() { return this.#unitContextMenu; } @@ -247,7 +256,7 @@ export class Map extends L.Map { this.#airbaseContextMenu.setAirbase(airbase); } - getAirbaseContextMenu(){ + getAirbaseContextMenu() { return this.#airbaseContextMenu; } @@ -270,8 +279,7 @@ export class Map extends L.Map { } centerOnUnit(ID: number | null) { - if (ID != null) - { + if (ID != null) { this.options.scrollWheelZoom = 'center'; this.#centerUnit = getUnitsManager().getUnitByID(ID); } @@ -289,16 +297,14 @@ export class Map extends L.Map { else if (theatre == "MarianaIslands") bounds = new L.LatLngBounds([10.5777778, 135.7477778], [22.5127778, 149.5427778]); else if (theatre == "Nevada") - bounds = new L.LatLngBounds([34.4037128, -119.7806729], [39.7372411, -112.1130805]) + bounds = new L.LatLngBounds([34.4037128, -119.7806729], [39.7372411, -112.1130805]) else if (theatre == "PersianGulf") - bounds = new L.LatLngBounds([21.729393, 47.572675], [33.131584, 64.7313594]) - else if (theatre == "Falklands") - { + bounds = new L.LatLngBounds([21.729393, 47.572675], [33.131584, 64.7313594]) + else if (theatre == "Falklands") { // TODO } - else if (theatre == "Caucasus") - { - bounds = new L.LatLngBounds([39.6170191, 27.634935], [47.3907982, 49.3101946]) + else if (theatre == "Caucasus") { + bounds = new L.LatLngBounds([39.6170191, 27.634935], [47.3907982, 49.3101946]) miniMapZoom = 4; } @@ -309,25 +315,65 @@ export class Map extends L.Map { this.#miniMap.remove(); //@ts-ignore // Needed because some of the inputs are wrong in the original module interface - this.#miniMap = new ClickableMiniMap(this.#miniMapLayerGroup, {position: "topright", width: 192*1.5, height: 108*1.5, zoomLevelFixed: miniMapZoom, centerFixed: bounds.getCenter()}).addTo(this); + this.#miniMap = new ClickableMiniMap(this.#miniMapLayerGroup, { position: "topright", width: 192 * 1.5, height: 108 * 1.5, zoomLevelFixed: miniMapZoom, centerFixed: bounds.getCenter() }).addTo(this); this.#miniMap.disableInteractivity(); this.#miniMap.getMap().on("click", (e: any) => { if (this.#miniMap) this.setView(e.latlng); }) - + } getMiniMapLayerGroup() { return this.#miniMapLayerGroup; } + handleMapPanning(e: any) { + if (e.type === "keyup"){ + switch (e.code) { + case "KeyA": + case "KeyD": + case "ArrowLeft": + case "ArrowRight": + this.#panDeltaX = 0; + break; + case "KeyW": + case "KeyS": + case "ArrowUp": + case "ArrowDown": + this.#panDeltaY = 0 + break; + } + } + else { + switch (e.code) + { + case 'KeyD': + case 'ArrowRight': + this.#panDeltaX = this.#deafultPanDelta; + break; + case 'KeyA': + case 'ArrowLeft': + this.#panDeltaX = -this.#deafultPanDelta; + break; + case 'KeyW': + case 'ArrowUp': + this.#panDeltaY = -this.#deafultPanDelta; + break; + case 'KeyS': + case 'ArrowDown': + this.#panDeltaY = this.#deafultPanDelta; + break; + } + } + } + /* Event handlers */ #onClick(e: any) { if (!this.#preventLeftClick) { this.hideAllContextMenus(); if (this.#state === IDLE) { - + } else if (this.#state === MOVE_UNIT) { this.setState(IDLE); @@ -337,7 +383,7 @@ export class Map extends L.Map { } #onDoubleClick(e: any) { - + } #onContextMenu(e: any) { @@ -355,44 +401,34 @@ export class Map extends L.Map { } } - #onSelectionEnd(e: any) - { + #onSelectionEnd(e: any) { clearTimeout(this.#leftClickTimer); this.#preventLeftClick = true; this.#leftClickTimer = window.setTimeout(() => { - this.#preventLeftClick = false; + this.#preventLeftClick = false; }, 200); getUnitsManager().selectFromBounds(e.selectionBounds); } - #onMouseDown(e: any) - { + #onMouseDown(e: any) { this.hideAllContextMenus(); - if ((e.originalEvent.which == 1) && (e.originalEvent.button == 0)) - this.dragging.disable(); } - #onMouseUp(e: any) - { - if ((e.originalEvent.which == 1) && (e.originalEvent.button == 0)) - this.dragging.enable(); + #onMouseUp(e: any) { } - #onMouseMove(e: any) - { + #onMouseMove(e: any) { this.#lastMousePosition.x = e.originalEvent.x; this.#lastMousePosition.y = e.originalEvent.y; } - #onZoom(e: any) - { + #onZoom(e: any) { if (this.#centerUnit != null) this.#panToUnit(this.#centerUnit); } - #panToUnit(unit: Unit) - { + #panToUnit(unit: Unit) { var unitPosition = new L.LatLng(unit.getFlightData().latitude, unit.getFlightData().longitude); - this.setView(unitPosition, this.getZoom(), {animate: false}); + this.setView(unitPosition, this.getZoom(), { animate: false }); } } diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index 07c0c0e6..d3ee4338 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -89,7 +89,7 @@ export class Unit extends Marker { } constructor(ID: number, data: UpdateData) { - super(new LatLng(0, 0), { riseOnHover: true }); + super(new LatLng(0, 0), { riseOnHover: true, keyboard: false }); this.ID = ID; @@ -121,7 +121,7 @@ export class Unit extends Marker { var icon = new DivIcon({ html: this.getMarkerHTML(), className: 'leaflet-unit-marker', - iconAnchor: [25, 0], + iconAnchor: [25, 25], iconSize: [50, 50], }); this.setIcon(icon); diff --git a/client/views/connectionstatuspanel.ejs b/client/views/connectionstatuspanel.ejs index b0c7df1b..ac92da78 100644 --- a/client/views/connectionstatuspanel.ejs +++ b/client/views/connectionstatuspanel.ejs @@ -1,4 +1,4 @@ -
+
diff --git a/client/views/contextmenus.ejs b/client/views/contextmenus.ejs index b386ec9a..884642a9 100644 --- a/client/views/contextmenus.ejs +++ b/client/views/contextmenus.ejs @@ -1,4 +1,4 @@ -
+
-
+
-
+

diff --git a/client/views/dialogs.ejs b/client/views/dialogs.ejs index 1822808d..042c05be 100644 --- a/client/views/dialogs.ejs +++ b/client/views/dialogs.ejs @@ -1,4 +1,4 @@ -
+
@@ -17,7 +17,7 @@
-
+
@@ -141,7 +141,7 @@
-
+
diff --git a/client/views/mouseinfopanel.ejs b/client/views/mouseinfopanel.ejs index 932e4e01..5127e4a4 100644 --- a/client/views/mouseinfopanel.ejs +++ b/client/views/mouseinfopanel.ejs @@ -1,4 +1,4 @@ -
+
diff --git a/client/views/navbar.ejs b/client/views/navbar.ejs index aad54404..2ddf7c8d 100644 --- a/client/views/navbar.ejs +++ b/client/views/navbar.ejs @@ -1,4 +1,4 @@ -