mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Basic scenic and miss on purpose AAA modes
This commit is contained in:
parent
d209f98265
commit
5035c408e7
@ -329,6 +329,18 @@ export class ServerManager {
|
||||
this.PUT(data, callback);
|
||||
}
|
||||
|
||||
scenicAAA(ID: number, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID }
|
||||
var data = { "scenicAAA": command }
|
||||
this.PUT(data, callback);
|
||||
}
|
||||
|
||||
missOnPurpose(ID: number, callback: CallableFunction = () => {}) {
|
||||
var command = { "ID": ID }
|
||||
var data = { "missOnPurpose": command }
|
||||
this.PUT(data, callback);
|
||||
}
|
||||
|
||||
setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings, callback: CallableFunction = () => {}) {
|
||||
var command = {
|
||||
"ID": ID,
|
||||
|
||||
@ -739,6 +739,15 @@ export class Unit extends CustomMarker {
|
||||
});
|
||||
}
|
||||
|
||||
scenicAAA() {
|
||||
getApp().getServerManager().scenicAAA(this.ID);
|
||||
}
|
||||
|
||||
missOnPurpose() {
|
||||
getApp().getServerManager().missOnPurpose(this.ID);
|
||||
}
|
||||
|
||||
|
||||
/***********************************************/
|
||||
onAdd(map: Map): this {
|
||||
super.onAdd(map);
|
||||
@ -802,7 +811,7 @@ export class Unit extends CustomMarker {
|
||||
if (selectedUnits.length > 0 && !(selectedUnits.length == 1 && (selectedUnits.includes(this)))) {
|
||||
options["attack"] = { text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons" };
|
||||
if (getApp().getUnitsManager().getSelectedUnitsCategories().length == 1 && getApp().getUnitsManager().getSelectedUnitsCategories()[0] === "Aircraft")
|
||||
options["follow"] = { text: "Follow", tooltip: "Follow the unit at a user defined distance and position" };;
|
||||
options["follow"] = { text: "Follow", tooltip: "Follow the unit at a user defined distance and position" };
|
||||
}
|
||||
else if ((selectedUnits.length > 0 && (selectedUnits.includes(this))) || selectedUnits.length == 0) {
|
||||
if (this.getCategory() == "Aircraft") {
|
||||
@ -813,6 +822,11 @@ export class Unit extends CustomMarker {
|
||||
if (selectedUnitTypes.length === 1 && ["NavyUnit", "GroundUnit"].includes(selectedUnitTypes[0]) && getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) !== undefined)
|
||||
options["group"] = { text: "Create group", tooltip: "Create a group from the selected units." };
|
||||
|
||||
if (selectedUnitTypes.length === 1 && ["GroundUnit"].includes(selectedUnitTypes[0]) && selectedUnits.every((unit: Unit) => { return ["AAA"].includes(unit.getType())})) {
|
||||
options["scenic-aaa"] = { text: "Scenic AAA", tooltip: "Shoot AAA in the air without aiming at any target" };
|
||||
options["miss-aaa"] = { text: "Miss on purpose AAA", tooltip: "Shoot AAA towards the closes enemy, but don't aim precisely" };
|
||||
}
|
||||
|
||||
if (Object.keys(options).length > 0) {
|
||||
getApp().getMap().showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng);
|
||||
getApp().getMap().getUnitContextMenu().setOptions(options, (option: string) => {
|
||||
@ -831,6 +845,10 @@ export class Unit extends CustomMarker {
|
||||
getApp().getUnitsManager().selectedUnitsRefuel();
|
||||
else if (action === "group")
|
||||
getApp().getUnitsManager().selectedUnitsCreateGroup();
|
||||
else if (action === "scenic-aaa")
|
||||
getApp().getUnitsManager().selectedUnitsScenicAAA();
|
||||
else if (action === "miss-aaa")
|
||||
getApp().getUnitsManager().selectedUnitsMissOnPurpose();
|
||||
else if (action === "follow")
|
||||
this.#showFollowOptions(e);
|
||||
}
|
||||
|
||||
@ -625,6 +625,26 @@ export class UnitsManager {
|
||||
});
|
||||
this.#showActionMessage(selectedUnits, `unit simulating fire fight`);
|
||||
}
|
||||
|
||||
/** Instruct units to enter into scenic AAA mode. Units will shoot in the air without aiming
|
||||
*
|
||||
*/
|
||||
selectedUnitsScenicAAA() {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].scenicAAA();
|
||||
}
|
||||
}
|
||||
|
||||
/** Instruct units to enter into miss on purpose mode. Units will aim to the nearest enemy unit but not precisely.
|
||||
*
|
||||
*/
|
||||
selectedUnitsMissOnPurpose() {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].missOnPurpose();
|
||||
}
|
||||
}
|
||||
|
||||
/*********************** Control operations on selected units ************************/
|
||||
/** See getUnitsCategories for more info
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
local version = "v0.4.4-alpha"
|
||||
|
||||
local debug = true -- True enables debug printing using DCS messages
|
||||
local debug = false -- True enables debug printing using DCS messages
|
||||
|
||||
-- .dll related variables
|
||||
Olympus.OlympusDLL = nil
|
||||
|
||||
@ -65,7 +65,9 @@ namespace State
|
||||
CARPET_BOMB,
|
||||
BOMB_BUILDING,
|
||||
FIRE_AT_AREA,
|
||||
SIMULATE_FIRE_FIGHT
|
||||
SIMULATE_FIRE_FIGHT,
|
||||
SCENIC_AAA,
|
||||
MISS_ON_PURPOSE
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -186,6 +186,7 @@ protected:
|
||||
|
||||
/********** Other **********/
|
||||
unsigned int taskCheckCounter = 0;
|
||||
unsigned int internalCounter = 0;
|
||||
bool hasTaskAssigned = false;
|
||||
double initialFuel = 0;
|
||||
map<unsigned char, unsigned long long> updateTimeMap;
|
||||
|
||||
@ -23,7 +23,8 @@ public:
|
||||
void deleteUnit(unsigned int ID, bool explosion, bool immediate);
|
||||
void acquireControl(unsigned int ID);
|
||||
void loadDatabases();
|
||||
|
||||
Unit* getClosestUnit(Unit* unit, unsigned char coalition, vector<string> categories, double &distance);
|
||||
|
||||
private:
|
||||
map<unsigned int, Unit*> units;
|
||||
json::value missionDB;
|
||||
|
||||
@ -73,6 +73,12 @@ void GroundUnit::setState(unsigned char newState)
|
||||
setTargetPosition(Coords(NULL));
|
||||
break;
|
||||
}
|
||||
case State::SCENIC_AAA: {
|
||||
break;
|
||||
}
|
||||
case State::MISS_ON_PURPOSE: {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -99,6 +105,16 @@ void GroundUnit::setState(unsigned char newState)
|
||||
resetActiveDestination();
|
||||
break;
|
||||
}
|
||||
case State::SCENIC_AAA: {
|
||||
clearActivePath();
|
||||
resetActiveDestination();
|
||||
break;
|
||||
}
|
||||
case State::MISS_ON_PURPOSE: {
|
||||
clearActivePath();
|
||||
resetActiveDestination();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -156,11 +172,13 @@ void GroundUnit::AIloop()
|
||||
scheduler->appendCommand(command);
|
||||
setHasTask(true);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State::SIMULATE_FIRE_FIGHT: {
|
||||
setTask("Simulating fire fight");
|
||||
|
||||
if (!getHasTask() || (((double)(rand()) / (double)(RAND_MAX)) < 0.002)) {
|
||||
if (!getHasTask() || internalCounter == 0) {
|
||||
double dist;
|
||||
double bearing1;
|
||||
double bearing2;
|
||||
@ -175,7 +193,6 @@ void GroundUnit::AIloop()
|
||||
if (databaseEntry.has_number_field(L"barrelHeight") && databaseEntry.has_number_field(L"muzzleVelocity")) {
|
||||
barrelHeight = databaseEntry[L"barrelHeight"].as_number().to_double();
|
||||
muzzleVelocity = databaseEntry[L"muzzleVelocity"].as_number().to_double();
|
||||
log(to_string(barrelHeight) + " " + to_string(muzzleVelocity));
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,6 +210,93 @@ void GroundUnit::AIloop()
|
||||
scheduler->appendCommand(command);
|
||||
setHasTask(true);
|
||||
}
|
||||
|
||||
if (internalCounter == 0)
|
||||
internalCounter = 20 * scheduler->getFrameRate(); /* 20 seconds */
|
||||
internalCounter--;
|
||||
|
||||
break;
|
||||
}
|
||||
case State::SCENIC_AAA: {
|
||||
setTask("Scenic AAA");
|
||||
|
||||
if (!getHasTask() || internalCounter == 0) {
|
||||
double r = 15; /* m */
|
||||
double barrelElevation = r * tan(acos(((double)(rand()) / (double)(RAND_MAX))));
|
||||
|
||||
double lat = 0;
|
||||
double lng = 0;
|
||||
double randomBearing = ((double)(rand()) / (double)(RAND_MAX)) * 360;
|
||||
Geodesic::WGS84().Direct(position.lat, position.lng, randomBearing, r, lat, lng);
|
||||
|
||||
std::ostringstream taskSS;
|
||||
taskSS.precision(10);
|
||||
taskSS << "{id = 'FireAtPoint', lat = " << lat << ", lng = " << lng << ", alt = " << position.alt + barrelElevation << ", radius = 0.001}";
|
||||
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
|
||||
scheduler->appendCommand(command);
|
||||
setHasTask(true);
|
||||
}
|
||||
|
||||
if (internalCounter == 0)
|
||||
internalCounter = 20 * scheduler->getFrameRate(); /* 20 seconds */
|
||||
internalCounter--;
|
||||
|
||||
break;
|
||||
}
|
||||
case State::MISS_ON_PURPOSE: {
|
||||
setTask("Missing on purpose");
|
||||
|
||||
/* Only run this when the internal counter reaches 0 to avoid excessive computations when no nearby target */
|
||||
if (internalCounter == 0) {
|
||||
double distance = 0;
|
||||
Unit* target = unitsManager->getClosestUnit(this, 1, { "Aircraft", "Helicopter" }, distance); /* Red, TODO make assignable */
|
||||
|
||||
/* Only do if we have a valid target close enough for AAA */
|
||||
if (target != nullptr && distance < 10000 /* m */) {
|
||||
double aimTime = 10; /* s TODO: add to database */
|
||||
|
||||
/* Compute where the target will be in aimTime seconds. We don't consider vertical velocity atm, since after all we are not really tring to hit */
|
||||
double aimDistance = target->getSpeed() * aimTime;
|
||||
double aimLat = 0;
|
||||
double aimLng = 0;
|
||||
Geodesic::WGS84().Direct(target->getPosition().lat, target->getPosition().lng, target->getHeading(), aimDistance, aimLat, aimLng);
|
||||
|
||||
/* Compute distance to the aim point */
|
||||
double dist;
|
||||
double bearing1;
|
||||
double bearing2;
|
||||
Geodesic::WGS84().Inverse(position.lat, position.lng, aimLat, aimLng, dist, bearing1, bearing2);
|
||||
|
||||
/* Default gun values */
|
||||
double barrelHeight = 1.0; /* m */
|
||||
double muzzleVelocity = 860; /* m/s */
|
||||
if (database.has_object_field(to_wstring(name))) {
|
||||
json::value databaseEntry = database[to_wstring(name)];
|
||||
if (databaseEntry.has_number_field(L"barrelHeight") && databaseEntry.has_number_field(L"muzzleVelocity")) {
|
||||
barrelHeight = databaseEntry[L"barrelHeight"].as_number().to_double();
|
||||
muzzleVelocity = databaseEntry[L"muzzleVelocity"].as_number().to_double();
|
||||
}
|
||||
}
|
||||
|
||||
/* Send the command */
|
||||
std::ostringstream taskSS;
|
||||
taskSS.precision(10);
|
||||
taskSS << "{id = 'FireAtPoint', lat = " << aimLat << ", lng = " << aimLng << ", alt = " << target->getPosition().alt + 0.5 * 9.81 * (dist * dist) / (muzzleVelocity * muzzleVelocity) << ", radius = 0.001}";
|
||||
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
|
||||
scheduler->appendCommand(command);
|
||||
setHasTask(true);
|
||||
}
|
||||
else {
|
||||
if (getHasTask())
|
||||
resetTask();
|
||||
}
|
||||
}
|
||||
|
||||
if (internalCounter == 0)
|
||||
internalCounter = 5 * scheduler->getFrameRate();
|
||||
internalCounter--;
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
|
||||
@ -390,6 +390,7 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
|
||||
Coords location; location.lat = lat; location.lng = lng;
|
||||
cloneOptions.push_back({ ID, location });
|
||||
log(username + " cloning unit with ID " + to_string(ID), true);
|
||||
}
|
||||
|
||||
command = dynamic_cast<Command*>(new Clone(cloneOptions, deleteOriginal));
|
||||
@ -575,6 +576,22 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
unit->setTargetPosition(loc);
|
||||
log(username + " tasked unit " + unit->getName() + " to simulate a fire fight", true);
|
||||
}
|
||||
else if (key.compare("scenicAAA") == 0)
|
||||
{
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
unit->setState(State::SCENIC_AAA);
|
||||
log(username + " tasked unit " + unit->getName() + " to enter scenic AAA state", true);
|
||||
}
|
||||
else if (key.compare("missOnPurpose") == 0)
|
||||
{
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
unit->setState(State::MISS_ON_PURPOSE);
|
||||
log(username + " tasked unit " + unit->getName() + " to enter Miss On Purpose state", true);
|
||||
}
|
||||
else if (key.compare("setCommandModeOptions") == 0) {
|
||||
setCommandModeOptions(value);
|
||||
log(username + " updated the Command Mode Options", true);
|
||||
|
||||
@ -11,6 +11,9 @@
|
||||
#include "scheduler.h"
|
||||
#include "defines.h"
|
||||
|
||||
#include <GeographicLib/Geodesic.hpp>
|
||||
using namespace GeographicLib;
|
||||
|
||||
#include "base64.hpp"
|
||||
using namespace base64;
|
||||
|
||||
@ -148,6 +151,48 @@ void UnitsManager::deleteUnit(unsigned int ID, bool explosion, bool immediate)
|
||||
}
|
||||
}
|
||||
|
||||
Unit* UnitsManager::getClosestUnit(Unit* unit, unsigned char coalition, vector<string> categories, double &distance) {
|
||||
Unit* closestUnit = nullptr;
|
||||
distance = 0;
|
||||
|
||||
for (auto const& p : units) {
|
||||
/* Check if the units category is of the correct type */
|
||||
bool requestedCategory = false;
|
||||
for (auto const& category : categories) {
|
||||
if (p.second->getCategory().compare(category) == 0) {
|
||||
requestedCategory = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if the unit belongs to the desired coalition, is alive, and is of the category requested */
|
||||
if (requestedCategory && p.second->getCoalition() == coalition && p.second->getAlive()) {
|
||||
/* Compute the distance from the unit to the tested unit */
|
||||
double dist;
|
||||
double bearing1;
|
||||
double bearing2;
|
||||
Geodesic::WGS84().Inverse(unit->getPosition().lat, unit->getPosition().lng, p.second->getPosition().lat, p.second->getPosition().lng, dist, bearing1, bearing2);
|
||||
|
||||
/* If the closest unit has not been assigned yet, assign it to this unit */
|
||||
if (closestUnit == nullptr)
|
||||
{
|
||||
closestUnit = p.second;
|
||||
distance = dist;
|
||||
|
||||
}
|
||||
else {
|
||||
/* Check if the unit is closer than the one already selected */
|
||||
if (dist < distance) {
|
||||
closestUnit = p.second;
|
||||
distance = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return closestUnit;
|
||||
}
|
||||
|
||||
void UnitsManager::acquireControl(unsigned int ID) {
|
||||
Unit* leader = getGroupLeader(ID);
|
||||
if (leader != nullptr) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user