mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Merge branch 'python-api' into release-candidate
This commit is contained in:
commit
dca8f9189f
@ -22,6 +22,8 @@ public:
|
||||
virtual void setRacetrackLength(double newValue);
|
||||
virtual void setRacetrackAnchor(Coords newValue);
|
||||
virtual void setRacetrackBearing(double newValue);
|
||||
|
||||
virtual void setCargoWeight(double newValue);
|
||||
|
||||
protected:
|
||||
virtual void AIloop();
|
||||
|
||||
@ -538,4 +538,44 @@ public:
|
||||
private:
|
||||
const unsigned int spotID;
|
||||
const Coords destination;
|
||||
};
|
||||
|
||||
/* Set cargo weight */
|
||||
class SetCargoWeight : public Command
|
||||
{
|
||||
public:
|
||||
SetCargoWeight(unsigned int ID, double weight, function<void(void)> callback = []() {}) :
|
||||
Command(callback),
|
||||
ID(ID),
|
||||
weight(weight)
|
||||
{
|
||||
priority = CommandPriority::LOW;
|
||||
};
|
||||
virtual string getString();
|
||||
virtual unsigned int getLoad() { return 5; }
|
||||
|
||||
private:
|
||||
const unsigned int ID;
|
||||
const double weight;
|
||||
};
|
||||
|
||||
/* Register draw argument */
|
||||
class RegisterDrawArgument : public Command
|
||||
{
|
||||
public:
|
||||
RegisterDrawArgument(unsigned int ID, unsigned int argument, bool active, function<void(void)> callback = []() {}) :
|
||||
Command(callback),
|
||||
ID(ID),
|
||||
argument(argument),
|
||||
active(active)
|
||||
{
|
||||
priority = CommandPriority::LOW;
|
||||
};
|
||||
virtual string getString();
|
||||
virtual unsigned int getLoad() { return 5; }
|
||||
|
||||
private:
|
||||
const unsigned int ID;
|
||||
const unsigned int argument;
|
||||
const bool active;
|
||||
};
|
||||
@ -70,6 +70,8 @@ namespace DataIndex {
|
||||
aimMethodRange,
|
||||
acquisitionRange,
|
||||
airborne,
|
||||
cargoWeight,
|
||||
drawArguments,
|
||||
lastIndex,
|
||||
endOfData = 255
|
||||
};
|
||||
@ -159,6 +161,11 @@ namespace DataTypes {
|
||||
unsigned int ID = 0;
|
||||
unsigned char detectionMethod = 0;
|
||||
};
|
||||
|
||||
struct DrawArgument {
|
||||
unsigned int argument = 0;
|
||||
double value = 0.0;
|
||||
};
|
||||
}
|
||||
#pragma pack(pop)
|
||||
|
||||
@ -167,6 +174,7 @@ bool operator==(const DataTypes::Radio& lhs, const DataTypes::Radio& rhs);
|
||||
bool operator==(const DataTypes::GeneralSettings& lhs, const DataTypes::GeneralSettings& rhs);
|
||||
bool operator==(const DataTypes::Ammo& lhs, const DataTypes::Ammo& rhs);
|
||||
bool operator==(const DataTypes::Contact& lhs, const DataTypes::Contact& rhs);
|
||||
bool operator==(const DataTypes::DrawArgument& lhs, const DataTypes::DrawArgument& rhs);
|
||||
|
||||
struct SpawnOptions {
|
||||
string unitType;
|
||||
|
||||
@ -19,6 +19,7 @@ public:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void setFrameRate(double newFrameRate) { frameRate = newFrameRate; }
|
||||
|
||||
@ -130,9 +130,11 @@ public:
|
||||
virtual void setAcquisitionRange(double newValue) { updateValue(acquisitionRange, newValue, DataIndex::acquisitionRange); }
|
||||
virtual void setRadarState(bool newValue) { updateValue(radarState, newValue, DataIndex::radarState); }
|
||||
virtual void setAirborne(bool newValue) { updateValue(airborne, newValue, DataIndex::airborne); }
|
||||
virtual void setCargoWeight(double newValue) { updateValue(cargoWeight, newValue, DataIndex::cargoWeight); }
|
||||
virtual void setDrawArguments(vector<DataTypes::DrawArgument> newValue);
|
||||
|
||||
/********** Getters **********/
|
||||
virtual string getCategory() { return category; };
|
||||
virtual string getCategory() { return category; }
|
||||
virtual bool getAlive() { return alive; }
|
||||
virtual unsigned char getAlarmState() { return alarmState; }
|
||||
virtual bool getHuman() { return human; }
|
||||
@ -197,6 +199,8 @@ public:
|
||||
virtual double getAcquisitionRange() { return acquisitionRange; }
|
||||
virtual bool getRadarState() { return radarState; }
|
||||
virtual bool getAirborne() { return airborne; }
|
||||
virtual double getCargoWeight() { return cargoWeight; }
|
||||
virtual vector<DataTypes::DrawArgument> getDrawArguments() { return drawArguments; }
|
||||
|
||||
protected:
|
||||
unsigned int ID;
|
||||
@ -267,6 +271,8 @@ protected:
|
||||
double aimMethodRange = 0;
|
||||
double acquisitionRange = 0;
|
||||
bool airborne = false;
|
||||
double cargoWeight = 0;
|
||||
vector<DataTypes::DrawArgument> drawArguments;
|
||||
|
||||
/********** Other **********/
|
||||
unsigned int taskCheckCounter = 0;
|
||||
|
||||
@ -428,4 +428,14 @@ void AirUnit::setRacetrackBearing(double newRacetrackBearing) {
|
||||
|
||||
triggerUpdate(DataIndex::racetrackBearing);
|
||||
}
|
||||
}
|
||||
|
||||
void AirUnit::setCargoWeight(double newCargoWeight) {
|
||||
if (cargoWeight != newCargoWeight) {
|
||||
cargoWeight = newCargoWeight;
|
||||
triggerUpdate(DataIndex::cargoWeight);
|
||||
|
||||
Command* command = dynamic_cast<Command*>(new SetCargoWeight(this->ID, cargoWeight));
|
||||
scheduler->appendCommand(command);
|
||||
}
|
||||
}
|
||||
@ -318,4 +318,27 @@ string DeleteSpot::getString()
|
||||
commandSS << "Olympus.deleteSpot, "
|
||||
<< spotID;
|
||||
return commandSS.str();
|
||||
}
|
||||
|
||||
/* SetCargoWeight command */
|
||||
string SetCargoWeight::getString()
|
||||
{
|
||||
std::ostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
commandSS << "Olympus.setCargoWeight, "
|
||||
<< ID << ", "
|
||||
<< weight;
|
||||
return commandSS.str();
|
||||
}
|
||||
|
||||
/* RegisterDrawArgument command */
|
||||
string RegisterDrawArgument::getString()
|
||||
{
|
||||
std::ostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
commandSS << "Olympus.registerDrawArgument, "
|
||||
<< ID << ", "
|
||||
<< argument << ", "
|
||||
<< active;
|
||||
return commandSS.str();
|
||||
}
|
||||
@ -12,19 +12,24 @@ bool operator==(const DataTypes::Radio& lhs, const DataTypes::Radio& rhs)
|
||||
|
||||
bool operator==(const DataTypes::GeneralSettings& lhs, const DataTypes::GeneralSettings& rhs)
|
||||
{
|
||||
return lhs.prohibitAA == rhs.prohibitAA && lhs.prohibitAfterburner == rhs.prohibitAfterburner && lhs.prohibitAG == rhs.prohibitAG &&
|
||||
return lhs.prohibitAA == rhs.prohibitAA && lhs.prohibitAfterburner == rhs.prohibitAfterburner && lhs.prohibitAG == rhs.prohibitAG &&
|
||||
lhs.prohibitAirWpn == rhs.prohibitAirWpn && lhs.prohibitJettison == rhs.prohibitJettison;
|
||||
}
|
||||
|
||||
bool operator==(const DataTypes::Ammo& lhs, const DataTypes::Ammo& rhs)
|
||||
{
|
||||
return lhs.category == rhs.category && lhs.guidance == rhs.guidance && lhs.missileCategory == rhs.missileCategory &&
|
||||
return lhs.category == rhs.category && lhs.guidance == rhs.guidance && lhs.missileCategory == rhs.missileCategory &&
|
||||
lhs.quantity == rhs.quantity && strcmp(lhs.name, rhs.name) == 0;
|
||||
}
|
||||
|
||||
bool operator==(const DataTypes::DrawArgument& lhs, const DataTypes::DrawArgument& rhs)
|
||||
{
|
||||
return lhs.argument == rhs.argument && lhs.value == rhs.value;
|
||||
}
|
||||
|
||||
bool operator==(const DataTypes::Contact& lhs, const DataTypes::Contact& rhs)
|
||||
{
|
||||
return lhs.detectionMethod == rhs.detectionMethod && lhs.ID == rhs.ID;
|
||||
return lhs.detectionMethod == rhs.detectionMethod && lhs.ID == rhs.ID;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -168,6 +168,12 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
string WP = to_string(i);
|
||||
double lat = path[i][L"lat"].as_double();
|
||||
double lng = path[i][L"lng"].as_double();
|
||||
if (path[i].has_number_field(L"threshold")) {
|
||||
double threshold = path[i][L"threshold"].as_double();
|
||||
Coords dest; dest.lat = lat; dest.lng = lng; dest.threshold = threshold;
|
||||
newPath.push_back(dest);
|
||||
continue;
|
||||
}
|
||||
Coords dest; dest.lat = lat; dest.lng = lng;
|
||||
newPath.push_back(dest);
|
||||
}
|
||||
@ -821,6 +827,31 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
unitsManager->loadDatabases();
|
||||
}
|
||||
/************************/
|
||||
else if (key.compare("setCargoWeight") == 0)
|
||||
{
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
Unit* unit = unitsManager->getUnit(ID);
|
||||
if (unit != nullptr) {
|
||||
double weight = value[L"weight"].as_double();
|
||||
unit->setCargoWeight(weight);
|
||||
log(username + " set weight to unit " + unit->getUnitName() + "(" + unit->getName() + "), " + to_string(weight), true);
|
||||
}
|
||||
}
|
||||
/************************/
|
||||
else if (key.compare("registerDrawArgument") == 0)
|
||||
{
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
Unit* unit = unitsManager->getUnit(ID);
|
||||
if (unit != nullptr) {
|
||||
int argument = value[L"argument"].as_integer();
|
||||
bool active = value[L"active"].as_bool();
|
||||
|
||||
command = dynamic_cast<Command*>(new RegisterDrawArgument(ID, argument, active));
|
||||
|
||||
log(username + " registered draw argument " + to_string(argument) + " for unit " + unit->getUnitName() + "(" + unit->getName() + "), value:" + to_string(active), true);
|
||||
}
|
||||
}
|
||||
/************************/
|
||||
else
|
||||
{
|
||||
log("Unknown command: " + key);
|
||||
|
||||
@ -128,6 +128,20 @@ void Unit::update(json::value json, double dt)
|
||||
setAmmo(ammo);
|
||||
}
|
||||
|
||||
if (json.has_object_field(L"drawArguments")) {
|
||||
vector<DataTypes::DrawArgument> drawArguments;
|
||||
for (auto const& el : json[L"drawArguments"].as_object()) {
|
||||
DataTypes::DrawArgument drawArgumentItem;
|
||||
auto drawArgumentJson = el.second;
|
||||
if (drawArgumentJson.has_number_field(L"argument"))
|
||||
drawArgumentItem.argument = drawArgumentJson[L"argument"].as_number().to_uint32();
|
||||
if (drawArgumentJson.has_number_field(L"value"))
|
||||
drawArgumentItem.value = drawArgumentJson[L"value"].as_number().to_double();
|
||||
drawArguments.push_back(drawArgumentItem);
|
||||
}
|
||||
setDrawArguments(drawArguments);
|
||||
}
|
||||
|
||||
if (json.has_object_field(L"contacts")) {
|
||||
vector<DataTypes::Contact> contacts;
|
||||
for (auto const& el : json[L"contacts"].as_object()) {
|
||||
@ -330,6 +344,8 @@ void Unit::getData(stringstream& ss, unsigned long long time)
|
||||
case DataIndex::aimMethodRange: appendNumeric(ss, datumIndex, aimMethodRange); break;
|
||||
case DataIndex::acquisitionRange: appendNumeric(ss, datumIndex, acquisitionRange); break;
|
||||
case DataIndex::airborne: appendNumeric(ss, datumIndex, airborne); break;
|
||||
case DataIndex::cargoWeight: appendNumeric(ss, datumIndex, cargoWeight); break;
|
||||
case DataIndex::drawArguments: appendVector(ss, datumIndex, drawArguments); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -701,6 +717,24 @@ void Unit::setGeneralSettings(DataTypes::GeneralSettings newGeneralSettings, boo
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::setDrawArguments(vector<DataTypes::DrawArgument> newDrawArguments)
|
||||
{
|
||||
if (drawArguments.size() == newDrawArguments.size()) {
|
||||
bool equal = true;
|
||||
for (int i = 0; i < drawArguments.size(); i++) {
|
||||
if (drawArguments.at(i) != newDrawArguments.at(i))
|
||||
{
|
||||
equal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (equal)
|
||||
return;
|
||||
}
|
||||
drawArguments = newDrawArguments;
|
||||
triggerUpdate(DataIndex::drawArguments);
|
||||
}
|
||||
|
||||
void Unit::setDesiredSpeed(double newDesiredSpeed)
|
||||
{
|
||||
if (desiredSpeed != newDesiredSpeed) {
|
||||
@ -767,6 +801,7 @@ void Unit::goToDestination(string enrouteTask)
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: if the current active path has a threshold set, that value will be used instead of the passed one
|
||||
bool Unit::isDestinationReached(double threshold)
|
||||
{
|
||||
if (activeDestination != NULL)
|
||||
@ -776,7 +811,7 @@ bool Unit::isDestinationReached(double threshold)
|
||||
{
|
||||
double dist = 0;
|
||||
Geodesic::WGS84().Inverse(p->getPosition().lat, p->getPosition().lng, activeDestination.lat, activeDestination.lng, dist);
|
||||
if (dist < threshold)
|
||||
if (dist < (activeDestination.threshold == 0? threshold: activeDestination.threshold))
|
||||
{
|
||||
log(unitName + " destination reached");
|
||||
return true;
|
||||
|
||||
@ -6,6 +6,7 @@ struct Coords {
|
||||
double lat = 0;
|
||||
double lng = 0;
|
||||
double alt = 0;
|
||||
double threshold = 0; // used for proximity checks only, not part of the actual coordinates
|
||||
};
|
||||
|
||||
struct Offset {
|
||||
|
||||
@ -64,9 +64,9 @@ std::string random_string(size_t length)
|
||||
return str;
|
||||
}
|
||||
|
||||
bool operator== (const Coords& a, const Coords& b) { return a.lat == b.lat && a.lng == b.lng && a.alt == b.alt; }
|
||||
bool operator== (const Coords& a, const Coords& b) { return a.lat == b.lat && a.lng == b.lng && a.alt == b.alt && a.threshold == b.threshold; }
|
||||
bool operator!= (const Coords& a, const Coords& b) { return !(a == b); }
|
||||
bool operator== (const Coords& a, const double& b) { return a.lat == b && a.lng == b && a.alt == b; }
|
||||
bool operator== (const Coords& a, const double& b) { return a.lat == b && a.lng == b && a.alt == b && a.threshold == b; }
|
||||
bool operator!= (const Coords& a, const double& b) { return !(a == b); }
|
||||
|
||||
bool operator== (const Offset& a, const Offset& b) { return a.x == b.x && a.y == b.y && a.z == b.z; }
|
||||
|
||||
6
frontend/react/.vscode/tasks.json
vendored
6
frontend/react/.vscode/tasks.json
vendored
@ -1,12 +1,6 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "check-setup",
|
||||
"type": "shell",
|
||||
"command": "cd .. ; ./check_setup.bat",
|
||||
"isBackground": false
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "dev",
|
||||
|
||||
@ -547,6 +547,8 @@ export enum DataIndexes {
|
||||
aimMethodRange,
|
||||
acquisitionRange,
|
||||
airborne,
|
||||
cargoWeight,
|
||||
drawingArguments,
|
||||
endOfData = 255,
|
||||
}
|
||||
|
||||
|
||||
@ -219,6 +219,11 @@ export interface Offset {
|
||||
z: number;
|
||||
}
|
||||
|
||||
export interface DrawingArgument {
|
||||
argument: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface UnitData {
|
||||
category: string;
|
||||
markerCategory: string;
|
||||
@ -286,6 +291,8 @@ export interface UnitData {
|
||||
aimMethodRange: number;
|
||||
acquisitionRange: number;
|
||||
airborne: boolean;
|
||||
cargoWeight: number;
|
||||
drawingArguments: DrawingArgument[];
|
||||
}
|
||||
|
||||
export interface LoadoutItemBlueprint {
|
||||
|
||||
18
frontend/react/src/map/latlng.ts
Normal file
18
frontend/react/src/map/latlng.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import * as L from "leaflet";
|
||||
|
||||
export class LatLng extends L.LatLng {
|
||||
threshold: number;
|
||||
|
||||
constructor(lat: number, lng: number, alt: number, threshold: number) {
|
||||
super(lat, lng, alt);
|
||||
this.threshold = threshold;
|
||||
}
|
||||
|
||||
setThreshold(threshold: number) {
|
||||
this.threshold = threshold;
|
||||
}
|
||||
|
||||
getThreshold() {
|
||||
return this.threshold;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN } from "../interfaces";
|
||||
import { Ammo, Contact, DrawingArgument, GeneralSettings, Offset, Radio, TACAN } from "../interfaces";
|
||||
|
||||
export class DataExtractor {
|
||||
#seekPosition = 0;
|
||||
@ -58,7 +58,9 @@ export class DataExtractor {
|
||||
}
|
||||
|
||||
extractLatLng() {
|
||||
return new LatLng(this.extractFloat64(), this.extractFloat64(), this.extractFloat64());
|
||||
let latlng = new LatLng(this.extractFloat64(), this.extractFloat64(), this.extractFloat64());
|
||||
let threshold = this.extractFloat64();
|
||||
return latlng;
|
||||
}
|
||||
|
||||
extractFromBitmask(bitmask: number, position: number) {
|
||||
@ -104,6 +106,14 @@ export class DataExtractor {
|
||||
return value;
|
||||
}
|
||||
|
||||
extractDrawingArgument() {
|
||||
const value: DrawingArgument = {
|
||||
argument: this.extractUInt32(),
|
||||
value: this.extractFloat64(),
|
||||
};
|
||||
return value;
|
||||
}
|
||||
|
||||
extractGeneralSettings() {
|
||||
const value: GeneralSettings = {
|
||||
prohibitJettison: this.extractBool(),
|
||||
@ -159,4 +169,13 @@ export class DataExtractor {
|
||||
};
|
||||
return value;
|
||||
}
|
||||
|
||||
extractDrawingArguments() {
|
||||
const value: DrawingArgument[] = [];
|
||||
const size = this.extractUInt16();
|
||||
for (let idx = 0; idx < size; idx++) {
|
||||
value.push(this.extractDrawingArgument());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map, Point, LeafletMouseEvent, DomEvent, DomUtil, Circle } from "leaflet";
|
||||
import { LatLng, Polyline, DivIcon, CircleMarker, Map, Point, DomEvent } from "leaflet";
|
||||
import { getApp } from "../olympusapp";
|
||||
import {
|
||||
enumToCoalition,
|
||||
@ -54,7 +54,7 @@ import {
|
||||
} from "../constants/constants";
|
||||
import { DataExtractor } from "../server/dataextractor";
|
||||
import { Weapon } from "../weapon/weapon";
|
||||
import { AlarmState, Ammo, Contact, GeneralSettings, LoadoutBlueprint, ObjectIconOptions, Offset, Radio, TACAN, UnitBlueprint, UnitData } from "../interfaces";
|
||||
import { AlarmState, Ammo, Contact, DrawingArgument, GeneralSettings, LoadoutBlueprint, ObjectIconOptions, Offset, Radio, TACAN, UnitBlueprint, UnitData } from "../interfaces";
|
||||
import { RangeCircle } from "../map/rangecircle";
|
||||
import { Group } from "./group";
|
||||
import { ContextActionSet } from "./contextactionset";
|
||||
@ -159,6 +159,8 @@ export abstract class Unit extends CustomMarker {
|
||||
#racetrackAnchor: LatLng = new LatLng(0, 0);
|
||||
#racetrackBearing: number = 0;
|
||||
#airborne: boolean = false;
|
||||
#cargoWeight: number = 0;
|
||||
#drawingArguments: DrawingArgument[] = [];
|
||||
|
||||
/* Other members used to draw the unit, mostly ancillary stuff like targets, ranges and so on */
|
||||
#blueprint: UnitBlueprint | null = null;
|
||||
@ -406,6 +408,12 @@ export abstract class Unit extends CustomMarker {
|
||||
getAirborne() {
|
||||
return this.#airborne;
|
||||
}
|
||||
getCargoWeight() {
|
||||
return this.#cargoWeight;
|
||||
}
|
||||
getDrawingArguments() {
|
||||
return this.#drawingArguments;
|
||||
}
|
||||
|
||||
static getConstructor(type: string) {
|
||||
if (type === "GroundUnit") return GroundUnit;
|
||||
@ -797,6 +805,12 @@ export abstract class Unit extends CustomMarker {
|
||||
case DataIndexes.airborne:
|
||||
this.#airborne = dataExtractor.extractBool();
|
||||
break;
|
||||
case DataIndexes.cargoWeight:
|
||||
this.#cargoWeight = dataExtractor.extractFloat64();
|
||||
break;
|
||||
case DataIndexes.drawingArguments:
|
||||
this.#drawingArguments = dataExtractor.extractDrawingArguments();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -920,6 +934,8 @@ export abstract class Unit extends CustomMarker {
|
||||
aimMethodRange: this.#aimMethodRange,
|
||||
acquisitionRange: this.#acquisitionRange,
|
||||
airborne: this.#airborne,
|
||||
cargoWeight: this.#cargoWeight,
|
||||
drawingArguments: this.#drawingArguments,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@ Olympus.unitIndex = 0 -- Counter used to spread the computational load of data
|
||||
Olympus.unitStep = 50 -- Max number of units that get updated each cycle
|
||||
Olympus.units = {} -- Table holding references to all the currently existing units
|
||||
Olympus.unitsInitialLife = {} -- getLife0 returns 0 for ships, so we need to store the initial life of units
|
||||
Olympus.drawArguments = {} -- Table that sets what drawArguments to read for each unit
|
||||
|
||||
Olympus.weaponIndex = 0 -- Counter used to spread the computational load of data retrievial from DCS
|
||||
Olympus.weaponStep = 50 -- Max number of weapons that get updated each cycle
|
||||
@ -1087,10 +1088,38 @@ function Olympus.setOnOff(groupName, onOff)
|
||||
end
|
||||
end
|
||||
|
||||
-- Get the unit description
|
||||
function getUnitDescription(unit)
|
||||
return unit:getDescr()
|
||||
end
|
||||
|
||||
-- Set the unit cargo weight
|
||||
function Olympus.setCargoWeight(ID, weight)
|
||||
Olympus.debug("Olympus.setCargoWeight " .. ID .. " " .. tostring(weight), 2)
|
||||
|
||||
local unit = Olympus.getUnitByID(ID)
|
||||
if unit ~= nil and unit:isExist() then
|
||||
trigger.action.setUnitInternalCargo(unit:getName(), weight)
|
||||
end
|
||||
end
|
||||
|
||||
-- Register a drawArgument to be read for a unit
|
||||
function Olympus.registerDrawArgument(ID, argument, active)
|
||||
Olympus.debug("Olympus.registerDrawArgument " .. ID .. " " .. tostring(argument) .. " " .. tostring(active), 2)
|
||||
|
||||
-- Create the table if it does not exist
|
||||
if Olympus.drawArguments[ID] == nil then
|
||||
Olympus.drawArguments[ID] = {}
|
||||
end
|
||||
|
||||
-- Set the draw argument to true or false
|
||||
if active then
|
||||
Olympus.drawArguments[ID][argument] = true
|
||||
else
|
||||
Olympus.drawArguments[ID][argument] = false
|
||||
end
|
||||
end
|
||||
|
||||
-- This function gets the navpoints from the DCS mission
|
||||
function Olympus.getNavPoints()
|
||||
local function extract_tag(str)
|
||||
@ -1293,6 +1322,20 @@ function Olympus.setUnitsData(arg, time)
|
||||
table["radarState"] = false
|
||||
end
|
||||
end ]]
|
||||
|
||||
-- Read the draw arguments
|
||||
local drawArguments = {}
|
||||
if Olympus.drawArguments[ID] ~= nil then
|
||||
for argument, active in pairs(Olympus.drawArguments[ID]) do
|
||||
if active then
|
||||
drawArguments[#drawArguments + 1] = {
|
||||
argument = argument,
|
||||
value = unit:getDrawArgumentValue(argument)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
table["drawArguments"] = drawArguments
|
||||
|
||||
local group = unit:getGroup()
|
||||
if group ~= nil then
|
||||
|
||||
40
scripts/python/API/.vscode/launch.json
vendored
40
scripts/python/API/.vscode/launch.json
vendored
@ -5,18 +5,50 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Voice control",
|
||||
"name": "Python: Main",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "voice_control.py",
|
||||
"program": "${workspaceFolder}/main.py",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": false,
|
||||
},
|
||||
{
|
||||
"name": "Test bed",
|
||||
"name": "Example voice control",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "testbed.py",
|
||||
"program": "example_voice_control.py",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": false,
|
||||
},
|
||||
{
|
||||
"name": "Example disembarked infantry",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "example_disembarked_infantry.py",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": false,
|
||||
},
|
||||
{
|
||||
"name": "Example set cargo weight",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "example_set_cargo_weight.py",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": false,
|
||||
},
|
||||
{
|
||||
"name": "Example draw argument",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "example_draw_argument.py",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": false,
|
||||
},
|
||||
{
|
||||
"name": "Example precise movement",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "example_precise_movement.py",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": false,
|
||||
}
|
||||
|
||||
@ -119,7 +119,122 @@ class API:
|
||||
signal.signal(signal.SIGINT, signal_handler) # Ctrl+C
|
||||
if hasattr(signal, 'SIGTERM'):
|
||||
signal.signal(signal.SIGTERM, signal_handler) # Termination signal
|
||||
|
||||
async def _check_command_executed(self, command_hash: str, execution_callback, wait_for_result: bool, max_wait_time: int = 60):
|
||||
"""
|
||||
Check if a command has been executed by polling the API.
|
||||
"""
|
||||
start_time = time.time()
|
||||
while True:
|
||||
response = self._get(f"commands?commandHash={command_hash}")
|
||||
if response.status_code == 200:
|
||||
try:
|
||||
data = response.json()
|
||||
if data.get("commandExecuted") == True and (data.get("commandResult") is not None or (not wait_for_result)):
|
||||
self.logger.info(f"Command {command_hash} executed successfully, command result: {data.get('commandResult')}")
|
||||
if execution_callback:
|
||||
await execution_callback(data.get("commandResult"))
|
||||
break
|
||||
elif data.get("status") == "failed":
|
||||
self.logger.error(f"Command {command_hash} failed to execute.")
|
||||
break
|
||||
except ValueError:
|
||||
self.logger.error("Failed to parse JSON response")
|
||||
if time.time() - start_time > max_wait_time:
|
||||
self.logger.warning(f"Timeout: Command {command_hash} did not complete within {max_wait_time} seconds.")
|
||||
break
|
||||
await asyncio.sleep(1)
|
||||
|
||||
async def _run_callback_async(self, callback, *args):
|
||||
"""
|
||||
Run a callback asynchronously, handling both sync and async callbacks.
|
||||
"""
|
||||
try:
|
||||
if asyncio.iscoroutinefunction(callback):
|
||||
await callback(*args)
|
||||
else:
|
||||
callback(*args)
|
||||
except Exception as e:
|
||||
# Log the error but don't crash the update process
|
||||
self.logger.error(f"Error in callback: {e}")
|
||||
|
||||
async def _run_async(self):
|
||||
"""
|
||||
Async implementation of the API service loop.
|
||||
"""
|
||||
# Setup signal handlers for graceful shutdown
|
||||
self._setup_signal_handlers()
|
||||
|
||||
# Here you can add any initialization logic if needed
|
||||
self.logger.info("API started")
|
||||
self.logger.info("Press Ctrl+C to stop gracefully")
|
||||
|
||||
self.running = True
|
||||
self.should_stop = False
|
||||
|
||||
# Call the startup callback if registered
|
||||
if self.on_startup_callback:
|
||||
try:
|
||||
await self._run_callback_async(self.on_startup_callback, self)
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error in startup callback: {e}")
|
||||
|
||||
try:
|
||||
while not self.should_stop:
|
||||
# Update units from the last update timestamp
|
||||
self.update_units(self.units_update_timestamp)
|
||||
|
||||
if self.on_update_callback:
|
||||
await self._run_callback_async(self.on_update_callback, self)
|
||||
await asyncio.sleep(self.interval)
|
||||
except KeyboardInterrupt:
|
||||
self.logger.info("Keyboard interrupt received")
|
||||
self.stop()
|
||||
finally:
|
||||
self.logger.info("API stopped")
|
||||
self.running = False
|
||||
|
||||
def register_on_update_callback(self, callback):
|
||||
"""
|
||||
Register a callback function to be called on each update.
|
||||
|
||||
Args:
|
||||
callback (function): The function to call on update. Can be sync or async.
|
||||
The function should accept a single argument, which is the API instance.
|
||||
"""
|
||||
self.on_update_callback = callback
|
||||
|
||||
def unregister_on_update_callback(self):
|
||||
"""
|
||||
Unregister the callback function that is called on each update.
|
||||
"""
|
||||
self.on_update_callback = None
|
||||
|
||||
def register_on_startup_callback(self, callback):
|
||||
"""
|
||||
Register a callback function to be called on startup.
|
||||
Args:
|
||||
callback (function): The function to call on startup. Can be sync or async.
|
||||
The function should accept a single argument, which is the API instance.
|
||||
"""
|
||||
self.on_startup_callback = callback
|
||||
|
||||
def unregister_on_startup_callback(self):
|
||||
"""
|
||||
Unregister the callback function that is called on startup.
|
||||
"""
|
||||
self.on_startup_callback = None
|
||||
|
||||
def set_log_level(self, level):
|
||||
"""
|
||||
Set the logging level for the API.
|
||||
|
||||
Args:
|
||||
level: Logging level (e.g., logging.DEBUG, logging.INFO, logging.WARNING, self.logger.error)
|
||||
"""
|
||||
self.logger.setLevel(level)
|
||||
self.logger.info(f"Log level set to {logging.getLevelName(level)}")
|
||||
|
||||
def get_units(self):
|
||||
"""
|
||||
Get all units from the API. Notice that if the API is not running, update_units() must be manually called first.
|
||||
@ -170,8 +285,7 @@ class API:
|
||||
self.logger.error("Failed to parse JSON response")
|
||||
else:
|
||||
self.logger.error(f"Failed to fetch units: {response.status_code} - {response.text}")
|
||||
|
||||
|
||||
|
||||
def update_logs(self, time = 0):
|
||||
"""
|
||||
Fetch the logs from the API.
|
||||
@ -192,7 +306,7 @@ class API:
|
||||
else:
|
||||
self.logger.error(f"Failed to fetch logs: {response.status_code} - {response.text}")
|
||||
|
||||
def spawn_aircrafts(self, units: list[UnitSpawnTable], coalition: str, airbaseName: str, country: str, immediate: bool, spawnPoints: int = 0):
|
||||
def spawn_aircrafts(self, units: list[UnitSpawnTable], coalition: str, airbaseName: str, country: str, immediate: bool, spawnPoints: int = 0, execution_callback=None):
|
||||
"""
|
||||
Spawn aircraft units at the specified location or airbase.
|
||||
Args:
|
||||
@ -202,6 +316,7 @@ class API:
|
||||
country (str): The country of the units.
|
||||
immediate (bool): Whether to spawn the units immediately or not, overriding the scheduler.
|
||||
spawnPoints (int): Amount of spawn points to use, default is 0.
|
||||
execution_callback (function): An optional async callback function to execute after the command is processed.
|
||||
"""
|
||||
command = {
|
||||
"units": [unit.toJSON() for unit in units],
|
||||
@ -214,7 +329,21 @@ class API:
|
||||
data = { "spawnAircrafts": command }
|
||||
response = self._put(data)
|
||||
|
||||
def spawn_helicopters(self, units: list[UnitSpawnTable], coalition: str, airbaseName: str, country: str, immediate: bool, spawnPoints: int = 0):
|
||||
# Parse the response as JSON if callback is provided
|
||||
if execution_callback:
|
||||
try:
|
||||
response_data = response.json()
|
||||
command_hash = response_data.get("commandHash", None)
|
||||
if command_hash:
|
||||
self.logger.info(f"Aircraft spawned successfully. Command Hash: {command_hash}")
|
||||
# Start a background task to check if the command was executed
|
||||
asyncio.create_task(self._check_command_executed(command_hash, execution_callback, wait_for_result=True))
|
||||
else:
|
||||
self.logger.error("Command hash not found in response")
|
||||
except ValueError:
|
||||
self.logger.error("Failed to parse JSON response")
|
||||
|
||||
def spawn_helicopters(self, units: list[UnitSpawnTable], coalition: str, airbaseName: str, country: str, immediate: bool, spawnPoints: int = 0, execution_callback=None):
|
||||
"""
|
||||
Spawn helicopter units at the specified location or airbase.
|
||||
Args:
|
||||
@ -224,6 +353,7 @@ class API:
|
||||
country (str): The country of the units.
|
||||
immediate (bool): Whether to spawn the units immediately or not, overriding the scheduler.
|
||||
spawnPoints (int): Amount of spawn points to use, default is 0.
|
||||
execution_callback (function): An optional async callback function to execute after the command is processed.
|
||||
"""
|
||||
command = {
|
||||
"units": [unit.toJSON() for unit in units],
|
||||
@ -236,6 +366,20 @@ class API:
|
||||
data = { "spawnHelicopters": command }
|
||||
response = self._put(data)
|
||||
|
||||
# Parse the response as JSON if callback is provided
|
||||
if execution_callback:
|
||||
try:
|
||||
response_data = response.json()
|
||||
command_hash = response_data.get("commandHash", None)
|
||||
if command_hash:
|
||||
self.logger.info(f"Helicopters spawned successfully. Command Hash: {command_hash}")
|
||||
# Start a background task to check if the command was executed
|
||||
asyncio.create_task(self._check_command_executed(command_hash, execution_callback, wait_for_result=True))
|
||||
else:
|
||||
self.logger.error("Command hash not found in response")
|
||||
except ValueError:
|
||||
self.logger.error("Failed to parse JSON response")
|
||||
|
||||
def spawn_ground_units(self, units: list[UnitSpawnTable], coalition: str, country: str, immediate: bool, spawnPoints: int, execution_callback):
|
||||
"""
|
||||
Spawn ground units at the specified location.
|
||||
@ -272,32 +416,7 @@ class API:
|
||||
except ValueError:
|
||||
self.logger.error("Failed to parse JSON response")
|
||||
|
||||
async def _check_command_executed(self, command_hash: str, execution_callback, wait_for_result: bool, max_wait_time: int = 60):
|
||||
"""
|
||||
Check if a command has been executed by polling the API.
|
||||
"""
|
||||
start_time = time.time()
|
||||
while True:
|
||||
response = self._get(f"commands?commandHash={command_hash}")
|
||||
if response.status_code == 200:
|
||||
try:
|
||||
data = response.json()
|
||||
if data.get("commandExecuted") == True and (data.get("commandResult") is not None or (not wait_for_result)):
|
||||
self.logger.info(f"Command {command_hash} executed successfully, command result: {data.get('commandResult')}")
|
||||
if execution_callback:
|
||||
await execution_callback(data.get("commandResult"))
|
||||
break
|
||||
elif data.get("status") == "failed":
|
||||
self.logger.error(f"Command {command_hash} failed to execute.")
|
||||
break
|
||||
except ValueError:
|
||||
self.logger.error("Failed to parse JSON response")
|
||||
if time.time() - start_time > max_wait_time:
|
||||
self.logger.warning(f"Timeout: Command {command_hash} did not complete within {max_wait_time} seconds.")
|
||||
break
|
||||
await asyncio.sleep(1)
|
||||
|
||||
def spawn_navy_units(self, units: list[UnitSpawnTable], coalition: str, country: str, immediate: bool, spawnPoints: int = 0):
|
||||
def spawn_navy_units(self, units: list[UnitSpawnTable], coalition: str, country: str, immediate: bool, spawnPoints: int = 0, execution_callback=None):
|
||||
"""
|
||||
Spawn navy units at the specified location.
|
||||
Args:
|
||||
@ -306,6 +425,7 @@ class API:
|
||||
country (str): The country of the units.
|
||||
immediate (bool): Whether to spawn the units immediately or not, overriding the scheduler.
|
||||
spawnPoints (int): Amount of spawn points to use, default is 0.
|
||||
execution_callback (function): An optional async callback function to execute after the command is processed.
|
||||
"""
|
||||
command = {
|
||||
"units": [unit.toJSON() for unit in units],
|
||||
@ -316,6 +436,20 @@ class API:
|
||||
}
|
||||
data = { "spawnNavyUnits": command }
|
||||
response = self._put(data)
|
||||
|
||||
# Parse the response as JSON if callback is provided
|
||||
if execution_callback:
|
||||
try:
|
||||
response_data = response.json()
|
||||
command_hash = response_data.get("commandHash", None)
|
||||
if command_hash:
|
||||
self.logger.info(f"Navy units spawned successfully. Command Hash: {command_hash}")
|
||||
# Start a background task to check if the command was executed
|
||||
asyncio.create_task(self._check_command_executed(command_hash, execution_callback, wait_for_result=True))
|
||||
else:
|
||||
self.logger.error("Command hash not found in response")
|
||||
except ValueError:
|
||||
self.logger.error("Failed to parse JSON response")
|
||||
|
||||
def create_radio_listener(self):
|
||||
"""
|
||||
@ -327,55 +461,6 @@ class API:
|
||||
from radio.radio_listener import RadioListener
|
||||
return RadioListener(self, "localhost", self.config.get("audio").get("WSPort"))
|
||||
|
||||
def register_on_update_callback(self, callback):
|
||||
"""
|
||||
Register a callback function to be called on each update.
|
||||
|
||||
Args:
|
||||
callback (function): The function to call on update. Can be sync or async.
|
||||
The function should accept a single argument, which is the API instance.
|
||||
"""
|
||||
self.on_update_callback = callback
|
||||
|
||||
def register_on_startup_callback(self, callback):
|
||||
"""
|
||||
Register a callback function to be called on startup.
|
||||
Args:
|
||||
callback (function): The function to call on startup. Can be sync or async.
|
||||
The function should accept a single argument, which is the API instance.
|
||||
"""
|
||||
self.on_startup_callback = callback
|
||||
|
||||
def set_log_level(self, level):
|
||||
"""
|
||||
Set the logging level for the API.
|
||||
|
||||
Args:
|
||||
level: Logging level (e.g., logging.DEBUG, logging.INFO, logging.WARNING, self.logger.error)
|
||||
"""
|
||||
self.logger.setLevel(level)
|
||||
self.logger.info(f"Log level set to {logging.getLevelName(level)}")
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stop the API service gracefully.
|
||||
"""
|
||||
self.logger.info("Stopping API service...")
|
||||
self.should_stop = True
|
||||
|
||||
async def _run_callback_async(self, callback, *args):
|
||||
"""
|
||||
Run a callback asynchronously, handling both sync and async callbacks.
|
||||
"""
|
||||
try:
|
||||
if asyncio.iscoroutinefunction(callback):
|
||||
await callback(*args)
|
||||
else:
|
||||
callback(*args)
|
||||
except Exception as e:
|
||||
# Log the error but don't crash the update process
|
||||
self.logger.error(f"Error in callback: {e}")
|
||||
|
||||
def generate_audio_message(text: str, gender: str = "male", code: str = "en-US") -> str:
|
||||
"""
|
||||
Generate a WAV file from text using Google Text-to-Speech API.
|
||||
@ -412,28 +497,6 @@ class API:
|
||||
|
||||
return file_name
|
||||
|
||||
def send_command(self, command: str):
|
||||
"""
|
||||
Send a command to the API.
|
||||
|
||||
Args:
|
||||
command (str): The command to send.
|
||||
"""
|
||||
response = self._put(command)
|
||||
if response.status_code == 200:
|
||||
self.logger.info(f"Command sent successfully: {command}")
|
||||
else:
|
||||
self.logger.error(f"Failed to send command: {response.status_code} - {response.text}")
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Start the API service.
|
||||
|
||||
This method initializes the API and starts the necessary components.
|
||||
Sets up signal handlers for graceful shutdown.
|
||||
"""
|
||||
asyncio.run(self._run_async())
|
||||
|
||||
def get_closest_units(self, coalitions: list[str], categories: list[str], position: LatLng, operate_as: str | None = None, max_number: int = 1, max_distance: float = 10000) -> list[Unit]:
|
||||
"""
|
||||
Get the closest units of a specific coalition and category to a given position.
|
||||
@ -453,7 +516,7 @@ class API:
|
||||
|
||||
# Iterate through all units and find the closest ones that match the criteria
|
||||
for unit in self.units.values():
|
||||
if unit.alive and unit.coalition in coalitions and unit.category.lower() in categories and (operate_as is None or unit.operate_as == operate_as or unit.coalition is not "neutral"):
|
||||
if unit.alive and unit.coalition in coalitions and unit.category.lower() in categories and (operate_as is None or unit.operate_as == operate_as or unit.coalition != "neutral"):
|
||||
distance = position.distance_to(unit.position)
|
||||
if distance < closest_distance:
|
||||
closest_distance = distance
|
||||
@ -468,39 +531,33 @@ class API:
|
||||
closest_units = closest_units[:max_number]
|
||||
|
||||
return closest_units
|
||||
|
||||
async def _run_async(self):
|
||||
"""
|
||||
Async implementation of the API service loop.
|
||||
"""
|
||||
# Setup signal handlers for graceful shutdown
|
||||
self._setup_signal_handlers()
|
||||
|
||||
# Here you can add any initialization logic if needed
|
||||
self.logger.info("API started")
|
||||
self.logger.info("Press Ctrl+C to stop gracefully")
|
||||
|
||||
self.running = True
|
||||
self.should_stop = False
|
||||
|
||||
# Call the startup callback if registered
|
||||
if self.on_startup_callback:
|
||||
try:
|
||||
await self._run_callback_async(self.on_startup_callback, self)
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error in startup callback: {e}")
|
||||
|
||||
try:
|
||||
while not self.should_stop:
|
||||
# Update units from the last update timestamp
|
||||
self.update_units(self.units_update_timestamp)
|
||||
|
||||
if self.on_update_callback:
|
||||
await self._run_callback_async(self.on_update_callback, self)
|
||||
await asyncio.sleep(self.interval)
|
||||
except KeyboardInterrupt:
|
||||
self.logger.info("Keyboard interrupt received")
|
||||
self.stop()
|
||||
finally:
|
||||
self.logger.info("API stopped")
|
||||
self.running = False
|
||||
def send_command(self, command: str):
|
||||
"""
|
||||
Send a command to the API.
|
||||
|
||||
Args:
|
||||
command (str): The command to send.
|
||||
"""
|
||||
response = self._put(command)
|
||||
if response.status_code == 200:
|
||||
self.logger.info(f"Command sent successfully: {command}")
|
||||
else:
|
||||
self.logger.error(f"Failed to send command: {response.status_code} - {response.text}")
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stop the API service gracefully.
|
||||
"""
|
||||
self.logger.info("Stopping API service...")
|
||||
self.should_stop = True
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Start the API service.
|
||||
|
||||
This method initializes the API and starts the necessary components.
|
||||
Sets up signal handlers for graceful shutdown.
|
||||
"""
|
||||
asyncio.run(self._run_async())
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import struct
|
||||
from typing import List
|
||||
from data.data_types import LatLng, TACAN, Radio, GeneralSettings, Ammo, Contact, Offset
|
||||
from data.data_types import DrawArgument, LatLng, TACAN, Radio, GeneralSettings, Ammo, Contact, Offset
|
||||
|
||||
class DataExtractor:
|
||||
def __init__(self, buffer: bytes):
|
||||
@ -48,6 +48,7 @@ class DataExtractor:
|
||||
lat = self.extract_float64()
|
||||
lng = self.extract_float64()
|
||||
alt = self.extract_float64()
|
||||
threshold = self.extract_float64()
|
||||
return LatLng(lat, lng, alt)
|
||||
|
||||
def extract_from_bitmask(self, bitmask: int, position: int) -> bool:
|
||||
@ -136,4 +137,14 @@ class DataExtractor:
|
||||
x=self.extract_float64(),
|
||||
y=self.extract_float64(),
|
||||
z=self.extract_float64()
|
||||
)
|
||||
)
|
||||
|
||||
def extract_draw_arguments(self) -> List[DrawArgument]:
|
||||
value = []
|
||||
size = self.extract_uint16()
|
||||
for _ in range(size):
|
||||
value.append(DrawArgument(
|
||||
argument=self.extract_uint32(),
|
||||
value=self.extract_float64()
|
||||
))
|
||||
return value
|
||||
@ -67,4 +67,6 @@ class DataIndexes(Enum):
|
||||
AIM_METHOD_RANGE = 63
|
||||
ACQUISITION_RANGE = 64
|
||||
AIRBORNE = 65
|
||||
CARGO_WEIGHT = 66
|
||||
DRAW_ARGUMENTS = 67
|
||||
END_OF_DATA = 255
|
||||
@ -8,13 +8,15 @@ class LatLng:
|
||||
lat: float
|
||||
lng: float
|
||||
alt: float
|
||||
|
||||
threshold: Optional[float] = 0 # Optional threshold for proximity checks
|
||||
|
||||
def toJSON(self):
|
||||
"""Convert LatLng to a JSON serializable dictionary."""
|
||||
return {
|
||||
"lat": self.lat,
|
||||
"lng": self.lng,
|
||||
"alt": self.alt
|
||||
"alt": self.alt,
|
||||
"threshold": self.threshold
|
||||
}
|
||||
|
||||
def project_with_bearing_and_distance(self, d, bearing):
|
||||
@ -88,4 +90,9 @@ class Contact:
|
||||
class Offset:
|
||||
x: float
|
||||
y: float
|
||||
z: float
|
||||
z: float
|
||||
|
||||
@dataclass
|
||||
class DrawArgument:
|
||||
argument: int
|
||||
value: float
|
||||
@ -5,14 +5,14 @@ from math import pi
|
||||
|
||||
# Setup a logger for the module
|
||||
import logging
|
||||
logger = logging.getLogger("TestBed")
|
||||
logger = logging.getLogger("example_disembarked_infantry")
|
||||
logger.setLevel(logging.INFO)
|
||||
handler = logging.StreamHandler()
|
||||
formatter = logging.Formatter('[%(asctime)s] %(name)s - %(levelname)s - %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
logger.addHandler(handler)
|
||||
|
||||
units_to_delete = None
|
||||
units_to_delete = []
|
||||
|
||||
#############################################################################################
|
||||
# This class represents a disembarked infantry unit that will engage in combat
|
||||
@ -28,7 +28,7 @@ class DisembarkedInfantry(Unit):
|
||||
with the closest enemy unit.
|
||||
"""
|
||||
logger.info(f"Unit {self.unit_id} is now fighting.")
|
||||
|
||||
|
||||
# Pick a random target
|
||||
target = self.pick_random_target()
|
||||
|
||||
@ -86,7 +86,7 @@ class DisembarkedInfantry(Unit):
|
||||
|
||||
# Simulate a firefight in the direction of the enemy
|
||||
firefight_destination = self.position.project_with_bearing_and_distance(30, bearing_to_enemy)
|
||||
self.simulate_fire_fight(firefight_destination.lat, firefight_destination.lng, firefight_destination.alt + 1)
|
||||
self.simulate_fire_fight(firefight_destination, firefight_destination.alt + 1)
|
||||
|
||||
await asyncio.sleep(10) # Simulate some time spent in firefight
|
||||
self.start_fighting() # Restart the fighting process
|
||||
@ -109,8 +109,8 @@ def on_api_startup(api: API):
|
||||
if unit.alive and not unit.human and unit.coalition == "blue":
|
||||
units_to_delete.append(unit)
|
||||
try:
|
||||
unit.delete_unit(False, "", True)
|
||||
unit.register_on_property_change_callback("alive", on_unit_alive_change)
|
||||
unit.delete_unit(False, "", True)
|
||||
|
||||
logger.info(f"Deleted unit: {unit}")
|
||||
except Exception as e:
|
||||
@ -175,7 +175,7 @@ def on_api_update(api: API):
|
||||
new_unit.__class__ = DisembarkedInfantry
|
||||
new_unit.start_fighting()
|
||||
|
||||
api.spawn_ground_units([spawn_table], unit.coalition, "", True, 0, lambda new_group_ID: execution_callback(new_group_ID))
|
||||
api.spawn_ground_units([spawn_table], unit.coalition, "", True, 0, execution_callback)
|
||||
logger.info(f"Spawned new unit succesfully at {spawn_position} with heading {unit.heading}")
|
||||
break
|
||||
|
||||
|
||||
31
scripts/python/API/example_draw_argument.py
Normal file
31
scripts/python/API/example_draw_argument.py
Normal file
@ -0,0 +1,31 @@
|
||||
from api import API
|
||||
|
||||
def on_api_startup(api: API):
|
||||
units = api.update_units()
|
||||
for unit in units.values():
|
||||
if unit.name == "UH-1H":
|
||||
# Register draw argument 43 for UH-1H
|
||||
unit.register_draw_argument(43)
|
||||
|
||||
def on_api_update(api: API):
|
||||
units = api.get_units()
|
||||
for unit in units.values():
|
||||
if unit.name == "UH-1H":
|
||||
print(f"Draw Arguments for {unit.name}:")
|
||||
for draw_arg in unit.draw_arguments:
|
||||
print(f" Argument: {draw_arg.argument}, Value: {draw_arg.value}")
|
||||
|
||||
##############################################################################################
|
||||
# Main entry point for the script. It registers the callbacks and starts the API.
|
||||
##############################################################################################
|
||||
if __name__ == "__main__":
|
||||
# Initialize the API
|
||||
api = API()
|
||||
|
||||
# Register the callbacks
|
||||
api.register_on_update_callback(on_api_update)
|
||||
api.register_on_startup_callback(on_api_startup)
|
||||
|
||||
# Start the API, this will run forever until stopped
|
||||
api.run()
|
||||
|
||||
24
scripts/python/API/example_precise_movement.py
Normal file
24
scripts/python/API/example_precise_movement.py
Normal file
@ -0,0 +1,24 @@
|
||||
from api import API
|
||||
|
||||
def on_api_startup(api: API):
|
||||
units = api.update_units()
|
||||
for unit in units.values():
|
||||
if unit.name == "Infantry AK Ins":
|
||||
current_pos = unit.position
|
||||
next_pos = current_pos.project_with_bearing_and_distance(20, 0) # Move 20 meters north
|
||||
next_pos.threshold = 2 # Set threshold to 1 meter, very precise
|
||||
unit.set_path([next_pos])
|
||||
|
||||
##############################################################################################
|
||||
# Main entry point for the script. It registers the callbacks and starts the API.
|
||||
##############################################################################################
|
||||
if __name__ == "__main__":
|
||||
# Initialize the API
|
||||
api = API()
|
||||
|
||||
# Register the callbacks
|
||||
api.register_on_startup_callback(on_api_startup)
|
||||
|
||||
# Start the API, this will run forever until stopped
|
||||
api.run()
|
||||
|
||||
29
scripts/python/API/example_set_cargo_weight.py
Normal file
29
scripts/python/API/example_set_cargo_weight.py
Normal file
@ -0,0 +1,29 @@
|
||||
from api import API
|
||||
|
||||
def on_api_startup(api: API):
|
||||
units = api.update_units()
|
||||
for unit in units.values():
|
||||
if unit.name == "UH-1H":
|
||||
# Set cargo weight to 5000 kg
|
||||
unit.set_cargo_weight(5000.0)
|
||||
|
||||
def on_api_update(api: API):
|
||||
units = api.get_units()
|
||||
for unit in units.values():
|
||||
if unit.name == "UH-1H":
|
||||
print(f"Cargo Weight for {unit.name}: {unit.cargo_weight} kg")
|
||||
|
||||
##############################################################################################
|
||||
# Main entry point for the script. It registers the callbacks and starts the API.
|
||||
##############################################################################################
|
||||
if __name__ == "__main__":
|
||||
# Initialize the API
|
||||
api = API()
|
||||
|
||||
# Register the callbacks
|
||||
api.register_on_update_callback(on_api_update)
|
||||
api.register_on_startup_callback(on_api_startup)
|
||||
|
||||
# Start the API, this will run forever until stopped
|
||||
api.run()
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
|
||||
from math import pi
|
||||
import os
|
||||
|
||||
from api import API, UnitSpawnTable
|
||||
from radio.radio_listener import RadioListener
|
||||
|
||||
# Setup a logger for the module
|
||||
import logging
|
||||
logger = logging.getLogger("OlympusVoiceControl")
|
||||
logger = logging.getLogger("example_voice_control")
|
||||
logger.setLevel(logging.INFO)
|
||||
handler = logging.StreamHandler()
|
||||
formatter = logging.Formatter('[%(asctime)s] %(name)s - %(levelname)s - %(message)s')
|
||||
@ -70,12 +71,15 @@ def on_message_received(recognized_text: str, unit_id: str, api: API, listener:
|
||||
message_filename = api.generate_audio_message("I did not understand")
|
||||
listener.transmit_on_frequency(message_filename, listener.frequency, listener.modulation, listener.encryption)
|
||||
|
||||
# Delete the message file after processing
|
||||
os.remove(message_filename)
|
||||
|
||||
if __name__ == "__main__":
|
||||
api = API()
|
||||
logger.info("API initialized")
|
||||
|
||||
listener = api.create_radio_listener()
|
||||
listener.start(frequency=251.000e6, modulation=0, encryption=0)
|
||||
listener.register_message_callback(lambda wav_filename, unit_id, api=api, listener=listener: on_message_received(wav_filename, unit_id, api, listener))
|
||||
listener.register_message_callback(lambda recognized_text, unit_id, api=api, listener=listener: on_message_received(recognized_text, unit_id, api, listener))
|
||||
|
||||
api.run()
|
||||
@ -4,7 +4,7 @@
|
||||
"port": 4512
|
||||
},
|
||||
"authentication": {
|
||||
"gameMasterPassword": "a474219e5e9503c84d59500bb1bda3d9ade81e52d9fa1c234278770892a6dd74",
|
||||
"gameMasterPassword": "a00a5973aacb17e4659125fbe10f4160d096dd84b2f586d2d75669462a30106d",
|
||||
"blueCommanderPassword": "7d2e1ef898b21db7411f725a945b76ec8dcad340ed705eaf801bc82be6fe8a4a",
|
||||
"redCommanderPassword": "abc5de7abdb8ed98f6d11d22c9d17593e339fde9cf4b9e170541b4f41af937e3"
|
||||
},
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
import re
|
||||
|
||||
# Read the file
|
||||
with open('unit.py', 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Pattern to match callback invocations
|
||||
pattern = r'self\.on_property_change_callbacks\[\"(\w+)\"\]\(self, self\.(\w+)\)'
|
||||
replacement = r'self._trigger_callback("\1", self.\2)'
|
||||
|
||||
# Replace all matches
|
||||
new_content = re.sub(pattern, replacement, content)
|
||||
|
||||
# Write back to file
|
||||
with open('unit.py', 'w', encoding='utf-8') as f:
|
||||
f.write(new_content)
|
||||
|
||||
print('Updated all callback invocations')
|
||||
@ -3,7 +3,7 @@ import asyncio
|
||||
|
||||
from data.data_extractor import DataExtractor
|
||||
from data.data_indexes import DataIndexes
|
||||
from data.data_types import LatLng, TACAN, Radio, GeneralSettings, Ammo, Contact, Offset
|
||||
from data.data_types import DrawArgument, LatLng, TACAN, Radio, GeneralSettings, Ammo, Contact, Offset
|
||||
from data.roes import ROES
|
||||
from data.states import states
|
||||
from utils.utils import enum_to_coalition
|
||||
@ -81,6 +81,8 @@ class Unit:
|
||||
self.targeting_range = 0.0
|
||||
self.aim_method_range = 0.0
|
||||
self.acquisition_range = 0.0
|
||||
self.cargo_weight = 0.0
|
||||
self.draw_arguments: List[DrawArgument] = []
|
||||
|
||||
self.previous_total_ammo = 0
|
||||
self.total_ammo = 0
|
||||
@ -654,6 +656,20 @@ class Unit:
|
||||
# Trigger callbacks for property change
|
||||
if "airborne" in self.on_property_change_callbacks:
|
||||
self._trigger_callback("airborne", self.airborne)
|
||||
elif datum_index == DataIndexes.CARGO_WEIGHT.value:
|
||||
cargo_weight = data_extractor.extract_float64()
|
||||
if cargo_weight != self.cargo_weight:
|
||||
self.cargo_weight = cargo_weight
|
||||
# Trigger callbacks for property change
|
||||
if "cargo_weight" in self.on_property_change_callbacks:
|
||||
self._trigger_callback("cargo_weight", self.cargo_weight)
|
||||
elif datum_index == DataIndexes.DRAW_ARGUMENTS.value:
|
||||
draw_arguments = data_extractor.extract_draw_arguments()
|
||||
if draw_arguments != self.draw_arguments:
|
||||
self.draw_arguments = draw_arguments
|
||||
# Trigger callbacks for property change
|
||||
if "draw_arguments" in self.on_property_change_callbacks:
|
||||
self._trigger_callback("draw_arguments", self.draw_arguments)
|
||||
|
||||
# --- API functions requiring ID ---
|
||||
def set_path(self, path: List[LatLng]):
|
||||
@ -758,6 +774,8 @@ class Unit:
|
||||
def set_engagement_properties(self, barrel_height, muzzle_velocity, aim_time, shots_to_fire, shots_base_interval, shots_base_scatter, engagement_range, targeting_range, aim_method_range, acquisition_range):
|
||||
return self.api.send_command({"setEngagementProperties": {"ID": self.ID, "barrelHeight": barrel_height, "muzzleVelocity": muzzle_velocity, "aimTime": aim_time, "shotsToFire": shots_to_fire, "shotsBaseInterval": shots_base_interval, "shotsBaseScatter": shots_base_scatter, "engagementRange": engagement_range, "targetingRange": targeting_range, "aimMethodRange": aim_method_range, "acquisitionRange": acquisition_range}})
|
||||
|
||||
|
||||
def set_cargo_weight(self, cargo_weight: float):
|
||||
return self.api.send_command({"setCargoWeight": {"ID": self.ID, "weight": cargo_weight}})
|
||||
|
||||
|
||||
def register_draw_argument(self, argument: int, active: bool = True):
|
||||
return self.api.send_command({"registerDrawArgument": {"ID": self.ID, "argument": argument, "active": active}})
|
||||
Loading…
x
Reference in New Issue
Block a user