diff --git a/client/public/stylesheets/markers/units.css b/client/public/stylesheets/markers/units.css
index d4f88333..bb1a88ad 100644
--- a/client/public/stylesheets/markers/units.css
+++ b/client/public/stylesheets/markers/units.css
@@ -265,9 +265,9 @@
}
[data-object|="unit"][data-state="attack"] .unit-state,
-[data-object|="unit"][data-state="bombing point"] .unit-state,
-[data-object|="unit"][data-state="carpet bombing"] .unit-state,
-[data-object|="unit"][data-state="firing at area"] .unit-state {
+[data-object|="unit"][data-state="bomb-point"] .unit-state,
+[data-object|="unit"][data-state="carpet-bombing"] .unit-state,
+[data-object|="unit"][data-state="fire-at-area"] .unit-state {
background-image: url("/resources/theme/images/states/attack.svg");
}
@@ -287,6 +287,10 @@
background-image: url("/resources/theme/images/states/dcs.svg");
}
+[data-object|="unit"][data-state="land-at-point"] .unit-state {
+ background-image: url("/resources/theme/images/states/land-at-point.svg");
+}
+
[data-object|="unit"][data-state="no-task"] .unit-state {
background-image: url("/resources/theme/images/states/no-task.svg");
}
diff --git a/client/public/stylesheets/other/contextmenus.css b/client/public/stylesheets/other/contextmenus.css
index 1e2c0843..53ddb6ed 100644
--- a/client/public/stylesheets/other/contextmenus.css
+++ b/client/public/stylesheets/other/contextmenus.css
@@ -420,6 +420,10 @@
content: url("/resources/theme/images/icons/group-navy.svg");
}
+#land-at-point::before {
+ content: url("/resources/theme/images/icons/land-at-point.svg");
+}
+
#trail::before {
content: url("/resources/theme/images/icons/trail.svg");
}
diff --git a/client/public/themes/olympus/images/icons/land-at-point.svg b/client/public/themes/olympus/images/icons/land-at-point.svg
new file mode 100644
index 00000000..8ed58233
--- /dev/null
+++ b/client/public/themes/olympus/images/icons/land-at-point.svg
@@ -0,0 +1,61 @@
+
+
diff --git a/client/public/themes/olympus/images/states/land-at-point.svg b/client/public/themes/olympus/images/states/land-at-point.svg
new file mode 100644
index 00000000..23bec41f
--- /dev/null
+++ b/client/public/themes/olympus/images/states/land-at-point.svg
@@ -0,0 +1,51 @@
+
+
diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts
index 0d602826..bd5b5532 100644
--- a/client/src/constants/constants.ts
+++ b/client/src/constants/constants.ts
@@ -20,7 +20,7 @@ export const IRST = 8;
export const RWR = 16;
export const DLINK = 32;
-export const states: string[] = ["none", "idle", "reach-destination", "attack", "follow", "land", "refuel", "AWACS", "tanker", "bomb-point", "carpet-bomb", "bomb-building", "fire-at-area", "simulate-fire-fight"];
+export const states: string[] = ["none", "idle", "reach-destination", "attack", "follow", "land", "refuel", "AWACS", "tanker", "bomb-point", "carpet-bomb", "bomb-building", "fire-at-area", "simulate-fire-fight", "scenic-aaa", "miss-on-purpose", "land-at-point"];
export const ROEs: string[] = ["free", "designated", "", "return", "hold"];
export const reactionsToThreat: string[] = ["none", "manoeuvre", "passive", "evade"];
export const emissionsCountermeasures: string[] = ["silent", "attack", "defend", "free"];
diff --git a/client/src/map/map.ts b/client/src/map/map.ts
index fc4d2fdb..7e9d51ab 100644
--- a/client/src/map/map.ts
+++ b/client/src/map/map.ts
@@ -24,8 +24,8 @@ import { TouchBoxSelect } from "./touchboxselect";
import { DestinationPreviewHandle } from "./markers/destinationpreviewHandle";
var hasTouchScreen = false;
-if ("maxTouchPoints" in navigator)
- hasTouchScreen = navigator.maxTouchPoints > 0;
+//if ("maxTouchPoints" in navigator)
+// hasTouchScreen = navigator.maxTouchPoints > 0;
if (hasTouchScreen)
L.Map.addInitHook('addHandler', 'boxSelect', TouchBoxSelect);
@@ -607,6 +607,10 @@ export class Map extends L.Map {
const selectedUnitTypes = getApp().getUnitsManager().getSelectedUnitsCategories();
if (selectedUnitTypes.length === 1 && ["Aircraft", "Helicopter"].includes(selectedUnitTypes[0])) {
+ if (selectedUnitTypes[0] === "Helicopter") {
+ options["land-at-point"] = { text: "Land here", tooltip: "Land at this precise location" };
+ }
+
if (selectedUnits.every((unit: Unit) => { return unit.canFulfillRole(["CAS", "Strike"]) })) {
options["bomb"] = { text: "Precision bombing", tooltip: "Precision bombing of a specific point" };
options["carpet-bomb"] = { text: "Carpet bombing", tooltip: "Carpet bombing close to a point" };
@@ -646,6 +650,10 @@ export class Map extends L.Map {
getApp().getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
getApp().getUnitsManager().selectedUnitsSimulateFireFight(this.getMouseCoordinates());
}
+ else if (option === "land-at-point") {
+ getApp().getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
+ getApp().getUnitsManager().selectedUnitsLandAtPoint(this.getMouseCoordinates());
+ }
});
}
}, 150);
diff --git a/client/src/server/servermanager.ts b/client/src/server/servermanager.ts
index be2bfb5e..cf600561 100644
--- a/client/src/server/servermanager.ts
+++ b/client/src/server/servermanager.ts
@@ -351,6 +351,12 @@ export class ServerManager {
this.PUT(data, callback);
}
+ landAtPoint(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
+ var command = { "ID": ID, "location": latlng }
+ var data = { "landAtPoint": command }
+ this.PUT(data, callback);
+ }
+
setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings, callback: CallableFunction = () => {}) {
var command = {
"ID": ID,
diff --git a/client/src/unit/unit.ts b/client/src/unit/unit.ts
index cb8ed83d..c849c49b 100644
--- a/client/src/unit/unit.ts
+++ b/client/src/unit/unit.ts
@@ -779,6 +779,10 @@ export class Unit extends CustomMarker {
getApp().getServerManager().missOnPurpose(this.ID, coalition);
}
+ landAtPoint(latlng: LatLng) {
+ getApp().getServerManager().landAtPoint(this.ID, latlng);
+ }
+
/***********************************************/
getActions(): { [key: string]: { text: string, tooltip: string, type: string } } {
/* To be implemented by child classes */ // TODO make Unit an abstract class
diff --git a/client/src/unit/unitsmanager.ts b/client/src/unit/unitsmanager.ts
index 65696f21..f03e0fbd 100644
--- a/client/src/unit/unitsmanager.ts
+++ b/client/src/unit/unitsmanager.ts
@@ -666,6 +666,19 @@ export class UnitsManager {
this.#showActionMessage(selectedUnits, `unit set to perform miss on purpose AAA`);
}
+ /** Instruct units to land at specific point
+ *
+ * @param latlng Point where to land
+ */
+ selectedUnitsLandAtPoint(latlng: LatLng) {
+ var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true });
+
+ for (let idx in selectedUnits) {
+ selectedUnits[idx].landAtPoint(latlng);
+ }
+ this.#showActionMessage(selectedUnits, `unit simulating fire fight`);
+ }
+
/*********************** Control operations on selected units ************************/
/** See getUnitsCategories for more info
*
diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua
index 28f4fe00..f1aa896e 100644
--- a/scripts/OlympusCommand.lua
+++ b/scripts/OlympusCommand.lua
@@ -1,6 +1,6 @@
local version = "v0.4.4-alpha"
-local debug = false -- True enables debug printing using DCS messages
+local debug = true -- True enables debug printing using DCS messages
-- .dll related variables
Olympus.OlympusDLL = nil
@@ -260,6 +260,15 @@ function Olympus.buildTask(groupName, options)
}
}
end
+ -- Land at a specific point
+ elseif options['id'] == 'LandAtPoint' then
+ local point = coord.LLtoLO(options['lat'], options['lng'], 0)
+ task = {
+ id = 'Land',
+ params = {
+ point = {x = point.x, y = point.z},
+ }
+ }
end
end
return task
diff --git a/src/core/include/datatypes.h b/src/core/include/datatypes.h
index dd098232..81a767af 100644
--- a/src/core/include/datatypes.h
+++ b/src/core/include/datatypes.h
@@ -68,7 +68,8 @@ namespace State
FIRE_AT_AREA,
SIMULATE_FIRE_FIGHT,
SCENIC_AAA,
- MISS_ON_PURPOSE
+ MISS_ON_PURPOSE,
+ LAND_AT_POINT
};
};
diff --git a/src/core/src/airunit.cpp b/src/core/src/airunit.cpp
index 2a5ced5a..3f1950e8 100644
--- a/src/core/src/airunit.cpp
+++ b/src/core/src/airunit.cpp
@@ -69,6 +69,9 @@ void AirUnit::setState(unsigned char newState)
setTargetPosition(Coords(NULL));
break;
}
+ case State::LAND_AT_POINT: {
+ break;
+ }
default:
break;
}
@@ -125,6 +128,10 @@ void AirUnit::setState(unsigned char newState)
resetActiveDestination();
break;
}
+ case State::LAND_AT_POINT: {
+ resetActiveDestination();
+ break;
+ }
default:
break;
}
@@ -340,6 +347,24 @@ void AirUnit::AIloop()
}
break;
}
+ case State::LAND_AT_POINT: {
+ setTask("Landing at point");
+
+ if (!getHasTask()) {
+ setActiveDestination();
+ std::ostringstream taskSS;
+ taskSS.precision(10),
+ taskSS << "{"
+ << "id = 'LandAtPoint', "
+ << "lat = " << activeDestination.lat << ", "
+ << "lng = " << activeDestination.lng
+ << "}";
+ Command* command = dynamic_cast(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
+ scheduler->appendCommand(command);
+ setHasTask(true);
+ }
+ break;
+ }
default:
break;
}
diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp
index df480c32..28ad9459 100644
--- a/src/core/src/scheduler.cpp
+++ b/src/core/src/scheduler.cpp
@@ -600,6 +600,22 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
Unit* unit = unitsManager->getGroupLeader(ID);
unit->setOperateAs(operateAs);
}
+ else if (key.compare("landAtPoint") == 0)
+ {
+ unsigned int ID = value[L"ID"].as_integer();
+ unitsManager->acquireControl(ID);
+ double lat = value[L"location"][L"lat"].as_double();
+ double lng = value[L"location"][L"lng"].as_double();
+ Coords loc; loc.lat = lat; loc.lng = lng;
+ Unit* unit = unitsManager->getGroupLeader(ID);
+
+ list newPath;
+ newPath.push_back(loc);
+ unit->setActivePath(newPath);
+ unit->setState(State::LAND_AT_POINT);
+
+ log(username + " tasked unit " + unit->getName() + " to land at point", true);
+ }
else if (key.compare("setCommandModeOptions") == 0)
{
setCommandModeOptions(value);