From ee08d9d48e03edff8f7091e6e35b8a8065ad84d0 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Fri, 28 Apr 2023 17:32:28 +0200 Subject: [PATCH 1/2] 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 2/2] 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;