mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Compare commits
6 Commits
temp
...
python-api
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a67540c12c | ||
|
|
a257afca4b | ||
|
|
3eef91fb24 | ||
|
|
73a7ea74f3 | ||
|
|
4e6701ff01 | ||
|
|
5fa1a26843 |
@@ -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,10 @@ namespace DataIndex {
|
||||
aimMethodRange,
|
||||
acquisitionRange,
|
||||
airborne,
|
||||
cargoWeight,
|
||||
drawArguments,
|
||||
customString,
|
||||
customInteger,
|
||||
lastIndex,
|
||||
endOfData = 255
|
||||
};
|
||||
@@ -159,6 +163,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 +176,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,13 @@ 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);
|
||||
virtual void setCustomString(string newValue) { updateValue(customString, newValue, DataIndex::customString); }
|
||||
virtual void setCustomInteger(unsigned long newValue) { updateValue(customInteger, newValue, DataIndex::customInteger); }
|
||||
|
||||
/********** 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 +201,10 @@ 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; }
|
||||
virtual string getCustomString() { return customString; }
|
||||
virtual unsigned long getCustomInteger() { return customInteger; }
|
||||
|
||||
protected:
|
||||
unsigned int ID;
|
||||
@@ -267,6 +275,11 @@ protected:
|
||||
double aimMethodRange = 0;
|
||||
double acquisitionRange = 0;
|
||||
bool airborne = false;
|
||||
double cargoWeight = 0;
|
||||
vector<DataTypes::DrawArgument> drawArguments;
|
||||
|
||||
string customString = "";
|
||||
unsigned long customInteger = 0;
|
||||
|
||||
/********** 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);
|
||||
}
|
||||
@@ -183,7 +189,7 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
string color = to_string(value[L"color"]);
|
||||
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;
|
||||
command = dynamic_cast<Command*>(new Smoke(color, loc));
|
||||
log(username + " added a " + color + " smoke at (" + to_string(lat) + ", " + to_string(lng) + ")", true);
|
||||
@@ -217,8 +223,8 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
string liveryID = to_string(unit[L"liveryID"]);
|
||||
string skill = to_string(unit[L"skill"]);
|
||||
|
||||
spawnOptions.push_back({unitType, location, loadout, skill, liveryID, heading});
|
||||
log(username + " spawned a " + coalition + " " + unitType , true);
|
||||
spawnOptions.push_back({ unitType, location, loadout, skill, liveryID, heading });
|
||||
log(username + " spawned a " + coalition + " " + unitType, true);
|
||||
}
|
||||
|
||||
if (key.compare("spawnAircrafts") == 0)
|
||||
@@ -251,8 +257,8 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
Coords location; location.lat = lat; location.lng = lng;
|
||||
string liveryID = to_string(unit[L"liveryID"]);
|
||||
string skill = to_string(unit[L"skill"]);
|
||||
|
||||
spawnOptions.push_back({ unitType, location, "", skill, liveryID, heading});
|
||||
|
||||
spawnOptions.push_back({ unitType, location, "", skill, liveryID, heading });
|
||||
log(username + " spawned a " + coalition + " " + unitType, true);
|
||||
}
|
||||
|
||||
@@ -398,7 +404,7 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
unsigned int ID = unit[L"ID"].as_integer();
|
||||
double lat = unit[L"location"][L"lat"].as_double();
|
||||
double lng = unit[L"location"][L"lng"].as_double();
|
||||
|
||||
|
||||
Coords location; location.lat = lat; location.lng = lng;
|
||||
cloneOptions.push_back({ ID, location });
|
||||
log(username + " cloning unit with ID " + to_string(ID), true);
|
||||
@@ -427,7 +433,8 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
unsigned char alarmState = value[L"alarmState"].as_number().to_uint32();
|
||||
unit->setAlarmState(alarmState);
|
||||
log(username + " set unit " + unit->getUnitName() + "(" + unit->getName() + ") alarm state to " + to_string(alarmState), true);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
log("Error while setting setAlarmState. Unit does not exist.");
|
||||
}
|
||||
}
|
||||
@@ -556,7 +563,7 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
unit->setTargetingRange(value[L"targetingRange"].as_number().to_double());
|
||||
unit->setAimMethodRange(value[L"aimMethodRange"].as_number().to_double());
|
||||
unit->setAcquisitionRange(value[L"acquisitionRange"].as_number().to_double());
|
||||
|
||||
|
||||
log(username + " updated unit " + unit->getUnitName() + "(" + unit->getName() + ") engagementProperties", true);
|
||||
}
|
||||
}
|
||||
@@ -581,7 +588,7 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
if (unit != nullptr) {
|
||||
unit->setOnOff(onOff);
|
||||
log(username + " set unit " + unit->getUnitName() + "(" + unit->getName() + ") onOff to: " + (onOff? "true": "false"), true);
|
||||
log(username + " set unit " + unit->getUnitName() + "(" + unit->getName() + ") onOff to: " + (onOff ? "true" : "false"), true);
|
||||
}
|
||||
}
|
||||
/************************/
|
||||
@@ -705,7 +712,7 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
unitsManager->acquireControl(ID);
|
||||
unsigned char operateAs = value[L"operateAs"].as_number().to_uint32();
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
if (unit != nullptr)
|
||||
if (unit != nullptr)
|
||||
unit->setOperateAs(operateAs);
|
||||
}
|
||||
/************************/
|
||||
@@ -811,7 +818,7 @@ void Scheduler::handleRequest(string key, json::value value, string username, js
|
||||
command = dynamic_cast<Command*>(new DeleteSpot(spotID));
|
||||
}
|
||||
/************************/
|
||||
else if (key.compare("setCommandModeOptions") == 0)
|
||||
else if (key.compare("setCommandModeOptions") == 0)
|
||||
{
|
||||
setCommandModeOptions(value);
|
||||
log(username + " updated the Command Mode Options", true);
|
||||
@@ -821,6 +828,53 @@ 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 if (key.compare("setCustomString") == 0)
|
||||
{
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
Unit* unit = unitsManager->getUnit(ID);
|
||||
if (unit != nullptr) {
|
||||
string customString = to_string(value[L"customString"]);
|
||||
unit->setCustomString(customString);
|
||||
log(username + " set custom string to unit " + unit->getUnitName() + "(" + unit->getName() + "), " + customString, true);
|
||||
}
|
||||
}
|
||||
/************************/
|
||||
else if (key.compare("setCustomInteger") == 0)
|
||||
{
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
Unit* unit = unitsManager->getUnit(ID);
|
||||
if (unit != nullptr) {
|
||||
double customNumber = value[L"customInteger"].as_double();
|
||||
unit->setCustomInteger(customNumber);
|
||||
log(username + " set custom number to unit " + unit->getUnitName() + "(" + unit->getName() + "), " + to_string(customNumber), 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()) {
|
||||
@@ -257,8 +271,6 @@ void Unit::getData(stringstream& ss, unsigned long long time)
|
||||
appendNumeric(ss, datumIndex, alive);
|
||||
datumIndex = DataIndex::unitID;
|
||||
appendNumeric(ss, datumIndex, unitID);
|
||||
datumIndex = DataIndex::groupID;
|
||||
appendNumeric(ss, datumIndex, groupID);
|
||||
}
|
||||
else {
|
||||
for (unsigned char datumIndex = DataIndex::startOfData + 1; datumIndex < DataIndex::lastIndex; datumIndex++)
|
||||
@@ -330,6 +342,10 @@ 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;
|
||||
case DataIndex::customString: appendString(ss, datumIndex, customString); break;
|
||||
case DataIndex::customInteger: appendNumeric(ss, datumIndex, customInteger); 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,10 @@ export enum DataIndexes {
|
||||
aimMethodRange,
|
||||
acquisitionRange,
|
||||
airborne,
|
||||
cargoWeight,
|
||||
drawingArguments,
|
||||
customString,
|
||||
customInteger,
|
||||
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,10 @@ export interface UnitData {
|
||||
aimMethodRange: number;
|
||||
acquisitionRange: number;
|
||||
airborne: boolean;
|
||||
cargoWeight: number;
|
||||
drawingArguments: DrawingArgument[];
|
||||
customString: string;
|
||||
customInteger: number;
|
||||
}
|
||||
|
||||
export interface LoadoutItemBlueprint {
|
||||
@@ -314,7 +323,6 @@ export interface UnitBlueprint {
|
||||
roles?: string[];
|
||||
type?: string;
|
||||
loadouts?: LoadoutBlueprint[];
|
||||
acceptedPayloads?: { [key: string]: { clsids: string[]; names: string[] } };
|
||||
filename?: string;
|
||||
liveries?: { [key: string]: { name: string; countries: string[] } };
|
||||
cost?: number;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -283,7 +283,7 @@ export function UnitSpawnMenu(props: {
|
||||
<span
|
||||
className={`
|
||||
font-normal
|
||||
dark:text-gray-400
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Altitude
|
||||
@@ -322,7 +322,7 @@ export function UnitSpawnMenu(props: {
|
||||
<span
|
||||
className={`
|
||||
my-auto font-normal
|
||||
dark:text-gray-400
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Role
|
||||
@@ -358,7 +358,7 @@ export function UnitSpawnMenu(props: {
|
||||
<span
|
||||
className={`
|
||||
my-auto font-normal
|
||||
dark:text-gray-400
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Weapons
|
||||
@@ -410,7 +410,7 @@ export function UnitSpawnMenu(props: {
|
||||
<span
|
||||
className={`
|
||||
my-auto font-normal
|
||||
dark:text-gray-400
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Livery
|
||||
@@ -476,7 +476,7 @@ export function UnitSpawnMenu(props: {
|
||||
<span
|
||||
className={`
|
||||
my-auto font-normal
|
||||
dark:text-gray-400
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Skill
|
||||
@@ -594,7 +594,7 @@ export function UnitSpawnMenu(props: {
|
||||
dark:bg-[#17212D]
|
||||
`}
|
||||
>
|
||||
{item.quantity} x
|
||||
{item.quantity}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
@@ -756,7 +756,7 @@ export function UnitSpawnMenu(props: {
|
||||
<span
|
||||
className={`
|
||||
font-normal
|
||||
dark:text-gray-400
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Altitude
|
||||
@@ -795,7 +795,7 @@ export function UnitSpawnMenu(props: {
|
||||
<span
|
||||
className={`
|
||||
my-auto font-normal
|
||||
dark:text-gray-400
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Role
|
||||
@@ -831,7 +831,7 @@ export function UnitSpawnMenu(props: {
|
||||
<span
|
||||
className={`
|
||||
my-auto font-normal
|
||||
dark:text-gray-400
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Weapons
|
||||
@@ -875,7 +875,7 @@ export function UnitSpawnMenu(props: {
|
||||
<span
|
||||
className={`
|
||||
my-auto font-normal
|
||||
dark:text-gray-400
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Livery
|
||||
@@ -944,7 +944,7 @@ export function UnitSpawnMenu(props: {
|
||||
<span
|
||||
className={`
|
||||
my-auto font-normal
|
||||
dark:text-gray-400
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Skill
|
||||
@@ -1058,7 +1058,7 @@ export function UnitSpawnMenu(props: {
|
||||
dark:bg-[#17212D]
|
||||
`}
|
||||
>
|
||||
{item.quantity} x
|
||||
{item.quantity}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
|
||||
@@ -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,10 @@ export abstract class Unit extends CustomMarker {
|
||||
#racetrackAnchor: LatLng = new LatLng(0, 0);
|
||||
#racetrackBearing: number = 0;
|
||||
#airborne: boolean = false;
|
||||
#cargoWeight: number = 0;
|
||||
#drawingArguments: DrawingArgument[] = [];
|
||||
#customString: string = "";
|
||||
#customInteger: number = 0;
|
||||
|
||||
/* Other members used to draw the unit, mostly ancillary stuff like targets, ranges and so on */
|
||||
#blueprint: UnitBlueprint | null = null;
|
||||
@@ -406,6 +410,18 @@ export abstract class Unit extends CustomMarker {
|
||||
getAirborne() {
|
||||
return this.#airborne;
|
||||
}
|
||||
getCargoWeight() {
|
||||
return this.#cargoWeight;
|
||||
}
|
||||
getDrawingArguments() {
|
||||
return this.#drawingArguments;
|
||||
}
|
||||
getCustomString() {
|
||||
return this.#customString;
|
||||
}
|
||||
getCustomInteger() {
|
||||
return this.#customInteger;
|
||||
}
|
||||
|
||||
static getConstructor(type: string) {
|
||||
if (type === "GroundUnit") return GroundUnit;
|
||||
@@ -797,6 +813,18 @@ 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;
|
||||
case DataIndexes.customString:
|
||||
this.#customString = dataExtractor.extractString();
|
||||
break;
|
||||
case DataIndexes.customInteger:
|
||||
this.#customInteger = dataExtractor.extractUInt32();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -920,6 +948,10 @@ export abstract class Unit extends CustomMarker {
|
||||
aimMethodRange: this.#aimMethodRange,
|
||||
acquisitionRange: this.#acquisitionRange,
|
||||
airborne: this.#airborne,
|
||||
cargoWeight: this.#cargoWeight,
|
||||
drawingArguments: this.#drawingArguments,
|
||||
customString: this.#customString,
|
||||
customInteger: this.#customInteger
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
2
scripts/python/.vscode/launch.json
vendored
2
scripts/python/.vscode/launch.json
vendored
@@ -68,7 +68,7 @@
|
||||
"args": {
|
||||
"key": "folder",
|
||||
"description": "DCS folder location",
|
||||
"default": "C:\\Program Files\\Eagle Dynamics\\DCS World"
|
||||
"default": "E:\\Eagle Dynamics\\DCS World (Open Beta)"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
48
scripts/python/API/.vscode/launch.json
vendored
48
scripts/python/API/.vscode/launch.json
vendored
@@ -5,18 +5,58 @@
|
||||
"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,
|
||||
},
|
||||
{
|
||||
"name": "Infantry boarding",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "infantry_boarding.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,8 @@ class DataIndexes(Enum):
|
||||
AIM_METHOD_RANGE = 63
|
||||
ACQUISITION_RANGE = 64
|
||||
AIRBORNE = 65
|
||||
CARGO_WEIGHT = 66
|
||||
DRAW_ARGUMENTS = 67
|
||||
CUSTOM_STRING = 68
|
||||
CUSTOM_INTEGER = 69
|
||||
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()
|
||||
776
scripts/python/API/infantry_boarding.py
Normal file
776
scripts/python/API/infantry_boarding.py
Normal file
@@ -0,0 +1,776 @@
|
||||
import asyncio
|
||||
from asyncio import Semaphore
|
||||
from random import randrange
|
||||
from api import API, Unit, UnitSpawnTable
|
||||
from math import pi
|
||||
import logging
|
||||
import time
|
||||
|
||||
#Set some globals up
|
||||
before_can_re_embark_time = 300 # this is the time it takes for the infantry, after disembarking, to become embarkable again
|
||||
min_toggle_time_period = 30 # this should typically be however long it takes the longest thing to load or unload, used to prevent accidental re toggling the toggle switch too early by accident
|
||||
####Transport types#####
|
||||
transport_ground = {}
|
||||
|
||||
transport_helicopters = {
|
||||
"UH-1H":{
|
||||
"max_capacity": 8,
|
||||
"max_embark_range": 100,
|
||||
"doors": 2,
|
||||
"door_positions": [(2.5,-pi/2),(0.8,0),(2.5,pi/2),(0.8,0)], #two values here offset and heading offset in radians and second distance offset and heading offset in radians
|
||||
"centre_offset_position": [(0,0),(0,0)], #used for calculating the unit door centre, when the doors aren't in line with the centre
|
||||
"door_argument_nos": [43,44], #draw argument numbers for the doors
|
||||
"door_open_thresholds": [0.8,0.8], #value above which the door is considered open
|
||||
"is_rear_loader": False,
|
||||
"boarding_distance": 5,
|
||||
"rotor_radius": 15
|
||||
},
|
||||
"CH-47Fbl1":{
|
||||
"max_capacity": 30,
|
||||
"max_embark_range": 100,
|
||||
"doors": 1,
|
||||
"door_positions": [(12,-pi),(0,0)], #two values here offset and heading offset in radians and second distance offset and heading offset in radians
|
||||
"centre_offset_position": [(11,-pi),(0,0)], #used for calculating the unit door centre, when the doors aren't in line with the centre
|
||||
"door_argument_nos": [86], #draw argument numbers for the doors
|
||||
"door_open_thresholds": [0.55], #value above which the door is considered open
|
||||
"is_rear_loader": True,
|
||||
"boarding_distance": 10,
|
||||
"rotor_radius": 31
|
||||
},
|
||||
"Mi-8MT":{
|
||||
"max_capacity": 24,
|
||||
"max_embark_range": 100,
|
||||
"doors": 1,
|
||||
"door_positions": [(6,-pi),(0,0)], #two values here offset and heading offset in radians and second distance offset and heading offset in radians
|
||||
"centre_offset_position": [(5.5,-pi),(0,0)], #used for calculating the unit door centre, when the doors aren't in line with the centre
|
||||
"door_argument_nos": [86], #draw argument numbers for the doors
|
||||
"door_open_thresholds": [0.8], #value above which the door is considered open
|
||||
"is_rear_loader": True,
|
||||
"boarding_distance": 10,
|
||||
"rotor_radius": 22
|
||||
},
|
||||
"Mi-24P":{
|
||||
"max_capacity": 8,
|
||||
"max_embark_range": 100,
|
||||
"doors": 2,
|
||||
"door_positions": [(2.5,+pi/2),(1.2,0),(2.5,-pi/2),(1.2,0)], #two values here offset and heading offset in radians and second distance offset and heading offset in radians
|
||||
"centre_offset_position": [(0,0),(0,0)], #used for calculating the unit door centre, when the doors aren't in line with the centre
|
||||
"door_argument_nos": [38,86], #draw argument numbers for the doors
|
||||
"door_open_thresholds": [0.8,0.8], #value above which the door is considered open
|
||||
"is_rear_loader": False,
|
||||
"boarding_distance": 5,
|
||||
"rotor_radius": 18
|
||||
}
|
||||
}
|
||||
|
||||
transport_types = set(transport_helicopters.keys()).union(transport_ground.keys())
|
||||
|
||||
#Infantry transport
|
||||
embarker_inf_red = {}
|
||||
embarker_inf_blue = {"Soldier M4 GRG","soldier_wwii_us"}
|
||||
embarker_types = embarker_inf_blue.union(embarker_inf_red)
|
||||
|
||||
#Time it takes after loading or unloading to swap back to the other
|
||||
|
||||
# Setup a logger for the module
|
||||
logger = logging.getLogger("infantry_boarding")
|
||||
logger.setLevel(logging.INFO)
|
||||
handler = logging.StreamHandler()
|
||||
formatter = logging.Formatter('[%(asctime)s] %(name)s - %(levelname)s - %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
logger.addHandler(handler)
|
||||
|
||||
def cross_product_2d(v1, v2):
|
||||
return v1[0] * v2[1] - v1[1] * v2[0]
|
||||
|
||||
def determine_side(self, door):
|
||||
# Calculate relative vectors
|
||||
vector_to_unit = [
|
||||
self.position.lat - self.transport_unit.position.lat,
|
||||
self.position.lng - self.transport_unit.position.lng
|
||||
]
|
||||
vector_to_door = [
|
||||
door.lat - self.transport_unit.position.lat,
|
||||
door.lng - self.transport_unit.position.lng
|
||||
]
|
||||
|
||||
# Compute the 2D cross product
|
||||
cross_z = cross_product_2d(vector_to_unit, vector_to_door)
|
||||
|
||||
# Determine the side based on the sign of the cross product
|
||||
if cross_z > 0:
|
||||
return True
|
||||
elif cross_z < 0:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
class Transporter(Unit):
|
||||
def __init__(self, Unit):
|
||||
self.unit = Unit
|
||||
|
||||
def set_as_transport(self):
|
||||
self.unit.is_transport = True
|
||||
if self.unit.name in transport_helicopters:
|
||||
if self.unit.name == "UH-1H":
|
||||
self.unit.max_capacity = transport_helicopters["UH-1H"]["max_capacity"]
|
||||
self.unit.max_embark_range = transport_helicopters["UH-1H"]["max_embark_range"]
|
||||
self.unit.boarding_distance = transport_helicopters["UH-1H"]["boarding_distance"]
|
||||
self.unit.current_capacity = 0
|
||||
self.unit.current_cargo_weight = 0
|
||||
self.unit.unit_array = []
|
||||
self.unit.en_boarding_queue = []
|
||||
self.unit.current_max_capacity = 0
|
||||
self.unit.doors = transport_helicopters["UH-1H"]["doors"]
|
||||
self.unit.door_positions = transport_helicopters["UH-1H"]["door_positions"]
|
||||
self.unit.centre_offset_position = transport_helicopters["UH-1H"]["centre_offset_position"]
|
||||
self.unit.door_argument_nos = transport_helicopters["UH-1H"]["door_argument_nos"]
|
||||
self.unit.door_open_thresholds = transport_helicopters["UH-1H"]["door_open_thresholds"]
|
||||
self.unit.will_disembark = True
|
||||
self.unit.register_draw_argument(43) #left door
|
||||
self.unit.register_draw_argument(44) #right door
|
||||
self.unit.register_draw_argument(446) #interior light colour switch, we use as a toggle
|
||||
self.unit.loading_toggle_argument = 2 #this is the argument registered in the index on the draw arguments that controls loading state, have to trial and error what it is for each transport type
|
||||
self.unit.disembark_embark_argument_toggle_argument_threshold = 0.7
|
||||
self.unit.is_rear_loader = transport_helicopters["UH-1H"]["is_rear_loader"]
|
||||
self.unit.rotor_radius = transport_helicopters["UH-1H"]["rotor_radius"]
|
||||
elif self.unit.name == "CH-47Fbl1":
|
||||
self.unit.max_capacity = transport_helicopters["CH-47Fbl1"]["max_capacity"]
|
||||
self.unit.max_embark_range = transport_helicopters["CH-47Fbl1"]["max_embark_range"]
|
||||
self.unit.boarding_distance = transport_helicopters["CH-47Fbl1"]["boarding_distance"]
|
||||
self.unit.current_capacity = 0
|
||||
self.unit.current_cargo_weight = 0
|
||||
self.unit.unit_array = []
|
||||
self.unit.en_boarding_queue = []
|
||||
self.unit.current_max_capacity = 0
|
||||
self.unit.doors = transport_helicopters["CH-47Fbl1"]["doors"]
|
||||
self.unit.centre_offset_position = transport_helicopters["CH-47Fbl1"]["centre_offset_position"]
|
||||
self.unit.door_positions = transport_helicopters["CH-47Fbl1"]["door_positions"]
|
||||
self.unit.door_argument_nos = transport_helicopters["CH-47Fbl1"]["door_argument_nos"]
|
||||
self.unit.door_open_thresholds = transport_helicopters["CH-47Fbl1"]["door_open_thresholds"]
|
||||
self.unit.will_disembark = True
|
||||
self.unit.register_draw_argument(86) #rear ramp
|
||||
self.unit.register_draw_argument(606) #interior light colour switch, we use as a toggle
|
||||
self.unit.loading_toggle_argument = 1 #this is the argument registered in the index on the draw arguments that controls loading state, have to trial and error what it is for each transport type
|
||||
self.unit.disembark_embark_argument_toggle_argument_threshold = 0.5
|
||||
self.unit.is_rear_loader = transport_helicopters["CH-47Fbl1"]["is_rear_loader"]
|
||||
self.unit.rotor_radius = transport_helicopters["CH-47Fbl1"]["rotor_radius"]
|
||||
elif self.unit.name == "Mi-8MT":
|
||||
self.unit.max_capacity = transport_helicopters["Mi-8MT"]["max_capacity"]
|
||||
self.unit.max_embark_range = transport_helicopters["Mi-8MT"]["max_embark_range"]
|
||||
self.unit.boarding_distance = transport_helicopters["Mi-8MT"]["boarding_distance"]
|
||||
self.unit.current_capacity = 0
|
||||
self.unit.current_cargo_weight = 0
|
||||
self.unit.unit_array = []
|
||||
self.unit.en_boarding_queue = []
|
||||
self.unit.current_max_capacity = 0
|
||||
self.unit.doors = transport_helicopters["Mi-8MT"]["doors"]
|
||||
self.unit.centre_offset_position = transport_helicopters["Mi-8MT"]["centre_offset_position"]
|
||||
self.unit.door_positions = transport_helicopters["Mi-8MT"]["door_positions"]
|
||||
self.unit.door_argument_nos = transport_helicopters["Mi-8MT"]["door_argument_nos"]
|
||||
self.unit.door_open_thresholds = transport_helicopters["Mi-8MT"]["door_open_thresholds"]
|
||||
self.unit.will_disembark = True
|
||||
self.unit.register_draw_argument(86) #rear ramp
|
||||
self.unit.register_draw_argument(133) #interior light colour switch, we use as a toggle
|
||||
self.unit.loading_toggle_argument = 1 #this is the argument registered in the index on the draw arguments that controls loading state, have to trial and error what it is for each transport type
|
||||
self.unit.disembark_embark_argument_toggle_argument_threshold = 0.8
|
||||
self.unit.is_rear_loader = transport_helicopters["Mi-8MT"]["is_rear_loader"]
|
||||
self.unit.rotor_radius = transport_helicopters["Mi-8MT"]["rotor_radius"]
|
||||
elif self.unit.name == "Mi-24P":
|
||||
self.unit.max_capacity = transport_helicopters["Mi-24P"]["max_capacity"]
|
||||
self.unit.max_embark_range = transport_helicopters["Mi-24P"]["max_embark_range"]
|
||||
self.unit.boarding_distance = transport_helicopters["Mi-24P"]["boarding_distance"]
|
||||
self.unit.current_capacity = 0
|
||||
self.unit.current_cargo_weight = 0
|
||||
self.unit.unit_array = []
|
||||
self.unit.en_boarding_queue = []
|
||||
self.unit.current_max_capacity = 0
|
||||
self.unit.doors = transport_helicopters["Mi-24P"]["doors"]
|
||||
self.unit.centre_offset_position = transport_helicopters["Mi-24P"]["centre_offset_position"]
|
||||
self.unit.door_positions = transport_helicopters["Mi-24P"]["door_positions"]
|
||||
self.unit.door_argument_nos = transport_helicopters["Mi-24P"]["door_argument_nos"]
|
||||
self.unit.door_open_thresholds = transport_helicopters["Mi-24P"]["door_open_thresholds"]
|
||||
self.unit.will_disembark = True
|
||||
self.unit.register_draw_argument(38) #left door
|
||||
self.unit.register_draw_argument(86) #right door
|
||||
self.unit.register_draw_argument(47) #interior light colour switch, we use as a toggle
|
||||
self.unit.loading_toggle_argument = 2 #this is the argument registered in the index on the draw arguments that controls loading state, have to trial and error what it is for each transport type
|
||||
self.unit.disembark_embark_argument_toggle_argument_threshold = 0.8
|
||||
self.unit.is_rear_loader = transport_helicopters["Mi-24P"]["is_rear_loader"]
|
||||
self.unit.rotor_radius = transport_helicopters["Mi-24P"]["rotor_radius"]
|
||||
else:
|
||||
pass
|
||||
|
||||
class DisembarkedInfantry(Unit):
|
||||
def __str__(self):
|
||||
return f"DisembarkedInfrantry(unit_id={self.unit_id}, group_id={self.group_id}, position={self.position}, heading={self.heading})"
|
||||
|
||||
def __init__(self, Unit):
|
||||
self.unit = Unit
|
||||
|
||||
def disembark_from_transport(self):
|
||||
destination = self.position.project_with_bearing_and_distance(30, self.heading)
|
||||
# Set the destination for the unit
|
||||
self.set_roe(4) #set to hold fire to avoid stopping to shoot
|
||||
self.is_loadable = False
|
||||
self.set_path([destination])
|
||||
if self.check_for_enemy_in_range():
|
||||
self.set_speed(10)
|
||||
else:
|
||||
self.set_speed(2)
|
||||
self.register_on_destination_reached_callback(
|
||||
self.on_destination_reached,
|
||||
destination,
|
||||
threshold=15.0,
|
||||
timeout=30.0 # Timeout after 30 seconds if the destination is not reached
|
||||
)
|
||||
|
||||
def check_for_enemy_in_range(self):
|
||||
units = api.get_units()
|
||||
for unit in units.values():
|
||||
if unit.alive and unit.coalition != self.coalition:
|
||||
distance_to_enemy = self.position.distance_to(unit.position)
|
||||
if distance_to_enemy < 2000: #if an enemy is within 2000m, approx rifle max range
|
||||
return True
|
||||
return False
|
||||
|
||||
async def on_destination_reached(self, _, reached: bool):
|
||||
if not reached:
|
||||
# logger.info(f"Unit {self} did not reach its destination.")
|
||||
self.set_roe(1)
|
||||
new_patrol = self.position.project_with_bearing_and_distance(1000, self.transport_spawn_heading)
|
||||
await asyncio.sleep(10) #wait a bit before trying again
|
||||
try:
|
||||
if self.og_transport.is_rear_loader:
|
||||
side_offset = self.position.project_with_bearing_and_distance(30,self.transport_spawn_heading-pi/2)
|
||||
self.set_path([side_offset,new_patrol])
|
||||
else:
|
||||
self.set_path([new_patrol])
|
||||
except AttributeError:
|
||||
self.set_path([new_patrol])
|
||||
if self.check_for_enemy_in_range():
|
||||
self.set_speed(10)
|
||||
else:
|
||||
self.set_speed(1.3)
|
||||
await asyncio.sleep(before_can_re_embark_time) #wait before setting to be boardable
|
||||
self.is_loadable = True
|
||||
logger.info(f"Unit {self} is now boardable again.")
|
||||
else:
|
||||
self.set_roe(1)
|
||||
new_patrol = self.position.project_with_bearing_and_distance(1000, self.transport_spawn_heading)
|
||||
await asyncio.sleep(10) #wait a bit before trying again
|
||||
try:
|
||||
if self.og_transport.is_rear_loader:
|
||||
side_offset = self.position.project_with_bearing_and_distance(30,self.transport_spawn_heading-pi/2)
|
||||
self.set_path([side_offset,new_patrol])
|
||||
else:
|
||||
self.set_path([new_patrol])
|
||||
except AttributeError:
|
||||
self.set_path([new_patrol])
|
||||
if self.check_for_enemy_in_range():
|
||||
self.set_speed(10)
|
||||
else:
|
||||
self.set_speed(1.3)
|
||||
await asyncio.sleep(before_can_re_embark_time) #wait before setting to be boardable
|
||||
self.is_loadable = True
|
||||
logger.info(f"Unit {self} is now boardable again.")
|
||||
|
||||
class Embarker(Unit):
|
||||
def __str__(self):
|
||||
return f"DisembarkedInfrantry(unit_id={self.unit_id}, group_id={self.group_id}, position={self.position}, heading={self.heading})"
|
||||
|
||||
def __init__(self, Unit):
|
||||
self.unit = Unit
|
||||
|
||||
def set_as_embarker(self):
|
||||
self.unit.is_embarker = True
|
||||
self.unit.is_moving = False
|
||||
self.unit.is_loadable = True
|
||||
logger.info(f"Set unit '{self.unit.name}' as embarker.")
|
||||
|
||||
def can_board(self):
|
||||
transport = self.transport_unit
|
||||
if transport.current_capacity < transport.max_capacity:
|
||||
transport.unit_array.append(self.name)
|
||||
transport.current_capacity += 1
|
||||
self.delete_unit()
|
||||
else:
|
||||
pass
|
||||
|
||||
def board_transport(self):
|
||||
door, num_doors_open = self.get_closest_door()
|
||||
if num_doors_open > 1:
|
||||
door_bypass = True
|
||||
else:
|
||||
door_bypass = False
|
||||
|
||||
if door is None:
|
||||
pass
|
||||
elif door is not None:
|
||||
if self.is_moving:
|
||||
pass
|
||||
elif not self.is_moving:
|
||||
distance = self.position.distance_to(door)
|
||||
distance_centre_offset_position = self.position.distance_to(self.transport_unit.position.project_with_bearing_and_distance(self.transport_unit.centre_offset_position[0][0], self.transport_unit.centre_offset_position[0][1]))
|
||||
|
||||
if distance >= distance_centre_offset_position:
|
||||
if determine_side(self,door): #right side
|
||||
if self.transport_unit.is_rear_loader: # chinook rear loader
|
||||
destination = door.project_with_bearing_and_distance(self.transport_unit.rotor_radius/3, self.transport_unit.heading-pi)
|
||||
destination = destination.project_with_bearing_and_distance(self.transport_unit.rotor_radius/2, self.transport_unit.heading+pi/2)
|
||||
destination.threshold = 2
|
||||
self.set_path([destination])
|
||||
self.register_on_destination_reached_callback(
|
||||
self.on_destination_reached,
|
||||
destination,
|
||||
threshold=2.0,
|
||||
timeout=10.0 # Timeout after 30 seconds if the destination is not reached
|
||||
)
|
||||
self.is_moving = True
|
||||
else: # huey front loader
|
||||
destination = door.project_with_bearing_and_distance(self.transport_unit.rotor_radius, self.transport_unit.heading)
|
||||
destination = destination.project_with_bearing_and_distance(self.transport_unit.rotor_radius/2, self.transport_unit.heading+pi/2)
|
||||
destination.threshold = 2
|
||||
self.set_path([destination])
|
||||
self.register_on_destination_reached_callback(
|
||||
self.on_destination_reached,
|
||||
destination,
|
||||
threshold=2.0,
|
||||
timeout=10.0 # Timeout after 30 seconds if the destination is not reached
|
||||
)
|
||||
self.is_moving = True
|
||||
else: #left side
|
||||
if self.transport_unit.is_rear_loader: # chinook rear loader
|
||||
destination = door.project_with_bearing_and_distance(self.transport_unit.rotor_radius/3, self.transport_unit.heading-pi)
|
||||
destination = destination.project_with_bearing_and_distance(self.transport_unit.rotor_radius/2, self.transport_unit.heading-pi/2)
|
||||
destination.threshold = 2
|
||||
self.set_path([destination])
|
||||
self.register_on_destination_reached_callback(
|
||||
self.on_destination_reached,
|
||||
destination,
|
||||
threshold=2.0,
|
||||
timeout=10.0 # Timeout after 30 seconds if the destination is not reached
|
||||
)
|
||||
self.is_moving = True
|
||||
else: # huey front loader
|
||||
destination = door.project_with_bearing_and_distance(self.transport_unit.rotor_radius, self.transport_unit.heading)
|
||||
destination = destination.project_with_bearing_and_distance(self.transport_unit.rotor_radius/2, self.transport_unit.heading-pi/2)
|
||||
destination.threshold = 2
|
||||
self.set_path([destination])
|
||||
self.register_on_destination_reached_callback(
|
||||
self.on_destination_reached,
|
||||
destination,
|
||||
threshold=2.0,
|
||||
timeout=10.0 # Timeout after 30 seconds if the destination is not reached
|
||||
)
|
||||
self.is_moving = True
|
||||
else:
|
||||
destination = self.position.project_with_bearing_and_distance(distance+2, self.position.bearing_to(door))
|
||||
#destination = destination.project_with_bearing_and_distance(self.transport_unit.rotor_radius, self.transport_unit.heading+pi/2)
|
||||
destination.threshold = 2
|
||||
self.set_path([destination,door])
|
||||
self.is_moving = True
|
||||
|
||||
|
||||
|
||||
def get_closest_door(self):
|
||||
return check_closest_open_door(self.transport_unit, self)
|
||||
|
||||
async def on_destination_reached(self, _, reached: bool):
|
||||
if not reached:
|
||||
logger.info(f"Unit {self} did not reach its destination.")
|
||||
self.is_moving = False
|
||||
else:
|
||||
logger.info(f"Unit {self} has reached its destination.")
|
||||
self.is_moving = False
|
||||
|
||||
await asyncio.sleep(10)
|
||||
self.board_transport() # Attempt to board again
|
||||
|
||||
def check_closest_open_door(transport, embarker):
|
||||
if transport.name in transport_helicopters:
|
||||
if transport.door_argument_nos is None and transport.doors > 0:
|
||||
return transport.position.project_with_bearing_and_distance(5,transport.heading + pi), transport.heading + pi
|
||||
elif transport.door_argument_nos is not None and transport.doors > 0:
|
||||
closest_door = None
|
||||
doors_open = 0
|
||||
distance_to_closest_door = float('inf')
|
||||
for i in range(transport.doors):
|
||||
if transport.draw_arguments[i].value >= transport.door_open_thresholds[i]:
|
||||
doors_open += 1
|
||||
distance = embarker.position.distance_to(transport.position.project_with_bearing_and_distance(transport.door_positions[i*2][0], transport.heading + transport.door_positions[i*2][1]).project_with_bearing_and_distance(transport.door_positions[i*2+1][0], transport.heading + transport.door_positions[i*2+1][1]))
|
||||
if distance < distance_to_closest_door:
|
||||
distance_to_closest_door = distance
|
||||
closest_door = transport.position.project_with_bearing_and_distance(transport.door_positions[i*2][0], transport.heading + transport.door_positions[i*2][1]).project_with_bearing_and_distance(transport.door_positions[i*2+1][0], transport.heading + transport.door_positions[i*2+1][1])
|
||||
return closest_door, doors_open
|
||||
else:
|
||||
return None, 0
|
||||
|
||||
def check_for_door_status(transporter):
|
||||
if not hasattr(transporter, 'draw_arguments') or len(transporter.draw_arguments) < transporter.doors:
|
||||
#logger.warning(f"Transporter '{transporter.name}' does not have enough draw arguments registered.")
|
||||
return False
|
||||
|
||||
if transporter.name in transport_helicopters:
|
||||
if transporter.door_argument_nos is None and transporter.doors > 0:
|
||||
return True
|
||||
elif transporter.door_argument_nos is not None and transporter.doors > 0:
|
||||
a_door_is_open = False
|
||||
for i in range(transporter.doors):
|
||||
if i >= len(transporter.draw_arguments): # Ensure the index is valid
|
||||
#logger.error(f"Index {i} out of range for draw_arguments in transporter '{transporter.name}'.")
|
||||
continue
|
||||
if transporter.draw_arguments[i].value >= transporter.door_open_thresholds[i]:
|
||||
a_door_is_open = True
|
||||
return a_door_is_open
|
||||
else:
|
||||
return False
|
||||
elif transporter.name in transport_ground:
|
||||
if transporter.door_argument_nos is None and transporter.doors > 0:
|
||||
return True
|
||||
elif transporter.door_argument_nos is not None and transporter.doors > 0:
|
||||
a_door_is_open = False
|
||||
for i in range(transporter.doors):
|
||||
if i >= len(transporter.draw_arguments): # Ensure the index is valid
|
||||
#logger.error(f"Index {i} out of range for draw_arguments in transporter '{transporter.name}'.")
|
||||
continue
|
||||
if transporter.draw_arguments[i].value >= transporter.door_open_thresholds[i]:
|
||||
a_door_is_open = True
|
||||
return a_door_is_open
|
||||
else:
|
||||
return False
|
||||
|
||||
async def load_loadable_units():
|
||||
units = api.get_units()
|
||||
for embarker in units.values():
|
||||
if embarker.alive and hasattr(embarker, 'is_embarker'):
|
||||
if hasattr(embarker, 'in_embark_queue') and hasattr(embarker, 'transport_unit') and hasattr(embarker, 'is_moving'):
|
||||
if embarker.transport_unit.name in transport_types:
|
||||
if embarker.roe != "hold":
|
||||
embarker.set_roe(4) #set to hold fire to avoid stopping to shoot
|
||||
#check the doors are open
|
||||
if check_for_door_status(embarker.transport_unit):
|
||||
closest_door, num_doors_open = check_closest_open_door(embarker.transport_unit, embarker)
|
||||
if closest_door is not None:
|
||||
#print(f"A door is open on {embarker.transport_unit.name}, closest door is {closest_door}, {num_doors_open} doors open")
|
||||
embarker.__class__ = Embarker
|
||||
#check if close enough to board
|
||||
closest_door, _ = embarker.get_closest_door()
|
||||
door_distance = embarker.position.distance_to(closest_door)
|
||||
if door_distance < embarker.transport_unit.boarding_distance or embarker.position.distance_to(embarker.transport_unit.position) < embarker.transport_unit.boarding_distance:
|
||||
transport = embarker.transport_unit
|
||||
embarker_units = [
|
||||
(embarker, embarker.position.distance_to(transport.position))
|
||||
for embarker in units.values()
|
||||
if embarker.alive
|
||||
and hasattr(embarker, 'is_embarker')
|
||||
and embarker.position.distance_to(closest_door) < transport.boarding_distance
|
||||
]
|
||||
|
||||
embarkers_sorted = sorted(embarker_units, key=lambda x: x[1])
|
||||
if not embarkers_sorted:
|
||||
pass
|
||||
else:
|
||||
if embarker.ID == embarkers_sorted[0][0].ID:
|
||||
transport.current_capacity += 1
|
||||
transport.current_max_capacity +=1
|
||||
transport.unit_array.append(embarker)
|
||||
transport.set_cargo_weight(transport.current_cargo_weight + 100) #assume 100kg per infantry with kit
|
||||
transport.current_cargo_weight += 100
|
||||
embarker.delete_unit()
|
||||
#asyncio.create_task(set_as_disembarking(transport))
|
||||
break
|
||||
#else run it closer
|
||||
if embarker.is_moving:
|
||||
if hasattr(embarker, 'last_pos'):
|
||||
if embarker.position == embarker.last_pos:
|
||||
embarker.is_moving = False
|
||||
embarker.last_pos = embarker.position
|
||||
pass
|
||||
elif not embarker.is_moving:
|
||||
embarker.board_transport()
|
||||
else:
|
||||
#no doors so do nothing
|
||||
pass
|
||||
|
||||
def generate_transport_units():
|
||||
units = api.get_units()
|
||||
for unit in units.values():
|
||||
if unit.alive and unit.name in transport_types and not hasattr(unit, 'is_transport'):
|
||||
new_transport = Transporter(unit)
|
||||
new_transport.set_as_transport()
|
||||
|
||||
elif unit.alive and unit.name in embarker_types and not hasattr(unit, 'is_embarker'):
|
||||
new_emabarquee = Embarker(unit)
|
||||
new_emabarquee.set_as_embarker()
|
||||
|
||||
async def set_as_disembarking(transport):
|
||||
transport.will_disembark = True
|
||||
transport.en_boarding_queue = []
|
||||
|
||||
async def set_as_not_disembarking(transport):
|
||||
transport.will_disembark = False
|
||||
transport.current_max_capacity = transport.current_capacity
|
||||
|
||||
unload_semaphore = Semaphore(1)
|
||||
|
||||
async def check_for_unloadable_units():
|
||||
# Use the semaphore to ensure only one instance runs at a time
|
||||
async with unload_semaphore:
|
||||
units = api.get_units()
|
||||
try:
|
||||
for transporter in units.values():
|
||||
if transporter.alive and hasattr(transporter, 'is_transport') and transporter.will_disembark:
|
||||
# Check if the transporter is in a position to disembark units
|
||||
if transporter.speed < 2 and check_for_door_status(transporter) and not transporter.airborne and transporter.current_capacity > 0: # check speed is less than 2 m/s and doors are open
|
||||
# Transport is ready to disembark
|
||||
to_remove = [] # Sets up variable to hold units to remove from queue
|
||||
for disembarker in transporter.unit_array:
|
||||
# Get the open doors
|
||||
open_doors = []
|
||||
open_doors_headings = []
|
||||
for i in range(transporter.doors):
|
||||
if transporter.draw_arguments[i].value >= transporter.door_open_thresholds[i]:
|
||||
door_position = transporter.position.project_with_bearing_and_distance(
|
||||
transporter.door_positions[i * 2][0],
|
||||
transporter.heading + transporter.door_positions[i * 2][1]
|
||||
).project_with_bearing_and_distance(
|
||||
transporter.door_positions[i * 2 + 1][0],
|
||||
transporter.heading + transporter.door_positions[i * 2 + 1][1]
|
||||
)
|
||||
door_heading = transporter.heading + transporter.door_positions[i * 2][1]
|
||||
open_doors.append(door_position)
|
||||
open_doors_headings.append(door_heading)
|
||||
|
||||
# Round-robin spawn mechanism
|
||||
if not hasattr(transporter, 'last_door_index'):
|
||||
transporter.last_door_index = 0 # Initialize the last used door index
|
||||
|
||||
# Get the next door in the round-robin sequence
|
||||
|
||||
|
||||
async def execution_callback(new_group_ID: int):
|
||||
logger.info(f"New units spawned, groupID: {new_group_ID}")
|
||||
units = api.get_units()
|
||||
for new_unit in units.values():
|
||||
if new_unit.group_id == new_group_ID:
|
||||
logger.info(f"New unit spawned: {new_unit}")
|
||||
new_unit.__class__ = DisembarkedInfantry
|
||||
new_unit.transport_spawn_heading = transporter.heading
|
||||
new_unit.og_transport = transporter
|
||||
new_unit.disembark_from_transport()
|
||||
new_unit.original_position = new_unit.position
|
||||
# The delay is a function of how many units are left to disembark and how long it takes to get to the disembark spot
|
||||
|
||||
async def delayed_spawn(delay,transporter,open_doors,open_doors_headings,disembarker):
|
||||
door_index = transporter.last_door_index % len(open_doors)
|
||||
transporter.last_door_index += 1
|
||||
# Increment the door index for the next spawn
|
||||
|
||||
# Spawn the unit at the selected door
|
||||
door_position = open_doors[door_index]
|
||||
door_heading = open_doors_headings[door_index]
|
||||
|
||||
spawn_table: UnitSpawnTable = UnitSpawnTable(
|
||||
unit_type=disembarker.name,
|
||||
location=door_position,
|
||||
heading=door_heading,
|
||||
skill="High",
|
||||
livery_id=""
|
||||
)
|
||||
# Add a delay before spawning the unit
|
||||
await asyncio.sleep(delay) # Delay of 2 seconds (adjust as needed)
|
||||
api.spawn_ground_units([spawn_table], transporter.coalition, "", True, 0, execution_callback)
|
||||
|
||||
transporter.set_cargo_weight(transporter.current_cargo_weight - 100) # Assume 100kg per infantry with kit
|
||||
transporter.current_cargo_weight -= 100
|
||||
logger.info(f"Spawned unit '{disembarker.name}' from open door of transport '{transporter.name}'.")
|
||||
|
||||
if len(open_doors) > 1:
|
||||
if (transporter.current_max_capacity - transporter.current_capacity) < len(open_doors):
|
||||
delay = 0.1
|
||||
else:
|
||||
delay = (transporter.current_max_capacity - transporter.current_capacity) * 1.25 - ((len(open_doors)-1) * 2.5) + 2.5
|
||||
else:
|
||||
delay = (transporter.current_max_capacity - transporter.current_capacity) * 2.5
|
||||
asyncio.create_task(delayed_spawn(delay,transporter,open_doors,open_doors_headings,disembarker))
|
||||
|
||||
transporter.en_boarding_queue = []
|
||||
transporter.current_capacity -= 1
|
||||
to_remove.append(disembarker)
|
||||
|
||||
for disembarker in to_remove:
|
||||
transporter.unit_array.remove(disembarker)
|
||||
|
||||
except Exception as e:
|
||||
#logging.warning(e, exc_info=True)
|
||||
logger.info(f"Error in check_for_unloadable_units: {e}")
|
||||
|
||||
async def check_for_loadable_units():
|
||||
units = api.get_units()
|
||||
try:
|
||||
for transporter in units.values():
|
||||
if transporter.alive and hasattr(transporter, 'is_transport') and not transporter.will_disembark:
|
||||
if len(transporter.unit_array) < transporter.max_capacity:
|
||||
if transporter.speed < 2 and check_for_door_status(transporter): #check speed is less than 2 m/s and doors are open
|
||||
# print("Speed is okay")
|
||||
embarker_units = [
|
||||
(embarker, embarker.position.distance_to(transporter.position))
|
||||
for embarker in units.values()
|
||||
if embarker.alive
|
||||
and hasattr(embarker, 'is_embarker')
|
||||
and getattr(embarker, 'is_loadable', True) # Check if is_loadable is True
|
||||
and embarker.position.distance_to(transporter.position) < transporter.max_embark_range
|
||||
]
|
||||
if embarker_units is None or len(embarker_units) == 0:
|
||||
continue
|
||||
else:
|
||||
for embarker in embarker_units:
|
||||
if hasattr(embarker, 'in_embark_queue') and embarker.in_embark_queue:
|
||||
if embarker.in_embark_queue:
|
||||
embarker_units.remove(embarker)
|
||||
|
||||
embarkers_sorted = sorted(embarker_units, key=lambda x: x[1])
|
||||
closest_embarkers = embarkers_sorted[:transporter.max_capacity-len(transporter.en_boarding_queue)]
|
||||
|
||||
for embarker, distance in closest_embarkers:
|
||||
if embarker not in transporter.en_boarding_queue and distance < transporter.max_embark_range and not hasattr(embarker, 'is_in_queue'):
|
||||
transporter.en_boarding_queue.append(embarker)
|
||||
embarker.in_embark_queue = True
|
||||
embarker.is_in_queue = True
|
||||
embarker.transport_unit = transporter
|
||||
logger.info(f"Added embarker '{embarker.name}' to '{transporter.name}' s boarding queue.")
|
||||
elif embarker not in transporter.en_boarding_queue and distance < transporter.max_embark_range and hasattr(embarker, 'is_in_queue'):
|
||||
if embarker.is_in_queue:
|
||||
await asyncio.sleep(60) #wait a bit and try again next time
|
||||
embarker.is_in_queue = False
|
||||
else:
|
||||
transporter.en_boarding_queue.append(embarker)
|
||||
embarker.in_embark_queue = True
|
||||
embarker.is_in_queue = True
|
||||
embarker.transport_unit = transporter
|
||||
logger.info(f"Added embarker '{embarker.name}' to '{transporter.name}' s boarding queue.")
|
||||
elif embarker in transporter.en_boarding_queue:
|
||||
pass
|
||||
|
||||
else:
|
||||
pass #we pass as the transport is full
|
||||
except Exception as e:
|
||||
logger.error(f"Error in check_for_loadable_units: {e}")
|
||||
|
||||
async def check_for_transport_embarker_or_disembark():
|
||||
units = api.get_units()
|
||||
try:
|
||||
for transporter in units.values():
|
||||
if transporter.alive and hasattr(transporter, 'is_transport'):
|
||||
# Ensure the transporter has a `last_toggle_time` attribute
|
||||
if not hasattr(transporter, 'last_toggle_time'):
|
||||
transporter.last_toggle_time = 0 # Initialize it to 0
|
||||
|
||||
# Get the current time
|
||||
current_time = time.time()
|
||||
|
||||
# Check if the toggle is allowed (min_toggle_time_period seconds since the last toggle)
|
||||
if current_time - transporter.last_toggle_time < min_toggle_time_period:
|
||||
continue # Skip toggling if the cooldown hasn't passed
|
||||
|
||||
# Check the loading toggle argument and toggle the state
|
||||
if transporter.loading_toggle_argument is None or not hasattr(transporter, 'draw_arguments') or len(transporter.draw_arguments) <= transporter.loading_toggle_argument:
|
||||
pass
|
||||
else:
|
||||
if transporter.will_disembark:
|
||||
if transporter.draw_arguments[transporter.loading_toggle_argument].value <= transporter.disembark_embark_argument_toggle_argument_threshold:
|
||||
continue
|
||||
elif transporter.draw_arguments[transporter.loading_toggle_argument].value > transporter.disembark_embark_argument_toggle_argument_threshold:
|
||||
# Set to embark
|
||||
await set_as_not_disembarking(transporter)
|
||||
transporter.last_toggle_time = current_time # Update the last toggle time
|
||||
logger.info(f"Transporter '{transporter.name}' set to embark.")
|
||||
else:
|
||||
if transporter.draw_arguments[transporter.loading_toggle_argument].value <= transporter.disembark_embark_argument_toggle_argument_threshold:
|
||||
# Set to disembark
|
||||
await set_as_disembarking(transporter)
|
||||
transporter.last_toggle_time = current_time # Update the last toggle time
|
||||
logger.info(f"Transporter '{transporter.name}' set to disembark.")
|
||||
elif transporter.draw_arguments[transporter.loading_toggle_argument].value > transporter.disembark_embark_argument_toggle_argument_threshold:
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in check_for_transport_embarker_or_disembark: {e}")
|
||||
|
||||
#############
|
||||
#API SECTION#
|
||||
#############
|
||||
def on_api_startup(api: API):
|
||||
global units_to_delete
|
||||
logger.info("API started")
|
||||
|
||||
# Get all the units from the API. Force an update to get the latest units.
|
||||
units = api.update_units()
|
||||
|
||||
# Initialize the list to hold units to delete
|
||||
units_to_delete = []
|
||||
generate_transport_units() #comment this if doing draw Args testing
|
||||
#new_test_unit() # comment this if running normally, this is used only for getting draw args
|
||||
|
||||
def on_unit_alive_change(unit: Unit, value: bool):
|
||||
global units_to_delete
|
||||
|
||||
if units_to_delete is None:
|
||||
logger.error("units_to_delete is not initialized.")
|
||||
return
|
||||
|
||||
# Check if the unit has been deleted
|
||||
if value is False:
|
||||
if unit in units_to_delete:
|
||||
units_to_delete.remove(unit)
|
||||
else:
|
||||
pass
|
||||
|
||||
# unit_args = []
|
||||
# exclusions_array = [1,102,103,11,12,17,278,279,280,281,282,283,284,286,287,288,289,290,337,37,39,393,399,4,40,41,42,448,487,488,6,77,99]
|
||||
# async def check_args_changed():
|
||||
# global unit_args
|
||||
# units = api.get_units()
|
||||
# for unit in units.values():
|
||||
# for argument in unit.draw_arguments:
|
||||
# if argument in unit_args:
|
||||
# pass
|
||||
# else:
|
||||
# if argument.argument in exclusions_array:
|
||||
# pass
|
||||
# else:
|
||||
# print(argument.argument, end=",")
|
||||
# unit_args = unit.draw_arguments
|
||||
# print("New loop")
|
||||
|
||||
# def new_test_unit():
|
||||
# units = api.get_units()
|
||||
# for unit in units.values():
|
||||
# if unit.alive and unit.name in transport_types and not hasattr(unit, 'is_transport'):
|
||||
# for i in range(500): #191
|
||||
# unit.register_draw_argument(i)
|
||||
|
||||
# def check_arg_value():
|
||||
# units = api.get_units()
|
||||
# for unit in units.values():
|
||||
# if unit.alive and unit.name in transport_types:
|
||||
# unit.register_draw_argument(47) #191
|
||||
# print(f"{unit.draw_arguments[0].argument} value is {unit.draw_arguments[0].value}")
|
||||
|
||||
async def on_api_update(api: API):
|
||||
asyncio.create_task(check_for_loadable_units())
|
||||
asyncio.create_task(load_loadable_units())
|
||||
asyncio.create_task(check_for_unloadable_units())
|
||||
asyncio.create_task(check_for_transport_embarker_or_disembark())
|
||||
generate_transport_units()
|
||||
#asyncio.create_task(check_args_changed())
|
||||
#check_arg_value()
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
api = API()
|
||||
api.register_on_startup_callback(on_api_startup)
|
||||
api.register_on_update_callback(on_api_update)
|
||||
api.run()
|
||||
@@ -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,10 @@ 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.custom_string = ""
|
||||
self.custom_integer = 0
|
||||
|
||||
self.previous_total_ammo = 0
|
||||
self.total_ammo = 0
|
||||
@@ -654,6 +658,34 @@ 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)
|
||||
elif datum_index == DataIndexes.CUSTOM_STRING.value:
|
||||
custom_string = data_extractor.extract_string()
|
||||
if custom_string != self.custom_string:
|
||||
self.custom_string = custom_string
|
||||
# Trigger callbacks for property change
|
||||
if "custom_string" in self.on_property_change_callbacks:
|
||||
self._trigger_callback("custom_string", self.custom_string)
|
||||
elif datum_index == DataIndexes.CUSTOM_INTEGER.value:
|
||||
custom_integer = data_extractor.extract_uint32()
|
||||
if custom_integer != self.custom_integer:
|
||||
self.custom_integer = custom_integer
|
||||
# Trigger callbacks for property change
|
||||
if "custom_integer" in self.on_property_change_callbacks:
|
||||
self._trigger_callback("custom_integer", self.custom_integer)
|
||||
|
||||
# --- API functions requiring ID ---
|
||||
def set_path(self, path: List[LatLng]):
|
||||
@@ -758,6 +790,14 @@ 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}})
|
||||
|
||||
def set_custom_string(self, custom_string: str):
|
||||
return self.api.send_command({"setCustomString": {"ID": self.ID, "customString": custom_string}})
|
||||
|
||||
def set_custom_integer(self, custom_integer: int):
|
||||
return self.api.send_command({"setCustomInteger": {"ID": self.ID, "customInteger": custom_integer}})
|
||||
@@ -114,16 +114,13 @@ if len(sys.argv) > 1:
|
||||
database = json.load(f)
|
||||
|
||||
# Loads the loadout names
|
||||
with open('unitPayloads.lua') as f:
|
||||
with open('../unitPayloads.lua') as f:
|
||||
lines = f.readlines()
|
||||
unit_payloads = lua.decode("".join(lines).replace("Olympus.unitPayloads = ", "").replace("\n", ""))
|
||||
|
||||
# Loads the loadout roles
|
||||
with open('payloadRoles.json') as f:
|
||||
payloads_roles = json.load(f)
|
||||
|
||||
with open('pylonUsage.json') as f:
|
||||
pylon_usage = json.load(f)
|
||||
|
||||
# Loop on all the units in the database
|
||||
for unit_name in database:
|
||||
@@ -157,15 +154,6 @@ if len(sys.argv) > 1:
|
||||
}
|
||||
database[unit_name]["loadouts"].append(empty_loadout)
|
||||
|
||||
# Add the available pylon usage
|
||||
database[unit_name]["acceptedPayloads"] = {}
|
||||
for pylon_name in pylon_usage[unit_name]:
|
||||
pylon_data = pylon_usage[unit_name][pylon_name]
|
||||
database[unit_name]["acceptedPayloads"][pylon_name] = {
|
||||
"clsids": pylon_data,
|
||||
"names": [find_weapon_name(clsid) for clsid in pylon_data]
|
||||
}
|
||||
|
||||
# Loop on all the loadouts for that unit
|
||||
for payload_name in unit_payloads[unit_name]:
|
||||
payload_weapons = {}
|
||||
|
||||
@@ -47,10 +47,8 @@ if len(sys.argv) > 1:
|
||||
print(f"Warning, could not find {unit_name} in classes list. Skipping...")
|
||||
continue
|
||||
|
||||
if not "acquisitionRange" in database[unit_name]:
|
||||
database[unit_name]["acquisitionRange"] = unitmap[found_name].detection_range
|
||||
if not "engagementRange" in database[unit_name]:
|
||||
database[unit_name]["engagementRange"] = unitmap[found_name].threat_range
|
||||
database[unit_name]["acquisitionRange"] = unitmap[found_name].detection_range
|
||||
database[unit_name]["engagementRange"] = unitmap[found_name].threat_range
|
||||
|
||||
except Exception as e:
|
||||
print(f"Could not find data for unitof type {unit_name}: {e}, skipping...")
|
||||
|
||||
@@ -92,9 +92,7 @@ for filename in filenames:
|
||||
src = tmp['payloads'].values()
|
||||
else:
|
||||
src = tmp['payloads']
|
||||
|
||||
print(f"Processing {filename} with {len(src)} payloads, detected unit name {tmp['unitType']}")
|
||||
|
||||
|
||||
names[tmp['unitType']] = []
|
||||
roles[tmp['unitType']] = {}
|
||||
payloads[tmp['unitType']] = {}
|
||||
@@ -131,22 +129,9 @@ for filename in filenames:
|
||||
with open('payloadRoles.json', 'w') as f:
|
||||
json.dump(roles, f, ensure_ascii = False, indent = 2)
|
||||
|
||||
with open('unitPayloads.lua', 'w') as f:
|
||||
with open('../unitPayloads.lua', 'w') as f:
|
||||
f.write("Olympus.unitPayloads = " + dump_lua(payloads))
|
||||
|
||||
|
||||
|
||||
# Iterate over the payloads and accumulate the pylon data
|
||||
pylon_usage = {}
|
||||
for unitType, unitPayloads in payloads.items():
|
||||
pylon_usage[unitType] = {}
|
||||
for payloadName, pylons in unitPayloads.items():
|
||||
for pylonID, pylonData in pylons.items():
|
||||
# Keep track of what CLSIDs are used on each pylon
|
||||
clsid = pylonData['CLSID']
|
||||
if pylonID not in pylon_usage[unitType]:
|
||||
pylon_usage[unitType][pylonID] = []
|
||||
if clsid not in pylon_usage[unitType][pylonID]:
|
||||
pylon_usage[unitType][pylonID].append(clsid)
|
||||
|
||||
# Save the pylon usage data to a JSON file
|
||||
with open('pylonUsage.json', 'w') as f:
|
||||
json.dump(pylon_usage, f, ensure_ascii=False, indent=2)
|
||||
@@ -3773,23 +3773,6 @@
|
||||
"1": 29
|
||||
}
|
||||
},
|
||||
"F4U-1D": {
|
||||
"HVAR x 8": {
|
||||
"1": 32,
|
||||
"2": 31,
|
||||
"3": 30
|
||||
},
|
||||
"Bomb x 2, HVAR x 4": {
|
||||
"1": 32,
|
||||
"2": 31,
|
||||
"3": 30
|
||||
},
|
||||
"Tiny Tim x2, HVAR x 4": {
|
||||
"1": 32,
|
||||
"2": 31,
|
||||
"3": 30
|
||||
}
|
||||
},
|
||||
"F/A-18A": {
|
||||
"GBU-16*2,AIM-9*2,AIM-7,FLIR Pod,Fuel*3": {
|
||||
"1": 33
|
||||
@@ -4743,15 +4726,6 @@
|
||||
"AEROBATIC": {}
|
||||
},
|
||||
"Mirage-F1B": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*R550 Magic I, 2*R530IR, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -4807,15 +4781,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1BD": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*R550 Magic I, 2*S530F, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -4865,15 +4830,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1BE": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*AIM9-JULI, 2*R530IR, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -4953,15 +4909,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1BQ": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*R550 Magic I, 2*S530F, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -5011,15 +4958,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1C-200": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*R550 Magic I, 2*R530IR, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -5075,15 +5013,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1C": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*R550 Magic I, 2*R530IR, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -5139,15 +5068,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1CE": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*AIM9-JULI, 2*R530IR, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -5227,15 +5147,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1CG": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*AIM-9 JULI, 2*R530IR, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -5297,15 +5208,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1CH": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*R550 Magic I, 2*R530IR, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -5361,15 +5263,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1CJ": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*R550 Magic I, 2*R530IR, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -5425,15 +5318,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1CK": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*R550 Magic I, 2*R530IR, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -5489,15 +5373,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1CR": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*R550 Magic I": {
|
||||
"1": 10
|
||||
},
|
||||
@@ -5535,15 +5410,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1CT": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*R550 Magic I, 2*S530F, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -5593,15 +5459,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1CZ": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*R550 Magic I, 2*R530IR, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -5657,15 +5514,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1DDA": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*R550 Magic I, 2*S530F, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -5715,15 +5563,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1ED": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*R550 Magic II, 2*S530F, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -5773,15 +5612,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1EDA": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*R550 Magic I, 2*S530F, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -5831,15 +5661,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1EE": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*AIM9-JULI, 2*R530IR, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -5925,15 +5746,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1EH": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*R550 Magic I, 2*R530IR, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -5989,15 +5801,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1EQ": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*R550 Magic I, 2*S530F, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -6047,15 +5850,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1JA": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*R550 Magic I, 2*Python III, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -6102,15 +5896,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1M-CE": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*AIM9-JULI, 2*R530IR, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -6184,15 +5969,6 @@
|
||||
}
|
||||
},
|
||||
"Mirage-F1M-EE": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 19,
|
||||
"5": 31,
|
||||
"6": 32,
|
||||
"7": 34
|
||||
},
|
||||
"2*AIM9-JULI, 2*R530IR, 1*Fuel Tank": {
|
||||
"1": 10,
|
||||
"2": 11,
|
||||
@@ -8369,7 +8145,7 @@
|
||||
"UB-32*2,Fuel*3": {
|
||||
"1": 32
|
||||
},
|
||||
"Kh-59M*2,R-60M*2": {
|
||||
"Kh-59M*2,R-60M*2,Fuel": {
|
||||
"1": 33
|
||||
},
|
||||
"S-25*4": {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1780,8 +1780,8 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[1] = {["CLSID"]="{FC23864E-3B80-48E3-9C03-4DA8B1D7497B}"},
|
||||
[4] = {["CLSID"]="{C-101-DEFA553}"}},
|
||||
["2*AIM-9P, 2*BELOUGA, DEFA 553 CANNON"]={[4] = {["CLSID"]="{C-101-DEFA553}"},
|
||||
[5] = {["CLSID"]="{BLG66_AC}"},
|
||||
[3] = {["CLSID"]="{BLG66_AC}"},
|
||||
[5] = {["CLSID"]="{BLG66_BELOUGA}"},
|
||||
[3] = {["CLSID"]="{BLG66_BELOUGA}"},
|
||||
[7] = {["CLSID"]="{9BFD8C90-F7AE-4e90-833B-BFD0CED0E536}"},
|
||||
[1] = {["CLSID"]="{9BFD8C90-F7AE-4e90-833B-BFD0CED0E536}"}},
|
||||
["2*AIM9-P, 2*SEA EAGLE, DEFA-553 CANNON"]={[7] = {["CLSID"]="{9BFD8C90-F7AE-4e90-833B-BFD0CED0E536}"},
|
||||
@@ -1797,8 +1797,8 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
["2*AIM-9M, AN-M3 CANNON"]={[7] = {["CLSID"]="{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}"},
|
||||
[1] = {["CLSID"]="{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}"},
|
||||
[4] = {["CLSID"]="{AN-M3}"}},
|
||||
["2*BELOUGA,2*BDU-33, DEFA-553 CANNON"]={[6] = {["CLSID"]="{BLG66_AC}"},
|
||||
[2] = {["CLSID"]="{BLG66_AC}"},
|
||||
["2*BELOUGA,2*BDU-33, DEFA-553 CANNON"]={[6] = {["CLSID"]="{BLG66_BELOUGA}"},
|
||||
[2] = {["CLSID"]="{BLG66_BELOUGA}"},
|
||||
[3] = {["CLSID"]="CBLS-200"},
|
||||
[5] = {["CLSID"]="CBLS-200"},
|
||||
[4] = {["CLSID"]="{C-101-DEFA553}"}},
|
||||
@@ -1820,11 +1820,11 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
["2*R.550 MAGIC, DEFA 553 CANNON (IV)"]={[4] = {["CLSID"]="{C-101-DEFA553}"},
|
||||
[1] = {["CLSID"]="{FC23864E-3B80-48E3-9C03-4DA8B1D7497B}"},
|
||||
[7] = {["CLSID"]="{FC23864E-3B80-48E3-9C03-4DA8B1D7497B}"}},
|
||||
["2*BELOUGA, 2*BR-500, DEFA 553 CANNON"]={[2] = {["CLSID"]="{BLG66_AC}"},
|
||||
["2*BELOUGA, 2*BR-500, DEFA 553 CANNON"]={[2] = {["CLSID"]="{BLG66_BELOUGA}"},
|
||||
[3] = {["CLSID"]="BR_500"},
|
||||
[4] = {["CLSID"]="{C-101-DEFA553}"},
|
||||
[5] = {["CLSID"]="BR_500"},
|
||||
[6] = {["CLSID"]="{BLG66_AC}"}},
|
||||
[6] = {["CLSID"]="{BLG66_BELOUGA}"}},
|
||||
["2*AIM-9M, DEFA 553 CANNON (IV)"]={[7] = {["CLSID"]="{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}"},
|
||||
[1] = {["CLSID"]="{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}"},
|
||||
[4] = {["CLSID"]="{C-101-DEFA553}"}},
|
||||
@@ -1837,8 +1837,8 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
["2*AIM-9M ,2*BELOUGA,2*BIN-200, AN-M3 CANNON"]={[4] = {["CLSID"]="{AN-M3}"},
|
||||
[3] = {["CLSID"]="BIN_200"},
|
||||
[5] = {["CLSID"]="BIN_200"},
|
||||
[6] = {["CLSID"]="{BLG66_AC}"},
|
||||
[2] = {["CLSID"]="{BLG66_AC}"},
|
||||
[6] = {["CLSID"]="{BLG66_BELOUGA}"},
|
||||
[2] = {["CLSID"]="{BLG66_BELOUGA}"},
|
||||
[7] = {["CLSID"]="{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}"},
|
||||
[1] = {["CLSID"]="{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}"}},
|
||||
["2*AIM-9M, 2*LAU 68, 2*MK-82, DEFA 553 CANNON"]={[4] = {["CLSID"]="{C-101-DEFA553}"},
|
||||
@@ -3346,13 +3346,13 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[10] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"}},
|
||||
["GUIDED: GBU-8 HOBOS*2, Aim-7E2*3, Aim-9L*4, AN/AVQ-23 Pave Spike*1, ALE-40 (30-60)*1, Sargent Fl. Fuel Tank 600 GAL*1"]={[13] = {["CLSID"]="{GBU_8_B}"},
|
||||
["GUIDED: GBU-8 HOBOS*2, Aim-7E2*3, Aim-9L*4, AN/AVQ-23 Pave Spike*1, ALE-40 (30-60)*1, Sargent Fl. Fuel Tank 600 GAL*1"]={[13] = {["CLSID"]="{HB_F4E_GBU_8}"},
|
||||
[9] = {["CLSID"]="{HB_F4E_AIM-7E-2}"},
|
||||
[8] = {["CLSID"]="{HB_F4E_AIM-7E-2}"},
|
||||
[7] = {["CLSID"]="{F4_SARGENT_TANK_600_GAL}"},
|
||||
[6] = {["CLSID"]="{HB_PAVE_SPIKE_FAST_ON_ADAPTER_IN_AERO7}"},
|
||||
[5] = {["CLSID"]="{HB_F4E_AIM-7E-2}"},
|
||||
[1] = {["CLSID"]="{GBU_8_B}"},
|
||||
[1] = {["CLSID"]="{HB_F4E_GBU_8}"},
|
||||
[14] = {["CLSID"]="{HB_ALE_40_30_60}"},
|
||||
[12] = {["CLSID"]="{AIM-9L}"},
|
||||
[10] = {["CLSID"]="{AIM-9L}"},
|
||||
@@ -3907,15 +3907,15 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[4] = {["CLSID"]="{AIM-9L}"},
|
||||
[2] = {["CLSID"]="{AIM-9L}"},
|
||||
[14] = {["CLSID"]="{HB_ALE_40_30_60}"}},
|
||||
["GUIDED: GBU-8 HOBOS*4, Aim-7E2*3, AN/AVQ-23 Pave Spike*1, ALE-40 (30-60)*1, Sargent Fl. Fuel Tank 600 GAL*1"]={[13] = {["CLSID"]="{GBU_8_B}"},
|
||||
["GUIDED: GBU-8 HOBOS*4, Aim-7E2*3, AN/AVQ-23 Pave Spike*1, ALE-40 (30-60)*1, Sargent Fl. Fuel Tank 600 GAL*1"]={[13] = {["CLSID"]="{HB_F4E_GBU_8}"},
|
||||
[9] = {["CLSID"]="{HB_F4E_AIM-7E-2}"},
|
||||
[8] = {["CLSID"]="{HB_F4E_AIM-7E-2}"},
|
||||
[7] = {["CLSID"]="{F4_SARGENT_TANK_600_GAL}"},
|
||||
[6] = {["CLSID"]="{HB_PAVE_SPIKE_FAST_ON_ADAPTER_IN_AERO7}"},
|
||||
[5] = {["CLSID"]="{HB_F4E_AIM-7E-2}"},
|
||||
[1] = {["CLSID"]="{GBU_8_B}"},
|
||||
[11] = {["CLSID"]="{GBU_8_B}"},
|
||||
[3] = {["CLSID"]="{GBU_8_B}"},
|
||||
[1] = {["CLSID"]="{HB_F4E_GBU_8}"},
|
||||
[11] = {["CLSID"]="{HB_F4E_GBU_8}"},
|
||||
[3] = {["CLSID"]="{HB_F4E_GBU_8}"},
|
||||
[14] = {["CLSID"]="{HB_ALE_40_30_60}"},
|
||||
[12] = {["CLSID"]="<CLEAN>"},
|
||||
[10] = {["CLSID"]="<CLEAN>"},
|
||||
@@ -4504,8 +4504,8 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[6] = {["CLSID"]="{HB_PAVE_SPIKE_FAST_ON_ADAPTER_IN_AERO7}"},
|
||||
[5] = {["CLSID"]="{HB_F4E_AIM-7E-2}"},
|
||||
[1] = {["CLSID"]="{F4_SARGENT_TANK_370_GAL}"},
|
||||
[11] = {["CLSID"]="{GBU_8_B}"},
|
||||
[3] = {["CLSID"]="{GBU_8_B}"},
|
||||
[11] = {["CLSID"]="{HB_F4E_GBU_8}"},
|
||||
[3] = {["CLSID"]="{HB_F4E_GBU_8}"},
|
||||
[14] = {["CLSID"]="{HB_ALE_40_30_60}"},
|
||||
[12] = {["CLSID"]="<CLEAN>"},
|
||||
[10] = {["CLSID"]="<CLEAN>"},
|
||||
@@ -5084,8 +5084,8 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[6] = {["CLSID"]="{HB_PAVE_SPIKE_FAST_ON_ADAPTER_IN_AERO7}"},
|
||||
[5] = {["CLSID"]="{HB_F4E_AIM-7E-2}"},
|
||||
[1] = {["CLSID"]="{34759BBC-AF1E-4AEE-A581-498FF7A6EBCE}"},
|
||||
[11] = {["CLSID"]="{GBU_8_B}"},
|
||||
[3] = {["CLSID"]="{GBU_8_B}"},
|
||||
[11] = {["CLSID"]="{HB_F4E_GBU_8}"},
|
||||
[3] = {["CLSID"]="{HB_F4E_GBU_8}"},
|
||||
[14] = {["CLSID"]="{HB_ALE_40_30_60}"},
|
||||
[12] = {["CLSID"]="<CLEAN>"},
|
||||
[10] = {["CLSID"]="<CLEAN>"},
|
||||
@@ -5131,8 +5131,8 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[1] = {["CLSID"]="{LAU_34_AGM_45A}"},
|
||||
[11] = {["CLSID"]="{GBU_8_B}"},
|
||||
[3] = {["CLSID"]="{GBU_8_B}"}},
|
||||
[11] = {["CLSID"]="{HB_F4E_GBU_8}"},
|
||||
[3] = {["CLSID"]="{HB_F4E_GBU_8}"}},
|
||||
["IRON: M-117*11, Aim-7E2*3, ALQ-131 ECM*1, ALE-40 (30-60)*1, Sargent Fletcher Fuel Tank 370 GAL*2"]={[13] = {["CLSID"]="{F4_SARGENT_TANK_370_GAL_R}"},
|
||||
[9] = {["CLSID"]="{HB_F4E_AIM-7E-2}"},
|
||||
[8] = {["CLSID"]="{HB_F4E_AIM-7E-2}"},
|
||||
@@ -6617,26 +6617,6 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[3] = {["CLSID"]="{F14-300gal}"},
|
||||
[2] = {["CLSID"]="{SHOULDER AIM_54A_Mk60 L}"},
|
||||
[1] = {["CLSID"]="{LAU-138 wtip - AIM-9M}"}}},
|
||||
["F4U-1D"]={["HVAR x 8"]={[1] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[2] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[3] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[4] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[8] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[11] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[10] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[9] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"}},
|
||||
["Bomb x 2, HVAR x 4"]={[7] = {["CLSID"]="{AN-M64}"},
|
||||
[8] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[3] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[4] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[9] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[5] = {["CLSID"]="{AN-M64}"}},
|
||||
["Tiny Tim x2, HVAR x 4"]={[7] = {["CLSID"]="{Tiny_Tim_Corsair}"},
|
||||
[8] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[3] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[4] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[9] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[5] = {["CLSID"]="{Tiny_Tim_Corsair}"}}},
|
||||
["F/A-18A"]={["GBU-16*2,AIM-9*2,AIM-7,FLIR Pod,Fuel*3"]={[1] = {["CLSID"]="{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}"},
|
||||
[2] = {["CLSID"]="{0D33DDAE-524F-4A4E-B5B8-621754FE3ADE}"},
|
||||
[3] = {["CLSID"]="{EFEC8200-B922-11d7-9897-000476191836}"},
|
||||
@@ -8122,14 +8102,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[5] = {["CLSID"]="{R-3S}"},
|
||||
[6] = {["CLSID"]="{ASO-2}"}},
|
||||
["AEROBATIC"]={[7] = {["CLSID"]="{SMOKE_WHITE}"}}},
|
||||
["Mirage-F1B"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*R550 Magic I, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
["Mirage-F1B"]={["2*R550 Magic I, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[1] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[5] = {["CLSID"]="{R530F_IR}"},
|
||||
[3] = {["CLSID"]="{R530F_IR}"},
|
||||
@@ -8220,14 +8193,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[3] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[5] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[4] = {["CLSID"]="{CLB4_BLU107}"}}},
|
||||
["Mirage-F1BD"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*R550 Magic I, 2*S530F, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
["Mirage-F1BD"]={["2*R550 Magic I, 2*S530F, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[1] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[5] = {["CLSID"]="{S530F}"},
|
||||
[3] = {["CLSID"]="{S530F}"},
|
||||
@@ -8311,14 +8277,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[3] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[5] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[4] = {["CLSID"]="{CLB4_BLU107}"}}},
|
||||
["Mirage-F1BE"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*AIM9-JULI, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{AIM-9JULI}"},
|
||||
["Mirage-F1BE"]={["2*AIM9-JULI, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{AIM-9JULI}"},
|
||||
[1] = {["CLSID"]="{AIM-9JULI}"},
|
||||
[5] = {["CLSID"]="{R530F_IR}"},
|
||||
[3] = {["CLSID"]="{R530F_IR}"},
|
||||
@@ -8432,14 +8391,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[5] = {["CLSID"]="{S530F}"},
|
||||
[3] = {["CLSID"]="{S530F}"},
|
||||
[4] = {["CLSID"]="PTB-1200-F1"}}},
|
||||
["Mirage-F1BQ"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*R550 Magic I, 2*S530F, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
["Mirage-F1BQ"]={["2*R550 Magic I, 2*S530F, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[1] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[5] = {["CLSID"]="{S530F}"},
|
||||
[3] = {["CLSID"]="{S530F}"},
|
||||
@@ -8523,14 +8475,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[3] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[5] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[4] = {["CLSID"]="{CLB4_BLU107}"}}},
|
||||
["Mirage-F1C-200"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*R550 Magic I, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
["Mirage-F1C-200"]={["2*R550 Magic I, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[1] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[5] = {["CLSID"]="{R530F_IR}"},
|
||||
[3] = {["CLSID"]="{R530F_IR}"},
|
||||
@@ -8621,14 +8566,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[3] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[5] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[4] = {["CLSID"]="{CLB4_BLU107}"}}},
|
||||
["Mirage-F1C"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*R550 Magic I, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
["Mirage-F1C"]={["2*R550 Magic I, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[1] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[5] = {["CLSID"]="{R530F_IR}"},
|
||||
[3] = {["CLSID"]="{R530F_IR}"},
|
||||
@@ -8719,14 +8657,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[3] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[5] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[4] = {["CLSID"]="{CLB4_BLU107}"}}},
|
||||
["Mirage-F1CE"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*AIM9-JULI, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{AIM-9JULI}"},
|
||||
["Mirage-F1CE"]={["2*AIM9-JULI, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{AIM-9JULI}"},
|
||||
[1] = {["CLSID"]="{AIM-9JULI}"},
|
||||
[5] = {["CLSID"]="{R530F_IR}"},
|
||||
[3] = {["CLSID"]="{R530F_IR}"},
|
||||
@@ -8840,14 +8771,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[5] = {["CLSID"]="{S530F}"},
|
||||
[3] = {["CLSID"]="{S530F}"},
|
||||
[4] = {["CLSID"]="PTB-1200-F1"}}},
|
||||
["Mirage-F1CG"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*AIM-9 JULI, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{AIM-9JULI}"},
|
||||
["Mirage-F1CG"]={["2*AIM-9 JULI, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{AIM-9JULI}"},
|
||||
[1] = {["CLSID"]="{AIM-9JULI}"},
|
||||
[5] = {["CLSID"]="{R530F_IR}"},
|
||||
[3] = {["CLSID"]="{R530F_IR}"},
|
||||
@@ -8945,14 +8869,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[3] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[5] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[4] = {["CLSID"]="{CLB4_BLU107}"}}},
|
||||
["Mirage-F1CH"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*R550 Magic I, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
["Mirage-F1CH"]={["2*R550 Magic I, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[1] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[5] = {["CLSID"]="{R530F_IR}"},
|
||||
[3] = {["CLSID"]="{R530F_IR}"},
|
||||
@@ -9043,14 +8960,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[3] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[5] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[4] = {["CLSID"]="{CLB4_BLU107}"}}},
|
||||
["Mirage-F1CJ"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*R550 Magic I, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
["Mirage-F1CJ"]={["2*R550 Magic I, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[1] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[5] = {["CLSID"]="{R530F_IR}"},
|
||||
[3] = {["CLSID"]="{R530F_IR}"},
|
||||
@@ -9141,14 +9051,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[3] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[5] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[4] = {["CLSID"]="{CLB4_BLU107}"}}},
|
||||
["Mirage-F1CK"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*R550 Magic I, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
["Mirage-F1CK"]={["2*R550 Magic I, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[1] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[5] = {["CLSID"]="{R530F_IR}"},
|
||||
[3] = {["CLSID"]="{R530F_IR}"},
|
||||
@@ -9239,14 +9142,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[3] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[5] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[4] = {["CLSID"]="{CLB4_BLU107}"}}},
|
||||
["Mirage-F1CR"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*R550 Magic I"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
["Mirage-F1CR"]={["2*R550 Magic I"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[1] = {["CLSID"]="{R550_Magic_1}"}},
|
||||
["2*R550_Magic_1, 2*Fuel Tank, 4*SAMP 400 LD"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[1] = {["CLSID"]="{R550_Magic_1}"},
|
||||
@@ -9314,14 +9210,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[3] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[5] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[4] = {["CLSID"]="{CLB4_BLU107}"}}},
|
||||
["Mirage-F1CT"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*R550 Magic I, 2*S530F, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
["Mirage-F1CT"]={["2*R550 Magic I, 2*S530F, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[1] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[5] = {["CLSID"]="{S530F}"},
|
||||
[3] = {["CLSID"]="{S530F}"},
|
||||
@@ -9405,14 +9294,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[3] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[5] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[4] = {["CLSID"]="{CLB4_BLU107}"}}},
|
||||
["Mirage-F1CZ"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*R550 Magic I, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
["Mirage-F1CZ"]={["2*R550 Magic I, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[1] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[5] = {["CLSID"]="{R530F_IR}"},
|
||||
[3] = {["CLSID"]="{R530F_IR}"},
|
||||
@@ -9503,14 +9385,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[3] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[5] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[4] = {["CLSID"]="{CLB4_BLU107}"}}},
|
||||
["Mirage-F1DDA"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*R550 Magic I, 2*S530F, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
["Mirage-F1DDA"]={["2*R550 Magic I, 2*S530F, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[1] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[5] = {["CLSID"]="{S530F}"},
|
||||
[3] = {["CLSID"]="{S530F}"},
|
||||
@@ -9594,14 +9469,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[3] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[5] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[4] = {["CLSID"]="{CLB4_BLU107}"}}},
|
||||
["Mirage-F1ED"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*R550 Magic II, 2*S530F, 1*Fuel Tank"]={[7] = {["CLSID"]="{FC23864E-3B80-48E3-9C03-4DA8B1D7497B}"},
|
||||
["Mirage-F1ED"]={["2*R550 Magic II, 2*S530F, 1*Fuel Tank"]={[7] = {["CLSID"]="{FC23864E-3B80-48E3-9C03-4DA8B1D7497B}"},
|
||||
[1] = {["CLSID"]="{FC23864E-3B80-48E3-9C03-4DA8B1D7497B}"},
|
||||
[5] = {["CLSID"]="{S530F}"},
|
||||
[3] = {["CLSID"]="{S530F}"},
|
||||
@@ -9685,14 +9553,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[3] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[5] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[4] = {["CLSID"]="{CLB4_BLU107}"}}},
|
||||
["Mirage-F1EDA"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*R550 Magic I, 2*S530F, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
["Mirage-F1EDA"]={["2*R550 Magic I, 2*S530F, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[1] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[5] = {["CLSID"]="{S530F}"},
|
||||
[3] = {["CLSID"]="{S530F}"},
|
||||
@@ -9776,14 +9637,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[3] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[5] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[4] = {["CLSID"]="{CLB4_BLU107}"}}},
|
||||
["Mirage-F1EE"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*AIM9-JULI, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{AIM-9JULI}"},
|
||||
["Mirage-F1EE"]={["2*AIM9-JULI, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{AIM-9JULI}"},
|
||||
[1] = {["CLSID"]="{AIM-9JULI}"},
|
||||
[5] = {["CLSID"]="{R530F_IR}"},
|
||||
[3] = {["CLSID"]="{R530F_IR}"},
|
||||
@@ -9903,14 +9757,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[3] = {["CLSID"]="{S530F}"},
|
||||
[4] = {["CLSID"]="PTB-1200-F1"},
|
||||
[6] = {["CLSID"]="BARAX_ECM"}}},
|
||||
["Mirage-F1EH"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*R550 Magic I, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
["Mirage-F1EH"]={["2*R550 Magic I, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[1] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[5] = {["CLSID"]="{R530F_IR}"},
|
||||
[3] = {["CLSID"]="{R530F_IR}"},
|
||||
@@ -10001,14 +9848,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[3] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[5] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[4] = {["CLSID"]="{CLB4_BLU107}"}}},
|
||||
["Mirage-F1EQ"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*R550 Magic I, 2*S530F, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
["Mirage-F1EQ"]={["2*R550 Magic I, 2*S530F, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[1] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[5] = {["CLSID"]="{S530F}"},
|
||||
[3] = {["CLSID"]="{S530F}"},
|
||||
@@ -10092,14 +9932,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[3] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[5] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[4] = {["CLSID"]="{CLB4_BLU107}"}}},
|
||||
["Mirage-F1JA"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*R550 Magic I, 2*Python III, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
["Mirage-F1JA"]={["2*R550 Magic I, 2*Python III, 1*Fuel Tank"]={[7] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[1] = {["CLSID"]="{R550_Magic_1}"},
|
||||
[2] = {["CLSID"]="DIS_PL-8B"},
|
||||
[6] = {["CLSID"]="DIS_PL-8B"},
|
||||
@@ -10183,14 +10016,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[3] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[5] = {["CLSID"]="{BLU107B_DURANDAL}"},
|
||||
[4] = {["CLSID"]="{CLB4_BLU107}"}}},
|
||||
["Mirage-F1M-CE"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*AIM9-JULI, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{AIM-9JULI}"},
|
||||
["Mirage-F1M-CE"]={["2*AIM9-JULI, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{AIM-9JULI}"},
|
||||
[1] = {["CLSID"]="{AIM-9JULI}"},
|
||||
[5] = {["CLSID"]="{R530F_IR}"},
|
||||
[3] = {["CLSID"]="{R530F_IR}"},
|
||||
@@ -10299,14 +10125,7 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[5] = {["CLSID"]="{S530F}"},
|
||||
[3] = {["CLSID"]="{S530F}"},
|
||||
[4] = {["CLSID"]="PTB-1200-F1"}}},
|
||||
["Mirage-F1M-EE"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
[4] = {["CLSID"]="<CLEAN>"},
|
||||
[5] = {["CLSID"]="<CLEAN>"},
|
||||
[6] = {["CLSID"]="<CLEAN>"},
|
||||
[7] = {["CLSID"]="<CLEAN>"}},
|
||||
["2*AIM9-JULI, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{AIM-9JULI}"},
|
||||
["Mirage-F1M-EE"]={["2*AIM9-JULI, 2*R530IR, 1*Fuel Tank"]={[7] = {["CLSID"]="{AIM-9JULI}"},
|
||||
[1] = {["CLSID"]="{AIM-9JULI}"},
|
||||
[5] = {["CLSID"]="{R530F_IR}"},
|
||||
[3] = {["CLSID"]="{R530F_IR}"},
|
||||
@@ -13317,11 +13136,11 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[5] = {["CLSID"]="{16602053-4A12-40A2-B214-AB60D481B20E}"},
|
||||
[7] = {["CLSID"]="{7D7EC917-05F6-49D4-8045-61FC587DD019}"},
|
||||
[8] = {["CLSID"]="{637334E4-AB5A-47C0-83A6-51B7F1DF3CD5}"}},
|
||||
["Kh-59M*2,R-60M*2"]={[1] = {["CLSID"]="{APU-60-1_R_60M}"},
|
||||
["Kh-59M*2,R-60M*2,Fuel"]={[1] = {["CLSID"]="{APU-60-1_R_60M}"},
|
||||
[2] = {["CLSID"]="{40AB87E8-BEFB-4D85-90D9-B2753ACF9514}"},
|
||||
[5] = {["CLSID"]="{16602053-4A12-40A2-B214-AB60D481B20E}"},
|
||||
[7] = {["CLSID"]="{40AB87E8-BEFB-4D85-90D9-B2753ACF9514}"},
|
||||
[8] = {["CLSID"]="{APU-60-1_R_60M}"},
|
||||
[4] = {["CLSID"]="{APK_9}"}},
|
||||
[8] = {["CLSID"]="{APU-60-1_R_60M}"}},
|
||||
["S-25*4"]={[1] = {["CLSID"]="{A0648264-4BC0-4EE8-A543-D119F6BA4257}"},
|
||||
[2] = {["CLSID"]="{A0648264-4BC0-4EE8-A543-D119F6BA4257}"},
|
||||
[7] = {["CLSID"]="{A0648264-4BC0-4EE8-A543-D119F6BA4257}"},
|
||||
Reference in New Issue
Block a user