mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Compare commits
17 Commits
temp
...
release-ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b42280133c | ||
|
|
94d0b4d10e | ||
|
|
057603f926 | ||
|
|
bfd11c49af | ||
|
|
2a9723b932 | ||
|
|
f565b9ee6e | ||
|
|
504c0a0ed9 | ||
|
|
c77173f7c9 | ||
|
|
73af60d91b | ||
|
|
31d7fb6051 | ||
|
|
def15f5565 | ||
|
|
a257afca4b | ||
|
|
dca8f9189f | ||
|
|
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;
|
||||
@@ -175,6 +185,7 @@ struct SpawnOptions {
|
||||
string skill;
|
||||
string liveryID;
|
||||
double heading;
|
||||
string payload;
|
||||
};
|
||||
|
||||
struct CloneOptions {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -102,6 +102,7 @@ string SpawnAircrafts::getString()
|
||||
<< "alt = " << spawnOptions[i].location.alt << ", "
|
||||
<< "heading = " << spawnOptions[i].heading << ", "
|
||||
<< "loadout = \"" << spawnOptions[i].loadout << "\"" << ", "
|
||||
<< "payload = " << spawnOptions[i].payload << ", "
|
||||
<< "liveryID = " << "\"" << spawnOptions[i].liveryID << "\"" << ", "
|
||||
<< "skill = \"" << spawnOptions[i].skill << "\"" << "}, ";
|
||||
}
|
||||
@@ -132,6 +133,7 @@ string SpawnHelicopters::getString()
|
||||
<< "alt = " << spawnOptions[i].location.alt << ", "
|
||||
<< "heading = " << spawnOptions[i].heading << ", "
|
||||
<< "loadout = \"" << spawnOptions[i].loadout << "\"" << ", "
|
||||
<< "payload = " << spawnOptions[i].payload << ", "
|
||||
<< "liveryID = " << "\"" << spawnOptions[i].liveryID << "\"" << ", "
|
||||
<< "skill = \"" << spawnOptions[i].skill << "\"" << "}, ";
|
||||
}
|
||||
@@ -318,4 +320,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,12 @@ 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);
|
||||
string payload = "nil";
|
||||
if (unit.has_string_field(L"payload"))
|
||||
payload = to_string(unit[L"payload"]);
|
||||
|
||||
spawnOptions.push_back({ unitType, location, loadout, skill, liveryID, heading, payload });
|
||||
log(username + " spawned a " + coalition + " " + unitType, true);
|
||||
}
|
||||
|
||||
if (key.compare("spawnAircrafts") == 0)
|
||||
@@ -251,8 +261,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 +408,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 +437,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 +567,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 +592,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 +716,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 +822,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 +832,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()) {
|
||||
@@ -330,6 +344,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 +719,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 +803,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 +813,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; }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
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",
|
||||
|
||||
@@ -360,6 +360,7 @@ export enum OlympusState {
|
||||
MEASURE = "Measure",
|
||||
TRAINING = "Training",
|
||||
ADMIN = "Admin",
|
||||
IMPORT_IMAGE_OVERLAY = "Import image overlay"
|
||||
}
|
||||
|
||||
export const NO_SUBSTATE = "No substate";
|
||||
@@ -398,6 +399,7 @@ export enum SpawnSubState {
|
||||
NO_SUBSTATE = "No substate",
|
||||
SPAWN_UNIT = "Unit",
|
||||
SPAWN_EFFECT = "Effect",
|
||||
LOADOUT_WIZARD = "Loadout wizard"
|
||||
}
|
||||
|
||||
export enum OptionsSubstate {
|
||||
@@ -547,6 +549,10 @@ export enum DataIndexes {
|
||||
aimMethodRange,
|
||||
acquisitionRange,
|
||||
airborne,
|
||||
cargoWeight,
|
||||
drawingArguments,
|
||||
customString,
|
||||
customInteger,
|
||||
endOfData = 255,
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,393 +2,405 @@ import { LatLng } from "leaflet";
|
||||
import { AudioOptions, Coalition, MapOptions } from "./types/types";
|
||||
|
||||
export interface OlympusConfig {
|
||||
/* Set by user */
|
||||
frontend: {
|
||||
port: number;
|
||||
elevationProvider: {
|
||||
provider: string;
|
||||
username: string | null;
|
||||
password: string | null;
|
||||
};
|
||||
mapLayers: {
|
||||
[key: string]: {
|
||||
urlTemplate: string;
|
||||
minZoom: number;
|
||||
maxZoom: number;
|
||||
attribution?: string;
|
||||
};
|
||||
};
|
||||
mapMirrors: {
|
||||
[key: string]: string;
|
||||
/* Set by user */
|
||||
frontend: {
|
||||
port: number;
|
||||
elevationProvider: {
|
||||
provider: string;
|
||||
username: string | null;
|
||||
password: string | null;
|
||||
};
|
||||
mapLayers: {
|
||||
[key: string]: {
|
||||
urlTemplate: string;
|
||||
minZoom: number;
|
||||
maxZoom: number;
|
||||
attribution?: string;
|
||||
};
|
||||
};
|
||||
mapMirrors: {
|
||||
[key: string]: string;
|
||||
};
|
||||
/* New with v2.0.0 */
|
||||
customAuthHeaders?: {
|
||||
enabled: boolean;
|
||||
username: string;
|
||||
group: string;
|
||||
};
|
||||
autoconnectWhenLocal?: boolean;
|
||||
};
|
||||
/* New with v2.0.0 */
|
||||
customAuthHeaders?: {
|
||||
enabled: boolean;
|
||||
username: string;
|
||||
group: string;
|
||||
audio?: {
|
||||
SRSPort: number;
|
||||
WSPort?: number;
|
||||
WSEndpoint?: string;
|
||||
};
|
||||
autoconnectWhenLocal?: boolean;
|
||||
};
|
||||
/* New with v2.0.0 */
|
||||
audio?: {
|
||||
SRSPort: number;
|
||||
WSPort?: number;
|
||||
WSEndpoint?: string;
|
||||
};
|
||||
controllers?: [{ type: string; coalition: Coalition; frequency: number; modulation: number; callsign: string }];
|
||||
profiles?: { [key: string]: ProfileOptions };
|
||||
controllers?: [{ type: string; coalition: Coalition; frequency: number; modulation: number; callsign: string }];
|
||||
profiles?: { [key: string]: ProfileOptions };
|
||||
|
||||
/* Set by server */
|
||||
local?: boolean;
|
||||
authentication?: {
|
||||
// Only sent when in localhost mode for autologin
|
||||
gameMasterPassword: string;
|
||||
blueCommanderPassword: string;
|
||||
redCommanderPassword: string;
|
||||
};
|
||||
/* Set by server */
|
||||
local?: boolean;
|
||||
authentication?: {
|
||||
// Only sent when in localhost mode for autologin
|
||||
gameMasterPassword: string;
|
||||
blueCommanderPassword: string;
|
||||
redCommanderPassword: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SessionData {
|
||||
radios?: { frequency: number; modulation: number; pan: number }[];
|
||||
fileSources?: { filename: string; volume: number }[];
|
||||
unitSinks?: { ID: number }[];
|
||||
connections?: any[];
|
||||
coalitionAreas?: (
|
||||
| { type: "circle"; label: string; latlng: { lat: number; lng: number }; radius: number; coalition: Coalition }
|
||||
| { type: "polygon"; label: string; latlngs: { lat: number; lng: number }[]; coalition: Coalition }
|
||||
)[];
|
||||
hotgroups?: { [key: string]: number[] };
|
||||
starredSpawns?: { [key: number]: SpawnRequestTable };
|
||||
drawings?: { [key: string]: { visibility: boolean; opacity: number; name: string; guid: string; containers: any; drawings: any } };
|
||||
navpoints?: { [key: string]: { visibility: boolean; opacity: number; name: string; guid: string; containers: any; drawings: any } };
|
||||
mapSource?: { id: string };
|
||||
radios?: { frequency: number; modulation: number; pan: number }[];
|
||||
fileSources?: { filename: string; volume: number }[];
|
||||
unitSinks?: { ID: number }[];
|
||||
connections?: any[];
|
||||
coalitionAreas?: (
|
||||
| { type: "circle"; label: string; latlng: { lat: number; lng: number }; radius: number; coalition: Coalition }
|
||||
| { type: "polygon"; label: string; latlngs: { lat: number; lng: number }[]; coalition: Coalition }
|
||||
)[];
|
||||
hotgroups?: { [key: string]: number[] };
|
||||
starredSpawns?: { [key: number]: SpawnRequestTable };
|
||||
drawings?: { [key: string]: { visibility: boolean; opacity: number; name: string; guid: string; containers: any; drawings: any } };
|
||||
navpoints?: { [key: string]: { visibility: boolean; opacity: number; name: string; guid: string; containers: any; drawings: any } };
|
||||
mapSource?: { id: string };
|
||||
customLoadouts?: { [key: string]: LoadoutBlueprint[] };
|
||||
}
|
||||
|
||||
export interface ProfileOptions {
|
||||
mapOptions?: MapOptions;
|
||||
shortcuts?: { [key: string]: ShortcutOptions };
|
||||
audioOptions?: AudioOptions;
|
||||
mapOptions?: MapOptions;
|
||||
shortcuts?: { [key: string]: ShortcutOptions };
|
||||
audioOptions?: AudioOptions;
|
||||
}
|
||||
|
||||
export interface ContextMenuOption {
|
||||
tooltip: string;
|
||||
src: string;
|
||||
callback: CallableFunction;
|
||||
tooltip: string;
|
||||
src: string;
|
||||
callback: CallableFunction;
|
||||
}
|
||||
|
||||
export interface AirbasesData {
|
||||
airbases: { [key: string]: any };
|
||||
sessionHash: string;
|
||||
time: number;
|
||||
airbases: { [key: string]: any };
|
||||
sessionHash: string;
|
||||
time: number;
|
||||
}
|
||||
|
||||
export interface BullseyesData {
|
||||
bullseyes: {
|
||||
[key: string]: { latitude: number; longitude: number; coalition: string };
|
||||
};
|
||||
sessionHash: string;
|
||||
time: number;
|
||||
bullseyes: {
|
||||
[key: string]: { latitude: number; longitude: number; coalition: string };
|
||||
};
|
||||
sessionHash: string;
|
||||
time: number;
|
||||
}
|
||||
|
||||
export interface SpotsData {
|
||||
spots: {
|
||||
[key: string]: { active: boolean; type: string; targetPosition: { lat: number; lng: number }; sourceUnitID: number; code?: number };
|
||||
};
|
||||
sessionHash: string;
|
||||
time: number;
|
||||
spots: {
|
||||
[key: string]: { active: boolean; type: string; targetPosition: { lat: number; lng: number }; sourceUnitID: number; code?: number };
|
||||
};
|
||||
sessionHash: string;
|
||||
time: number;
|
||||
}
|
||||
|
||||
export interface MissionData {
|
||||
mission: {
|
||||
theatre: string;
|
||||
dateAndTime: DateAndTime;
|
||||
commandModeOptions: CommandModeOptions;
|
||||
coalitions: { red: string[]; blue: string[] };
|
||||
};
|
||||
time: number;
|
||||
sessionHash: string;
|
||||
mission: {
|
||||
theatre: string;
|
||||
dateAndTime: DateAndTime;
|
||||
commandModeOptions: CommandModeOptions;
|
||||
coalitions: { red: string[]; blue: string[] };
|
||||
};
|
||||
time: number;
|
||||
sessionHash: string;
|
||||
}
|
||||
|
||||
export interface CommandModeOptions {
|
||||
commandMode: string;
|
||||
restrictSpawns: boolean;
|
||||
restrictToCoalition: boolean;
|
||||
setupTime: number;
|
||||
spawnPoints: {
|
||||
red: number;
|
||||
blue: number;
|
||||
};
|
||||
eras: string[];
|
||||
commandMode: string;
|
||||
restrictSpawns: boolean;
|
||||
restrictToCoalition: boolean;
|
||||
setupTime: number;
|
||||
spawnPoints: {
|
||||
red: number;
|
||||
blue: number;
|
||||
};
|
||||
eras: string[];
|
||||
}
|
||||
|
||||
export interface DateAndTime {
|
||||
date: { Year: number; Month: number; Day: number };
|
||||
time: { h: number; m: number; s: number };
|
||||
elapsedTime: number;
|
||||
startTime: number;
|
||||
date: { Year: number; Month: number; Day: number };
|
||||
time: { h: number; m: number; s: number };
|
||||
elapsedTime: number;
|
||||
startTime: number;
|
||||
}
|
||||
|
||||
export interface LogData {
|
||||
logs: { [key: string]: string };
|
||||
sessionHash: string;
|
||||
time: number;
|
||||
logs: { [key: string]: string };
|
||||
sessionHash: string;
|
||||
time: number;
|
||||
}
|
||||
|
||||
export interface ServerRequestOptions {
|
||||
time?: number;
|
||||
commandHash?: string;
|
||||
time?: number;
|
||||
commandHash?: string;
|
||||
}
|
||||
|
||||
export interface SpawnRequestTable {
|
||||
category: string;
|
||||
coalition: string;
|
||||
unit: UnitSpawnTable;
|
||||
amount: number;
|
||||
quickAccessName?: string;
|
||||
category: string;
|
||||
coalition: string;
|
||||
unit: UnitSpawnTable;
|
||||
amount: number;
|
||||
quickAccessName?: string;
|
||||
}
|
||||
|
||||
export interface EffectRequestTable {
|
||||
type: string;
|
||||
explosionType?: string;
|
||||
smokeColor?: string;
|
||||
type: string;
|
||||
explosionType?: string;
|
||||
smokeColor?: string;
|
||||
}
|
||||
|
||||
export interface UnitSpawnTable {
|
||||
unitType: string;
|
||||
location: LatLng;
|
||||
skill: string;
|
||||
liveryID: string;
|
||||
altitude?: number;
|
||||
loadout?: string;
|
||||
heading?: number;
|
||||
unitType: string;
|
||||
location: LatLng;
|
||||
skill: string;
|
||||
liveryID: string;
|
||||
altitude?: number;
|
||||
loadout?: string;
|
||||
heading?: number;
|
||||
payload?: string;
|
||||
}
|
||||
|
||||
export interface ObjectIconOptions {
|
||||
showState: boolean;
|
||||
showVvi: boolean;
|
||||
showHealth: boolean;
|
||||
showHotgroup: boolean;
|
||||
showUnitIcon: boolean;
|
||||
showShortLabel: boolean;
|
||||
showFuel: boolean;
|
||||
showAmmo: boolean;
|
||||
showSummary: boolean;
|
||||
showCallsign: boolean;
|
||||
rotateToHeading: boolean;
|
||||
showCluster: boolean;
|
||||
showAlarmState: boolean;
|
||||
showState: boolean;
|
||||
showVvi: boolean;
|
||||
showHealth: boolean;
|
||||
showHotgroup: boolean;
|
||||
showUnitIcon: boolean;
|
||||
showShortLabel: boolean;
|
||||
showFuel: boolean;
|
||||
showAmmo: boolean;
|
||||
showSummary: boolean;
|
||||
showCallsign: boolean;
|
||||
rotateToHeading: boolean;
|
||||
showCluster: boolean;
|
||||
showAlarmState: boolean;
|
||||
}
|
||||
|
||||
export interface GeneralSettings {
|
||||
prohibitJettison: boolean;
|
||||
prohibitAA: boolean;
|
||||
prohibitAG: boolean;
|
||||
prohibitAfterburner: boolean;
|
||||
prohibitAirWpn: boolean;
|
||||
prohibitJettison: boolean;
|
||||
prohibitAA: boolean;
|
||||
prohibitAG: boolean;
|
||||
prohibitAfterburner: boolean;
|
||||
prohibitAirWpn: boolean;
|
||||
}
|
||||
|
||||
export interface TACAN {
|
||||
isOn: boolean;
|
||||
channel: number;
|
||||
XY: string;
|
||||
callsign: string;
|
||||
isOn: boolean;
|
||||
channel: number;
|
||||
XY: string;
|
||||
callsign: string;
|
||||
}
|
||||
|
||||
export interface Radio {
|
||||
frequency: number;
|
||||
callsign: number;
|
||||
callsignNumber: number;
|
||||
frequency: number;
|
||||
callsign: number;
|
||||
callsignNumber: number;
|
||||
}
|
||||
|
||||
export interface Ammo {
|
||||
quantity: number;
|
||||
name: string;
|
||||
guidance: number;
|
||||
category: number;
|
||||
missileCategory: number;
|
||||
quantity: number;
|
||||
name: string;
|
||||
guidance: number;
|
||||
category: number;
|
||||
missileCategory: number;
|
||||
}
|
||||
|
||||
export interface Contact {
|
||||
ID: number;
|
||||
detectionMethod: number;
|
||||
ID: number;
|
||||
detectionMethod: number;
|
||||
}
|
||||
|
||||
export interface Offset {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
}
|
||||
|
||||
export interface DrawingArgument {
|
||||
argument: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface UnitData {
|
||||
category: string;
|
||||
markerCategory: string;
|
||||
ID: number;
|
||||
alive: boolean;
|
||||
alarmState: AlarmState;
|
||||
human: boolean;
|
||||
controlled: boolean;
|
||||
coalition: string;
|
||||
country: number;
|
||||
name: string;
|
||||
unitName: string;
|
||||
callsign: string;
|
||||
unitID: number;
|
||||
groupID: number;
|
||||
groupName: string;
|
||||
state: string;
|
||||
task: string;
|
||||
hasTask: boolean;
|
||||
position: LatLng;
|
||||
speed: number;
|
||||
horizontalVelocity: number;
|
||||
verticalVelocity: number;
|
||||
heading: number;
|
||||
track: number;
|
||||
isActiveTanker: boolean;
|
||||
isActiveAWACS: boolean;
|
||||
onOff: boolean;
|
||||
followRoads: boolean;
|
||||
fuel: number;
|
||||
desiredSpeed: number;
|
||||
desiredSpeedType: string;
|
||||
desiredAltitude: number;
|
||||
desiredAltitudeType: string;
|
||||
leaderID: number;
|
||||
formationOffset: Offset;
|
||||
targetID: number;
|
||||
targetPosition: LatLng;
|
||||
ROE: string;
|
||||
reactionToThreat: string;
|
||||
emissionsCountermeasures: string;
|
||||
TACAN: TACAN;
|
||||
radio: Radio;
|
||||
generalSettings: GeneralSettings;
|
||||
ammo: Ammo[];
|
||||
contacts: Contact[];
|
||||
activePath: LatLng[];
|
||||
isLeader: boolean;
|
||||
operateAs: string;
|
||||
shotsScatter: number;
|
||||
shotsIntensity: number;
|
||||
health: number;
|
||||
racetrackLength: number;
|
||||
racetrackAnchor: LatLng;
|
||||
racetrackBearing: number;
|
||||
timeToNextTasking: number;
|
||||
barrelHeight: number;
|
||||
muzzleVelocity: number;
|
||||
aimTime: number;
|
||||
shotsToFire: number;
|
||||
shotsBaseInterval: number;
|
||||
shotsBaseScatter: number;
|
||||
engagementRange: number;
|
||||
targetingRange: number;
|
||||
aimMethodRange: number;
|
||||
acquisitionRange: number;
|
||||
airborne: boolean;
|
||||
category: string;
|
||||
markerCategory: string;
|
||||
ID: number;
|
||||
alive: boolean;
|
||||
alarmState: AlarmState;
|
||||
human: boolean;
|
||||
controlled: boolean;
|
||||
coalition: string;
|
||||
country: number;
|
||||
name: string;
|
||||
unitName: string;
|
||||
callsign: string;
|
||||
unitID: number;
|
||||
groupID: number;
|
||||
groupName: string;
|
||||
state: string;
|
||||
task: string;
|
||||
hasTask: boolean;
|
||||
position: LatLng;
|
||||
speed: number;
|
||||
horizontalVelocity: number;
|
||||
verticalVelocity: number;
|
||||
heading: number;
|
||||
track: number;
|
||||
isActiveTanker: boolean;
|
||||
isActiveAWACS: boolean;
|
||||
onOff: boolean;
|
||||
followRoads: boolean;
|
||||
fuel: number;
|
||||
desiredSpeed: number;
|
||||
desiredSpeedType: string;
|
||||
desiredAltitude: number;
|
||||
desiredAltitudeType: string;
|
||||
leaderID: number;
|
||||
formationOffset: Offset;
|
||||
targetID: number;
|
||||
targetPosition: LatLng;
|
||||
ROE: string;
|
||||
reactionToThreat: string;
|
||||
emissionsCountermeasures: string;
|
||||
TACAN: TACAN;
|
||||
radio: Radio;
|
||||
generalSettings: GeneralSettings;
|
||||
ammo: Ammo[];
|
||||
contacts: Contact[];
|
||||
activePath: LatLng[];
|
||||
isLeader: boolean;
|
||||
operateAs: string;
|
||||
shotsScatter: number;
|
||||
shotsIntensity: number;
|
||||
health: number;
|
||||
racetrackLength: number;
|
||||
racetrackAnchor: LatLng;
|
||||
racetrackBearing: number;
|
||||
timeToNextTasking: number;
|
||||
barrelHeight: number;
|
||||
muzzleVelocity: number;
|
||||
aimTime: number;
|
||||
shotsToFire: number;
|
||||
shotsBaseInterval: number;
|
||||
shotsBaseScatter: number;
|
||||
engagementRange: number;
|
||||
targetingRange: number;
|
||||
aimMethodRange: number;
|
||||
acquisitionRange: number;
|
||||
airborne: boolean;
|
||||
cargoWeight: number;
|
||||
drawingArguments: DrawingArgument[];
|
||||
customString: string;
|
||||
customInteger: number;
|
||||
}
|
||||
|
||||
export interface LoadoutItemBlueprint {
|
||||
name: string;
|
||||
quantity: number;
|
||||
effectiveAgainst?: string;
|
||||
name: string;
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
export interface LoadoutBlueprint {
|
||||
fuel: number;
|
||||
items: LoadoutItemBlueprint[];
|
||||
roles: string[];
|
||||
code: string;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
items: LoadoutItemBlueprint[];
|
||||
roles: string[];
|
||||
code: string;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
isCustom?: boolean;
|
||||
persistent?: boolean;
|
||||
payload?: string;
|
||||
}
|
||||
|
||||
export interface UnitBlueprint {
|
||||
name: string;
|
||||
category: string;
|
||||
enabled: boolean;
|
||||
coalition: string;
|
||||
era: string;
|
||||
label: string;
|
||||
shortLabel: string;
|
||||
roles?: string[];
|
||||
type?: string;
|
||||
loadouts?: LoadoutBlueprint[];
|
||||
acceptedPayloads?: { [key: string]: { clsids: string[]; names: string[] } };
|
||||
filename?: string;
|
||||
liveries?: { [key: string]: { name: string; countries: string[] } };
|
||||
cost?: number;
|
||||
barrelHeight?: number;
|
||||
muzzleVelocity?: number;
|
||||
aimTime?: number;
|
||||
shotsToFire?: number;
|
||||
shotsBaseInterval?: number;
|
||||
shotsBaseScatter?: number;
|
||||
description?: string;
|
||||
abilities?: string;
|
||||
tags?: string;
|
||||
acquisitionRange?: number;
|
||||
engagementRange?: number;
|
||||
targetingRange?: number;
|
||||
aimMethodRange?: number;
|
||||
alertnessTimeConstant?: number;
|
||||
canTargetPoint?: boolean;
|
||||
canRearm?: boolean;
|
||||
canAAA?: boolean;
|
||||
indirectFire?: boolean;
|
||||
markerFile?: string;
|
||||
unitWhenGrouped?: string;
|
||||
mainRole?: string;
|
||||
length?: number;
|
||||
carrierFilename?: string;
|
||||
name: string;
|
||||
category: string;
|
||||
enabled: boolean;
|
||||
coalition: string;
|
||||
era: string;
|
||||
label: string;
|
||||
shortLabel: string;
|
||||
roles?: string[];
|
||||
type?: string;
|
||||
loadouts?: LoadoutBlueprint[];
|
||||
acceptedPayloads?: { [key: string]: { clsid: string; name: string; weight: number }[] };
|
||||
filename?: string;
|
||||
liveries?: { [key: string]: { name: string; countries: string[] } };
|
||||
cost?: number;
|
||||
barrelHeight?: number;
|
||||
muzzleVelocity?: number;
|
||||
aimTime?: number;
|
||||
shotsToFire?: number;
|
||||
shotsBaseInterval?: number;
|
||||
shotsBaseScatter?: number;
|
||||
description?: string;
|
||||
abilities?: string;
|
||||
tags?: string;
|
||||
acquisitionRange?: number;
|
||||
engagementRange?: number;
|
||||
targetingRange?: number;
|
||||
aimMethodRange?: number;
|
||||
alertnessTimeConstant?: number;
|
||||
canTargetPoint?: boolean;
|
||||
canRearm?: boolean;
|
||||
canAAA?: boolean;
|
||||
indirectFire?: boolean;
|
||||
markerFile?: string;
|
||||
unitWhenGrouped?: string;
|
||||
mainRole?: string;
|
||||
length?: number;
|
||||
carrierFilename?: string;
|
||||
}
|
||||
|
||||
export interface AirbaseOptions {
|
||||
name: string;
|
||||
position: L.LatLng;
|
||||
name: string;
|
||||
position: L.LatLng;
|
||||
}
|
||||
|
||||
export interface AirbaseChartData {
|
||||
elevation: string;
|
||||
ICAO: string;
|
||||
TACAN: string;
|
||||
runways: AirbaseChartRunwayData[];
|
||||
elevation: string;
|
||||
ICAO: string;
|
||||
TACAN: string;
|
||||
runways: AirbaseChartRunwayData[];
|
||||
}
|
||||
|
||||
export interface AirbaseChartRunwayHeadingData {
|
||||
[index: string]: {
|
||||
magHeading: string;
|
||||
ILS: string;
|
||||
};
|
||||
[index: string]: {
|
||||
magHeading: string;
|
||||
ILS: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AirbaseChartRunwayData {
|
||||
headings: AirbaseChartRunwayHeadingData[];
|
||||
length: string;
|
||||
headings: AirbaseChartRunwayHeadingData[];
|
||||
length: string;
|
||||
}
|
||||
|
||||
export interface ShortcutOptions {
|
||||
label: string;
|
||||
keyUpCallback: (e: KeyboardEvent) => void;
|
||||
keyDownCallback?: (e: KeyboardEvent) => void;
|
||||
code: string;
|
||||
altKey?: boolean;
|
||||
ctrlKey?: boolean;
|
||||
shiftKey?: boolean;
|
||||
label: string;
|
||||
keyUpCallback: (e: KeyboardEvent) => void;
|
||||
keyDownCallback?: (e: KeyboardEvent) => void;
|
||||
code: string;
|
||||
altKey?: boolean;
|
||||
ctrlKey?: boolean;
|
||||
shiftKey?: boolean;
|
||||
}
|
||||
|
||||
export interface ServerStatus {
|
||||
frameRate: number;
|
||||
load: number;
|
||||
elapsedTime: number;
|
||||
missionTime: DateAndTime["time"];
|
||||
connected: boolean;
|
||||
paused: boolean;
|
||||
frameRate: number;
|
||||
load: number;
|
||||
elapsedTime: number;
|
||||
missionTime: DateAndTime["time"];
|
||||
connected: boolean;
|
||||
paused: boolean;
|
||||
}
|
||||
|
||||
export type DrawingPoint = {
|
||||
x: number;
|
||||
y: number;
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
|
||||
export type PolygonPoints = DrawingPoint[] | DrawingPoint;
|
||||
@@ -396,36 +408,36 @@ export type PolygonPoints = DrawingPoint[] | DrawingPoint;
|
||||
export type DrawingPrimitiveType = "TextBox" | "Polygon" | "Line" | "Icon";
|
||||
|
||||
export interface Drawing {
|
||||
name: string;
|
||||
visible: boolean;
|
||||
mapX: number;
|
||||
mapY: number;
|
||||
layerName: string;
|
||||
layer: string;
|
||||
primitiveType: DrawingPrimitiveType;
|
||||
colorString: string;
|
||||
fillColorString?: string;
|
||||
borderThickness?: number;
|
||||
fontSize?: number;
|
||||
font?: string;
|
||||
text?: string;
|
||||
angle?: number;
|
||||
radius?: number;
|
||||
points?: PolygonPoints;
|
||||
style?: string;
|
||||
polygonMode?: string;
|
||||
thickness?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
closed?: boolean;
|
||||
lineMode?: string;
|
||||
hiddenOnPlanner?: boolean;
|
||||
file?: string;
|
||||
scale?: number;
|
||||
name: string;
|
||||
visible: boolean;
|
||||
mapX: number;
|
||||
mapY: number;
|
||||
layerName: string;
|
||||
layer: string;
|
||||
primitiveType: DrawingPrimitiveType;
|
||||
colorString: string;
|
||||
fillColorString?: string;
|
||||
borderThickness?: number;
|
||||
fontSize?: number;
|
||||
font?: string;
|
||||
text?: string;
|
||||
angle?: number;
|
||||
radius?: number;
|
||||
points?: PolygonPoints;
|
||||
style?: string;
|
||||
polygonMode?: string;
|
||||
thickness?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
closed?: boolean;
|
||||
lineMode?: string;
|
||||
hiddenOnPlanner?: boolean;
|
||||
file?: string;
|
||||
scale?: number;
|
||||
}
|
||||
|
||||
export enum AlarmState {
|
||||
RED = 'red',
|
||||
GREEN = 'green',
|
||||
AUTO = 'auto'
|
||||
RED = "red",
|
||||
GREEN = "green",
|
||||
AUTO = "auto",
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -443,7 +443,7 @@ export class Map extends L.Map {
|
||||
ctrlKey: false,
|
||||
});
|
||||
|
||||
for (let contextActionName in ContextActions) {
|
||||
for (const contextActionName of Object.keys(ContextActions) as Array<keyof typeof ContextActions>) {
|
||||
const contextAction = ContextActions[contextActionName] as ContextAction;
|
||||
if (contextAction.getOptions().code) {
|
||||
getApp()
|
||||
@@ -560,10 +560,15 @@ export class Map extends L.Map {
|
||||
}
|
||||
})
|
||||
.then((res: any) => {
|
||||
if ("alt-" + theatre.toLowerCase() in res) {
|
||||
let template = `${mirror}/alt-${theatre.toLowerCase()}/{z}/{x}/{y}.png`;
|
||||
// Convert the result keys to lower case to avoid case sensitivity issues
|
||||
let key = undefined;
|
||||
if ("alt-" + theatre.toLowerCase() in res) key = "alt-" + theatre.toLowerCase();
|
||||
else if ("alt-" + theatre in res) key = "alt-" + theatre;
|
||||
|
||||
if (key) {
|
||||
let template = `${mirror}/${key}/{z}/{x}/{y}.png`;
|
||||
layers.push(
|
||||
...res["alt-" + theatre.toLowerCase()].map((layerConfig: any) => {
|
||||
...res[key].map((layerConfig: any) => {
|
||||
return new L.TileLayer(template, {
|
||||
...layerConfig,
|
||||
crossOrigin: "",
|
||||
@@ -626,13 +631,13 @@ export class Map extends L.Map {
|
||||
return this.#spawnHeading;
|
||||
}
|
||||
|
||||
addStarredSpawnRequestTable(key, spawnRequestTable: SpawnRequestTable, quickAccessName: string) {
|
||||
addStarredSpawnRequestTable(key: string, spawnRequestTable: SpawnRequestTable, quickAccessName: string) {
|
||||
this.#starredSpawnRequestTables[key] = spawnRequestTable;
|
||||
this.#starredSpawnRequestTables[key].quickAccessName = quickAccessName;
|
||||
StarredSpawnsChangedEvent.dispatch(this.#starredSpawnRequestTables);
|
||||
}
|
||||
|
||||
removeStarredSpawnRequestTable(key) {
|
||||
removeStarredSpawnRequestTable(key: string) {
|
||||
if (key in this.#starredSpawnRequestTables) delete this.#starredSpawnRequestTables[key];
|
||||
StarredSpawnsChangedEvent.dispatch(this.#starredSpawnRequestTables);
|
||||
}
|
||||
@@ -673,7 +678,7 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
setHiddenType(key: string, value: boolean) {
|
||||
this.#hiddenTypes[key] = value;
|
||||
this.#hiddenTypes[key as keyof MapHiddenTypes] = value;
|
||||
HiddenTypesChangedEvent.dispatch(this.#hiddenTypes);
|
||||
}
|
||||
|
||||
@@ -783,13 +788,13 @@ export class Map extends L.Map {
|
||||
return smokeMarker;
|
||||
}
|
||||
|
||||
setOption(key, value) {
|
||||
setOption<K extends keyof MapOptions>(key: K, value: MapOptions[K]) {
|
||||
this.#options[key] = value;
|
||||
MapOptionsChangedEvent.dispatch(this.#options, key);
|
||||
MapOptionsChangedEvent.dispatch(this.#options, key as keyof MapOptions);
|
||||
}
|
||||
|
||||
setOptions(options) {
|
||||
this.#options = { ...options };
|
||||
setOptions(options: Partial<MapOptions>) {
|
||||
this.#options = { ...this.#options, ...options } as MapOptions;
|
||||
MapOptionsChangedEvent.dispatch(this.#options);
|
||||
}
|
||||
|
||||
@@ -1066,7 +1071,7 @@ export class Map extends L.Map {
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
(hash) => {
|
||||
(hash: string) => {
|
||||
this.addTemporaryMarker(
|
||||
e.latlng,
|
||||
this.#spawnRequestTable?.unit.unitType ?? "unknown",
|
||||
@@ -1234,7 +1239,7 @@ export class Map extends L.Map {
|
||||
this.#lastMouseCoordinates = e.latlng;
|
||||
|
||||
MouseMovedEvent.dispatch(e.latlng);
|
||||
getGroundElevation(e.latlng, (elevation) => {
|
||||
getGroundElevation(e.latlng, (elevation: number) => {
|
||||
MouseMovedEvent.dispatch(e.latlng, elevation);
|
||||
});
|
||||
|
||||
@@ -1361,8 +1366,8 @@ export class Map extends L.Map {
|
||||
.filter((unit) => !unit.getHuman());
|
||||
|
||||
Object.keys(this.#destinationPreviewMarkers).forEach((ID) => {
|
||||
this.#destinationPreviewMarkers[ID].removeFrom(this);
|
||||
delete this.#destinationPreviewMarkers[ID];
|
||||
this.#destinationPreviewMarkers[parseInt(ID)].removeFrom(this);
|
||||
delete this.#destinationPreviewMarkers[parseInt(ID)];
|
||||
});
|
||||
|
||||
if (this.#keepRelativePositions) {
|
||||
@@ -1380,7 +1385,7 @@ export class Map extends L.Map {
|
||||
#moveDestinationPreviewMarkers() {
|
||||
if (this.#keepRelativePositions) {
|
||||
Object.entries(getApp().getUnitsManager().computeGroupDestination(this.#destinationRotationCenter, this.#destinationRotation)).forEach(([ID, latlng]) => {
|
||||
this.#destinationPreviewMarkers[ID]?.setLatLng(latlng);
|
||||
this.#destinationPreviewMarkers[parseInt(ID)]?.setLatLng(latlng);
|
||||
});
|
||||
} else {
|
||||
Object.values(this.#destinationPreviewMarkers).forEach((marker) => {
|
||||
|
||||
@@ -310,11 +310,13 @@ export class OlympusApp {
|
||||
}
|
||||
|
||||
setState(state: OlympusState, subState: OlympusSubState = NO_SUBSTATE) {
|
||||
const previousState = this.#state;
|
||||
const previousSubState = this.#subState;
|
||||
this.#state = state;
|
||||
this.#subState = subState;
|
||||
|
||||
console.log(`App state set to ${state}, substate ${subState}`);
|
||||
AppStateChangedEvent.dispatch(state, subState);
|
||||
AppStateChangedEvent.dispatch(state, subState, previousState, previousSubState);
|
||||
}
|
||||
|
||||
getState() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
AudioSinksChangedEvent,
|
||||
AudioSourcesChangedEvent,
|
||||
CoalitionAreasChangedEvent,
|
||||
CustomLoadoutsUpdatedEvent,
|
||||
DrawingsUpdatedEvent,
|
||||
HotgroupsChangedEvent,
|
||||
MapSourceChangedEvent,
|
||||
@@ -16,7 +17,7 @@ import {
|
||||
SessionDataSavedEvent,
|
||||
StarredSpawnsChangedEvent,
|
||||
} from "./events";
|
||||
import { SessionData } from "./interfaces";
|
||||
import { LoadoutBlueprint, SessionData } from "./interfaces";
|
||||
import { CoalitionCircle } from "./map/coalitionarea/coalitioncircle";
|
||||
import { CoalitionPolygon } from "./map/coalitionarea/coalitionpolygon";
|
||||
import { getApp } from "./olympusapp";
|
||||
@@ -124,7 +125,7 @@ export class SessionDataManager {
|
||||
HotgroupsChangedEvent.on((hotgroups) => {
|
||||
this.#sessionData.hotgroups = {};
|
||||
Object.keys(hotgroups).forEach((hotgroup) => {
|
||||
(this.#sessionData.hotgroups as { [key: string]: number[] })[hotgroup] = hotgroups[hotgroup].map((unit) => unit.ID);
|
||||
(this.#sessionData.hotgroups as { [key: string]: number[] })[hotgroup] = hotgroups[parseInt(hotgroup)].map((unit) => unit.ID);
|
||||
});
|
||||
this.#saveSessionData();
|
||||
});
|
||||
@@ -146,6 +147,16 @@ export class SessionDataManager {
|
||||
this.#sessionData.mapSource = { id: source };
|
||||
this.#saveSessionData();
|
||||
});
|
||||
|
||||
CustomLoadoutsUpdatedEvent.on((unitName, loadout) => {
|
||||
// If the loadout is of type isPersistent, update the session data
|
||||
if (loadout.persistent) {
|
||||
if (!this.#sessionData.customLoadouts) this.#sessionData.customLoadouts = {};
|
||||
if (!this.#sessionData.customLoadouts[unitName]) this.#sessionData.customLoadouts[unitName] = [];
|
||||
this.#sessionData.customLoadouts[unitName].push({...loadout});
|
||||
}
|
||||
this.#saveSessionData();
|
||||
});
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -156,8 +156,8 @@ export function OlDropdown(props: {
|
||||
data-open={open}
|
||||
className={`
|
||||
absolute z-40 divide-y divide-gray-100 overflow-y-scroll
|
||||
no-scrollbar rounded-lg bg-white p-2 shadow
|
||||
dark:bg-gray-700
|
||||
no-scrollbar rounded-lg border border-2 border-gray-600 bg-gray-700
|
||||
p-2 shadow
|
||||
data-[open='false']:hidden
|
||||
`}
|
||||
>
|
||||
@@ -187,7 +187,7 @@ export function OlDropdown(props: {
|
||||
}
|
||||
|
||||
/* Conveniency Component for dropdown elements */
|
||||
export function OlDropdownItem(props: { onClick?: () => void; className?: string; borderColor?: string; children?: string | JSX.Element | JSX.Element[] }) {
|
||||
export function OlDropdownItem(props: { onClick?: () => void; className?: string; borderColor?: string; children?: string | JSX.Element | JSX.Element[], disabled?: boolean }) {
|
||||
return (
|
||||
<button
|
||||
onClick={props.onClick ?? (() => {})}
|
||||
@@ -195,8 +195,11 @@ export function OlDropdownItem(props: { onClick?: () => void; className?: string
|
||||
${props.className ?? ""}
|
||||
flex w-full cursor-pointer select-none flex-row content-center
|
||||
rounded-md px-4 py-2
|
||||
dark:hover:bg-gray-600 dark:hover:text-white
|
||||
hover:bg-gray-100
|
||||
hover:bg-gray-600 hover:text-white
|
||||
${props.disabled ? `
|
||||
cursor-default opacity-50
|
||||
hover:bg-transparent hover:text-gray-200
|
||||
` : ``}
|
||||
`}
|
||||
style={{
|
||||
border: props.borderColor ? `2px solid ${props.borderColor}` : "2px solid transparent",
|
||||
|
||||
@@ -8,6 +8,7 @@ export function OlNumberInput(props: {
|
||||
max: number;
|
||||
minLength?: number;
|
||||
className?: string;
|
||||
internalClassName?: string;
|
||||
tooltip?: string | (() => JSX.Element | JSX.Element[]);
|
||||
tooltipPosition?: string;
|
||||
tooltipRelativeToParent?: boolean;
|
||||
@@ -34,7 +35,10 @@ export function OlNumberInput(props: {
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className="relative flex max-w-[8rem] items-center"
|
||||
className={`
|
||||
relative flex max-w-[8rem] items-center
|
||||
${props.internalClassName ?? ""}
|
||||
`}
|
||||
ref={buttonRef}
|
||||
onMouseEnter={() => {
|
||||
setHoverTimeout(
|
||||
|
||||
@@ -8,8 +8,9 @@ export function Modal(props: {
|
||||
open: boolean;
|
||||
children?: JSX.Element | JSX.Element[];
|
||||
className?: string;
|
||||
size?: "sm" | "md" | "lg" | "full";
|
||||
size?: "sm" | "md" | "lg" | "full" | "tall";
|
||||
disableClose?: boolean;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const [splash, setSplash] = useState(Math.ceil(Math.random() * 7));
|
||||
|
||||
@@ -54,6 +55,14 @@ export function Modal(props: {
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
props.size === "tall"
|
||||
? `
|
||||
h-[80%] w-[800px]
|
||||
max-md:h-full max-md:w-full
|
||||
`
|
||||
: ""
|
||||
}
|
||||
${props.size === "full" ? "h-full w-full" : ""}
|
||||
`}
|
||||
>
|
||||
@@ -90,7 +99,7 @@ export function Modal(props: {
|
||||
>
|
||||
<FaXmark
|
||||
onClick={() => {
|
||||
getApp().setState(OlympusState.IDLE);
|
||||
props.onClose ? props.onClose() : getApp().setState(OlympusState.IDLE);
|
||||
}}
|
||||
/>{" "}
|
||||
</div>
|
||||
|
||||
180
frontend/react/src/ui/modals/imageoverlaymodal.tsx
Normal file
180
frontend/react/src/ui/modals/imageoverlaymodal.tsx
Normal file
@@ -0,0 +1,180 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Modal } from "./components/modal";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faArrowRight } from "@fortawesome/free-solid-svg-icons";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { NO_SUBSTATE, OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent } from "../../events";
|
||||
import { ImageOverlay, LatLng, LatLngBounds } from "leaflet";
|
||||
import { OlNumberInput } from "../components/olnumberinput";
|
||||
import { OlStringInput } from "../components/olstringinput";
|
||||
|
||||
export function ImageOverlayModal(props: { open: boolean }) {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [appSubState, setAppSubState] = useState(NO_SUBSTATE);
|
||||
const [bound1Lat, setBound1Lat] = useState("0");
|
||||
const [bound1Lon, setBound1Lon] = useState("0");
|
||||
const [bound2Lat, setBound2Lat] = useState("0");
|
||||
const [bound2Lon, setBound2Lon] = useState("0");
|
||||
const [importData, setImportData] = useState("");
|
||||
const [showWarning, setShowWarning] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((appState, appSubState) => {
|
||||
setAppState(appState);
|
||||
setAppSubState(appSubState);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (appState !== OlympusState.IMPORT_IMAGE_OVERLAY) return;
|
||||
|
||||
setImportData("");
|
||||
var input = document.createElement("input");
|
||||
input.type = "file";
|
||||
|
||||
input.onchange = async (e) => {
|
||||
// @ts-ignore TODO
|
||||
var file = e.target?.files[0];
|
||||
var reader = new FileReader();
|
||||
// Read the file content as image data URL
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = (readerEvent) => {
|
||||
// @ts-ignore TODO
|
||||
var content = readerEvent.target.result;
|
||||
if (content) {
|
||||
setImportData(content as string);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
input.click();
|
||||
}, [appState, appSubState]);
|
||||
|
||||
return (
|
||||
<Modal open={props.open} size="sm">
|
||||
<div className="flex h-full w-full flex-col justify-between">
|
||||
<div className={`flex flex-col justify-between gap-2`}>
|
||||
<span
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Import Image Overlay
|
||||
</span>
|
||||
|
||||
<span className="text-gray-400">Enter the corner coordinates of the image overlay to be imported.</span>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-gray-300">Corner 1 latitude </div>
|
||||
<div>
|
||||
<OlStringInput
|
||||
value={String(bound1Lat)}
|
||||
onChange={(ev) => {
|
||||
setBound1Lat(ev.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-gray-300">Corner 1 longitude </div>
|
||||
<div>
|
||||
<OlStringInput
|
||||
value={String(bound1Lon)}
|
||||
onChange={(ev) => {
|
||||
setBound1Lon(ev.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-gray-300">Corner 2 latitude </div>
|
||||
<div>
|
||||
<OlStringInput
|
||||
value={String(bound2Lat)}
|
||||
onChange={(ev) => {
|
||||
setBound2Lat(ev.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-gray-300">Corner 2 longitude </div>
|
||||
<div>
|
||||
<OlStringInput
|
||||
value={String(bound2Lon)}
|
||||
onChange={(ev) => {
|
||||
setBound2Lon(ev.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`
|
||||
${(showWarning ? "text-red-500" : `
|
||||
text-gray-400
|
||||
`)}
|
||||
text-sm
|
||||
`}>
|
||||
Please enter valid latitude and longitude values in decimal degrees format (e.g. 37.7749, -122.4194). Latitude must be between -90 and 90, and longitude must be between -180 and 180.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (
|
||||
isNaN(Number(bound1Lat)) || Number(bound1Lat) < -90 || Number(bound1Lat) > 90 ||
|
||||
isNaN(Number(bound1Lon)) || Number(bound1Lon) < -180 || Number(bound1Lon) > 180 ||
|
||||
isNaN(Number(bound2Lat)) || Number(bound2Lat) < -90 || Number(bound2Lat) > 90 ||
|
||||
isNaN(Number(bound2Lon)) || Number(bound2Lon) < -180 || Number(bound2Lon) > 180
|
||||
) {
|
||||
setShowWarning(true)
|
||||
return;
|
||||
}
|
||||
setShowWarning(false)
|
||||
|
||||
const bounds = new LatLngBounds([
|
||||
[Number(bound1Lat), Number(bound1Lon)],
|
||||
[Number(bound2Lat), Number(bound2Lon)]
|
||||
]
|
||||
)
|
||||
|
||||
let overlay = new ImageOverlay(importData, bounds);
|
||||
overlay.addTo(getApp().getMap());
|
||||
|
||||
getApp().setState(OlympusState.IDLE);
|
||||
}}
|
||||
className={`
|
||||
mb-2 me-2 ml-auto flex content-center items-center
|
||||
gap-2 rounded-sm bg-blue-700 px-5 py-2.5 text-sm
|
||||
font-medium text-white
|
||||
dark:bg-blue-600 dark:hover:bg-blue-700
|
||||
dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
>
|
||||
Continue
|
||||
<FontAwesomeIcon icon={faArrowRight} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => getApp().setState(OlympusState.IDLE)}
|
||||
className={`
|
||||
mb-2 me-2 flex content-center items-center gap-2
|
||||
rounded-sm border-[1px] bg-blue-700 px-5 py-2.5
|
||||
text-sm font-medium text-white
|
||||
dark:border-gray-600 dark:bg-gray-800
|
||||
dark:text-gray-400 dark:hover:bg-gray-700
|
||||
dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
160
frontend/react/src/ui/modals/loadoutwizardmodal.tsx
Normal file
160
frontend/react/src/ui/modals/loadoutwizardmodal.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Modal } from "./components/modal";
|
||||
import { FaMagic, FaStar } from "react-icons/fa";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faArrowRight } from "@fortawesome/free-solid-svg-icons";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { NO_SUBSTATE, OlympusState } from "../../constants/constants";
|
||||
import { AppStateChangedEvent, CustomLoadoutsUpdatedEvent, SetLoadoutWizardBlueprintEvent } from "../../events";
|
||||
import { WeaponsWizard } from "../panels/components/weaponswizard";
|
||||
import { LoadoutBlueprint, LoadoutItemBlueprint, UnitBlueprint } from "../../interfaces";
|
||||
import { OlToggle } from "../components/oltoggle";
|
||||
|
||||
export function LoadoutWizardModal(props: { open: boolean }) {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [appSubState, setAppSubState] = useState(NO_SUBSTATE);
|
||||
const [previousState, setPreviousState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
const [previousSubState, setPreviousSubState] = useState(NO_SUBSTATE);
|
||||
const [blueprint, setBlueprint] = useState(null as UnitBlueprint | null);
|
||||
const [isPersistent, setIsPersistent] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
AppStateChangedEvent.on((appState, appSubState, previousState, previousSubState) => {
|
||||
setAppState(appState);
|
||||
setAppSubState(appSubState);
|
||||
setPreviousState(previousState);
|
||||
setPreviousSubState(previousSubState);
|
||||
});
|
||||
SetLoadoutWizardBlueprintEvent.on((blueprint) => {
|
||||
setBlueprint(blueprint);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Clear blueprint when modal is closed
|
||||
if (!props.open) {
|
||||
setBlueprint(null);
|
||||
}
|
||||
}, [props.open]);
|
||||
|
||||
const [selectedWeapons, setSelectedWeapons] = useState({} as { [key: string]: { clsid: string; name: string; weight: number } });
|
||||
const [loadoutName, setLoadoutName] = useState("New loadout");
|
||||
const [loadoutRole, setLoadoutRole] = useState("Custom");
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedWeapons({});
|
||||
}, [props.open]);
|
||||
|
||||
// If "New Loadout" already exists in the blueprint loadouts, append a number to make it unique
|
||||
useEffect(() => {
|
||||
if (!blueprint) return;
|
||||
let name = "New loadout";
|
||||
let counter = 1;
|
||||
const existingLoadoutNames = blueprint.loadouts?.map((loadout) => loadout.name) || [];
|
||||
|
||||
while (existingLoadoutNames.includes(name)) {
|
||||
name = `New loadout ${counter}`;
|
||||
counter++;
|
||||
}
|
||||
|
||||
setLoadoutName(name);
|
||||
}, [blueprint]);
|
||||
|
||||
return (
|
||||
<Modal open={props.open} size={"tall"} onClose={() => getApp().setState(previousState, previousSubState)}>
|
||||
<div className="flex gap-4 text-xl text-white">
|
||||
<FaMagic
|
||||
className={`
|
||||
my-auto text-4xl text-gray-300
|
||||
`}
|
||||
/>
|
||||
<div className="my-auto">Loadout wizard</div>
|
||||
</div>
|
||||
<WeaponsWizard
|
||||
selectedWeapons={selectedWeapons}
|
||||
setSelectedWeapons={setSelectedWeapons}
|
||||
weaponsByPylon={blueprint?.acceptedPayloads ?? {}}
|
||||
loadoutName={loadoutName}
|
||||
setLoadoutName={setLoadoutName}
|
||||
loadoutRole={loadoutRole}
|
||||
setLoadoutRole={setLoadoutRole}
|
||||
/>
|
||||
<div className="mt-auto flex justify-between">
|
||||
<div className="flex gap-2 text-gray-200">
|
||||
<FaStar className={`my-auto text-2xl text-gray-200`}/>
|
||||
<div className={`my-auto mr-auto`}>Keep for the rest of the session</div>
|
||||
<OlToggle toggled={isPersistent} onClick={() => setIsPersistent(!isPersistent)} />
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
// Add a new loadout to the blueprint if it doesn't exist already
|
||||
if (blueprint) {
|
||||
const items: LoadoutItemBlueprint[] = [];
|
||||
for (const pylon in selectedWeapons) {
|
||||
const weapon = selectedWeapons[pylon];
|
||||
items.push({
|
||||
name: weapon.name,
|
||||
quantity: 1,
|
||||
});
|
||||
}
|
||||
|
||||
// Group the weapon items and sum their quantities if there are duplicates
|
||||
const groupedItems: LoadoutItemBlueprint[] = [];
|
||||
const itemMap: { [key: string]: LoadoutItemBlueprint } = {};
|
||||
for (const item of items) {
|
||||
if (itemMap[item.name]) {
|
||||
itemMap[item.name].quantity += item.quantity;
|
||||
} else {
|
||||
itemMap[item.name] = { ...item };
|
||||
}
|
||||
}
|
||||
for (const itemName in itemMap) {
|
||||
groupedItems.push(itemMap[itemName]);
|
||||
}
|
||||
|
||||
// Assemble the loadout payload section as a stringified lua table containing the payload number as key and the clsid as values
|
||||
// This must already be lua compatible
|
||||
let payloadLuaTable = "{pylons = {";
|
||||
for (const pylon in selectedWeapons) {
|
||||
const weapon = selectedWeapons[pylon];
|
||||
if (weapon) payloadLuaTable += `[${pylon}] = {CLSID = "${weapon.clsid}"},`;
|
||||
}
|
||||
payloadLuaTable += "}, fuel = 999999, flare=60, chaff=60, gun=100, ammo_type = 1}";
|
||||
|
||||
const newLoadout: LoadoutBlueprint = {
|
||||
items: groupedItems,
|
||||
roles: [loadoutRole],
|
||||
code: "",
|
||||
name: loadoutName,
|
||||
enabled: true,
|
||||
isCustom: true,
|
||||
persistent: isPersistent,
|
||||
payload: payloadLuaTable,
|
||||
};
|
||||
|
||||
if (!blueprint.loadouts) {
|
||||
blueprint.loadouts = [];
|
||||
}
|
||||
blueprint.loadouts.push(newLoadout);
|
||||
CustomLoadoutsUpdatedEvent.dispatch(blueprint.name, newLoadout);
|
||||
}
|
||||
getApp().setState(previousState, previousSubState);
|
||||
}}
|
||||
className={`
|
||||
mb-2 me-2 flex content-center items-center gap-2
|
||||
rounded-sm bg-blue-700 px-5 py-2.5 text-sm font-medium
|
||||
text-white
|
||||
dark:bg-blue-600 dark:hover:bg-blue-700
|
||||
dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
>
|
||||
Continue
|
||||
<FontAwesomeIcon className={`my-auto`} icon={faArrowRight} />
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
35
frontend/react/src/ui/panels/components/loadoutviewer.tsx
Normal file
35
frontend/react/src/ui/panels/components/loadoutviewer.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
|
||||
export function LoadoutViewer(props: { spawnLoadout: { items: { name: string; quantity: number }[] } }) {
|
||||
return (
|
||||
<div>
|
||||
{props.spawnLoadout.items.map((item) => {
|
||||
return (
|
||||
<div
|
||||
className={`flex content-center gap-2`}
|
||||
key={item.name}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
my-auto w-6 min-w-6 rounded-full py-0.5
|
||||
text-center text-sm font-bold text-gray-500
|
||||
dark:bg-[#17212D]
|
||||
`}
|
||||
>
|
||||
{item.quantity}x
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
my-auto overflow-hidden text-ellipsis text-nowrap
|
||||
text-sm
|
||||
dark:text-gray-300
|
||||
`}
|
||||
>
|
||||
{item.name}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
438
frontend/react/src/ui/panels/components/weaponswizard.tsx
Normal file
438
frontend/react/src/ui/panels/components/weaponswizard.tsx
Normal file
@@ -0,0 +1,438 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { FaArrowsRotate, FaTrash, FaXmark } from "react-icons/fa6";
|
||||
import { OlSearchBar } from "../../components/olsearchbar";
|
||||
import { OlToggle } from "../../components/oltoggle";
|
||||
export function WeaponsWizard(props: {
|
||||
selectedWeapons: { [key: string]: { clsid: string; name: string; weight: number } };
|
||||
setSelectedWeapons: (weapons: { [key: string]: { clsid: string; name: string; weight: number } }) => void;
|
||||
weaponsByPylon: { [key: string]: { clsid: string; name: string; weight: number }[] };
|
||||
loadoutName: string;
|
||||
setLoadoutName: (name: string) => void;
|
||||
loadoutRole: string;
|
||||
setLoadoutRole: (role: string) => void;
|
||||
}) {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [selectedPylons, setSelectedPylons] = useState<string[]>([]);
|
||||
const [autofillPylons, setAutofillPylons] = useState(false);
|
||||
const [fillEmptyOnly, setFillEmptyOnly] = useState(true);
|
||||
const [weaponLetters, setWeaponLetters] = useState<{ [key: string]: string }>({}); // Letter to weapon name mapping
|
||||
const [hoveredWeapon, setHoveredWeapon] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
// If autofill is enabled, clear selected pylons
|
||||
if (autofillPylons) {
|
||||
setSelectedPylons([]);
|
||||
}
|
||||
}, [autofillPylons]);
|
||||
|
||||
useEffect(() => {
|
||||
// Clear search text when weaponsByPylon changes
|
||||
setSearchText("");
|
||||
setSelectedPylons([]);
|
||||
}, [props.weaponsByPylon]);
|
||||
|
||||
// Find the weapons that are availabile in all the selected pylons, meaning the intersection of the weapons in each pylon
|
||||
let availableWeapons: { clsid: string; name: string; weight: number }[] = [];
|
||||
if (autofillPylons) {
|
||||
// If autofill is enabled, show all weapons
|
||||
availableWeapons = Object.values(props.weaponsByPylon).flat();
|
||||
} else {
|
||||
if (selectedPylons.length > 0) {
|
||||
// If pylons are selected, show only weapons that are in all selected pylons
|
||||
const weaponsInSelectedPylons = selectedPylons.map((pylon) => props.weaponsByPylon[pylon] || []);
|
||||
availableWeapons = weaponsInSelectedPylons.reduce((acc, weapons) => {
|
||||
return acc.filter((w) => weapons.some((w2) => w2.name === w.name));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Sort alphabetically
|
||||
availableWeapons.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
// Remove duplicates
|
||||
availableWeapons = availableWeapons.filter((weapon, index, self) => index === self.findIndex((w) => w.name === weapon.name));
|
||||
|
||||
// Filter by search text
|
||||
if (searchText.trim() !== "") {
|
||||
availableWeapons = availableWeapons.filter((weapon) => weapon.name.toLowerCase().includes(searchText.toLowerCase()));
|
||||
}
|
||||
|
||||
// If autofill is enabled and fillEmptyOnly is enabled, remove weapons that have no compatible empty pylons
|
||||
if (autofillPylons && fillEmptyOnly) {
|
||||
availableWeapons = availableWeapons.filter((weapon) => {
|
||||
// Check if there is at least one pylon that is compatible with this weapon and is empty
|
||||
return Object.keys(props.weaponsByPylon).some((pylon) => {
|
||||
const weaponsInPylon = props.weaponsByPylon[pylon];
|
||||
return weaponsInPylon.some((w) => w.name === weapon.name) && !props.selectedWeapons[pylon];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Assign a letter to each indiviual type of weapon selected in selectedWeapons for display in the pylon selection
|
||||
// Find the first unused letter
|
||||
Object.values(props.selectedWeapons).forEach((weapon) => {
|
||||
if (Object.entries(weaponLetters).findIndex(([letter, name]) => name === weapon.name) === -1) {
|
||||
// Find the first unused letter starting from A
|
||||
let currentLetter = "A";
|
||||
while (weaponLetters[currentLetter]) {
|
||||
currentLetter = String.fromCharCode(currentLetter.charCodeAt(0) + 1);
|
||||
}
|
||||
weaponLetters[currentLetter] = weapon.name;
|
||||
currentLetter = String.fromCharCode(currentLetter.charCodeAt(0) + 1);
|
||||
}
|
||||
});
|
||||
|
||||
// Remove letters for weapons that are no longer selected
|
||||
Object.entries(weaponLetters).forEach(([letter, name]) => {
|
||||
if (Object.values(props.selectedWeapons).findIndex((weapon) => weapon.name === name) === -1) {
|
||||
delete weaponLetters[letter];
|
||||
}
|
||||
});
|
||||
|
||||
if (JSON.stringify(weaponLetters) !== JSON.stringify(weaponLetters)) setWeaponLetters({ ...weaponLetters });
|
||||
|
||||
// List of very bright and distinct colors
|
||||
const colors = {
|
||||
A: "#FF5733",
|
||||
B: "#33FF57",
|
||||
C: "#3357FF",
|
||||
D: "#F333FF",
|
||||
E: "#33FFF5",
|
||||
F: "#F5FF33",
|
||||
G: "#FF33A8",
|
||||
H: "#A833FF",
|
||||
I: "#33FFA8",
|
||||
J: "#FFA833",
|
||||
K: "#33A8FF",
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col gap-6 text-white" onMouseEnter={() => setHoveredWeapon("")}>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex justify-between">
|
||||
<div className="my-auto font-semibold">Loadout Name</div>
|
||||
<input
|
||||
type="text"
|
||||
value={props.loadoutName}
|
||||
onChange={(e) => props.setLoadoutName(e.target.value)}
|
||||
className={`
|
||||
rounded-md border border-gray-300 bg-gray-800 p-2
|
||||
text-sm text-white
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div className="my-auto font-semibold">Loadout Role</div>
|
||||
<input
|
||||
type="text"
|
||||
value={props.loadoutRole}
|
||||
onChange={(e) => props.setLoadoutRole(e.target.value)}
|
||||
className={`
|
||||
rounded-md border border-gray-300 bg-gray-800 p-2
|
||||
text-sm text-white
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-gray-400">Select weapons for each pylon</span>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="mx-auto flex flex-col gap-2">
|
||||
{/* Draw an airplane seen from the front using only gray lines */}
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
className={`
|
||||
border-b-2 border- b-2 w-full border-gray-300
|
||||
`}
|
||||
></div>
|
||||
<div
|
||||
className={`
|
||||
h-14 min-w-14 rounded-full border-2
|
||||
border-gray-300
|
||||
`}
|
||||
></div>
|
||||
<div
|
||||
className={`
|
||||
border-b-2 border- b-2 w-full border-gray-300
|
||||
`}
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center gap-1">
|
||||
{Object.keys(props.weaponsByPylon).map((pylon) => {
|
||||
let weapon = props.selectedWeapons[pylon];
|
||||
let letter = Object.entries(weaponLetters).find(([letter, name]) => name === weapon?.name)?.[0] || "";
|
||||
// If the currently hovered weapon is compatible with this pylon, show "Hovered" else "Not Hovered"
|
||||
let isHovered = props.weaponsByPylon[pylon].some((w) => w.name === hoveredWeapon);
|
||||
return (
|
||||
<div key={pylon} className={``}>
|
||||
<div
|
||||
className={`
|
||||
flex h-20 flex-col items-center
|
||||
justify-center rounded-md border
|
||||
px-1
|
||||
${
|
||||
autofillPylons
|
||||
? `text-gray-400`
|
||||
: `
|
||||
cursor-pointer
|
||||
hover:bg-gray-700
|
||||
`
|
||||
}
|
||||
${
|
||||
selectedPylons.includes(pylon)
|
||||
? `
|
||||
border-gray-200
|
||||
`
|
||||
: `
|
||||
border-transparent
|
||||
`
|
||||
}
|
||||
`}
|
||||
onClick={() => {
|
||||
if (autofillPylons) return;
|
||||
if (selectedPylons.includes(pylon)) {
|
||||
setSelectedPylons(selectedPylons.filter((p) => p !== pylon));
|
||||
} else {
|
||||
setSelectedPylons([...selectedPylons, pylon]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className={`text-center text-xs`}>{pylon}</div>
|
||||
|
||||
<div
|
||||
data-autofill={autofillPylons ? "true" : "false"}
|
||||
className={`
|
||||
h-3 w-0 border
|
||||
data-[autofill='false']:border-white
|
||||
data-[autofill='true']:border-gray-400
|
||||
`}
|
||||
></div>
|
||||
{props.selectedWeapons[pylon] ? (
|
||||
<div
|
||||
data-autofill={autofillPylons ? "true" : "false"}
|
||||
data-hovered={isHovered ? "true" : "false"}
|
||||
className={`
|
||||
flex h-6 w-6 items-center
|
||||
justify-center
|
||||
rounded-full border
|
||||
data-[autofill='false']:border-white
|
||||
data-[autofill='true']:border-gray-400
|
||||
data-[hovered='true']:border-green-400
|
||||
`}
|
||||
>
|
||||
{/* Show the letter of the group the weapon belongs to from weaponLetters */}
|
||||
<span
|
||||
className={`
|
||||
text-sm font-bold
|
||||
`}
|
||||
style={{
|
||||
color: letter in colors ? colors[letter as keyof typeof colors] : "inherit",
|
||||
}}
|
||||
>
|
||||
{letter}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
data-autofill={autofillPylons ? "true" : "false"}
|
||||
data-hovered={isHovered ? "true" : "false"}
|
||||
className={`
|
||||
h-6 w-6 rounded-full
|
||||
border
|
||||
data-[autofill='false']:border-white
|
||||
data-[autofill='true']:border-gray-400
|
||||
data-[hovered='true']:border-green-400
|
||||
`}
|
||||
></div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
{/* List all the groups from weaponLetters */}
|
||||
<div className="flex flex-col gap-1">
|
||||
{Object.entries(weaponLetters).map(([letter, weapon]) => (
|
||||
<div
|
||||
key={letter}
|
||||
className={`
|
||||
flex items-center text-sm
|
||||
`}
|
||||
>
|
||||
<span className="font-bold" style={{ color: letter in colors ? colors[letter as keyof typeof colors] : "inherit" }}>
|
||||
{letter}:
|
||||
</span>
|
||||
<span className="ml-1 text-gray-400">{weapon}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Buttons to select/deselect all pylons, clear all weapons and remove weapons from selected pylons */}
|
||||
<div>
|
||||
<div className="flex justify-center gap-2">
|
||||
{selectedPylons.length > 0 && (
|
||||
<>
|
||||
<button
|
||||
className={`
|
||||
text-nowrap rounded-md bg-gray-700
|
||||
px-2 py-1 text-sm
|
||||
hover:bg-gray-600
|
||||
`}
|
||||
onClick={() => {
|
||||
setSelectedPylons([]);
|
||||
}}
|
||||
>
|
||||
<FaArrowsRotate className="inline" /> Reset selection
|
||||
</button>
|
||||
|
||||
{
|
||||
/* Checjk if any of the selected pylons have a weapon selected */
|
||||
props.selectedWeapons && selectedPylons.some((pylon) => props.selectedWeapons[pylon] !== undefined) && (
|
||||
<button
|
||||
className={`
|
||||
text-nowrap rounded-md
|
||||
bg-gray-700 px-2 py-1 text-sm
|
||||
hover:bg-gray-600
|
||||
`}
|
||||
onClick={() => {
|
||||
// Remove weapons from selected pylons
|
||||
let newSelectedWeapons = { ...props.selectedWeapons };
|
||||
selectedPylons.forEach((pylon) => {
|
||||
delete newSelectedWeapons[pylon];
|
||||
});
|
||||
props.setSelectedWeapons(newSelectedWeapons);
|
||||
}}
|
||||
>
|
||||
<FaXmark
|
||||
className={`
|
||||
inline text-red-500
|
||||
`}
|
||||
/>{" "}
|
||||
Remove
|
||||
</button>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)}
|
||||
{props.selectedWeapons && Object.keys(props.selectedWeapons).length > 0 && (
|
||||
<button
|
||||
className={`
|
||||
text-nowrap rounded-md bg-gray-700 px-2
|
||||
py-1 text-sm
|
||||
hover:bg-gray-600
|
||||
`}
|
||||
onClick={() => {
|
||||
// Clear all selected weapons
|
||||
props.setSelectedWeapons({});
|
||||
}}
|
||||
>
|
||||
<FaTrash className="inline text-red-500" /> Delete all
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="ml-2 text-sm">Autofill compatible pylons with weapon</span>
|
||||
<OlToggle
|
||||
toggled={autofillPylons}
|
||||
onClick={() => {
|
||||
setAutofillPylons(!autofillPylons);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{autofillPylons && (
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="ml-2 text-sm">Only fill empty pylons</span>
|
||||
<OlToggle
|
||||
toggled={fillEmptyOnly}
|
||||
onClick={() => {
|
||||
setFillEmptyOnly(!fillEmptyOnly);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<OlSearchBar onChange={setSearchText} text={searchText} />
|
||||
|
||||
<div
|
||||
className={`
|
||||
flex max-h-48 flex-col overflow-y-auto border
|
||||
border-gray-700 px-2
|
||||
`}
|
||||
>
|
||||
{selectedPylons.length === 0 && !autofillPylons && (
|
||||
<div
|
||||
className={`
|
||||
p-2 text-sm text-gray-400
|
||||
`}
|
||||
>
|
||||
No pylons selected
|
||||
</div>
|
||||
)}
|
||||
{availableWeapons.length === 0 && selectedPylons.length !== 0 && !autofillPylons && (
|
||||
<div
|
||||
className={`
|
||||
p-2 text-sm text-gray-400
|
||||
`}
|
||||
>
|
||||
No weapons compatible with all selected pylons
|
||||
</div>
|
||||
)}
|
||||
{availableWeapons.length === 0 && selectedPylons.length === 0 && autofillPylons && (
|
||||
<div
|
||||
className={`
|
||||
p-2 text-sm text-gray-400
|
||||
`}
|
||||
>
|
||||
No empty pylons available
|
||||
</div>
|
||||
)}
|
||||
|
||||
{availableWeapons.length !== 0 &&
|
||||
availableWeapons.map((weapon) => (
|
||||
<div
|
||||
key={weapon.name}
|
||||
onClick={() => {
|
||||
if (autofillPylons) {
|
||||
// Autofill all compatible pylons with the selected weapon
|
||||
let newSelectedWeapons = { ...props.selectedWeapons };
|
||||
Object.keys(props.weaponsByPylon).forEach((pylon) => {
|
||||
const weaponsInPylon = props.weaponsByPylon[pylon];
|
||||
if (fillEmptyOnly && props.selectedWeapons[pylon]) {
|
||||
// If "Only fill empty pylons" is enabled, skip filled pylons
|
||||
return;
|
||||
}
|
||||
if (weaponsInPylon.some((w) => w.name === weapon.name)) {
|
||||
newSelectedWeapons[pylon] = weapon;
|
||||
}
|
||||
});
|
||||
props.setSelectedWeapons(newSelectedWeapons);
|
||||
} else {
|
||||
let newSelectedWeapons = { ...props.selectedWeapons };
|
||||
// Add the weapon to the selected pylons
|
||||
selectedPylons.forEach((pylon) => {
|
||||
newSelectedWeapons[pylon] = weapon;
|
||||
});
|
||||
props.setSelectedWeapons(newSelectedWeapons);
|
||||
setSelectedPylons([]);
|
||||
}
|
||||
}}
|
||||
onMouseEnter={() => setHoveredWeapon(weapon.name)}
|
||||
onMouseLeave={() => setHoveredWeapon("")}
|
||||
className={`
|
||||
cursor-pointer rounded-md p-1 text-sm
|
||||
hover:bg-gray-700
|
||||
`}
|
||||
>
|
||||
{weapon.name}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -142,6 +142,31 @@ export function MainMenu(props: { open: boolean; onClose: () => void; children?:
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
group flex cursor-pointer select-none content-center gap-3
|
||||
rounded-md p-2
|
||||
dark:hover:bg-olympus-500
|
||||
hover:bg-gray-900/10
|
||||
`}
|
||||
|
||||
onClick={() => {
|
||||
getApp().setState(OlympusState.IMPORT_IMAGE_OVERLAY);
|
||||
}}
|
||||
>
|
||||
{/*<FontAwesomeIcon icon={faFileImport} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}
|
||||
Import image overlay
|
||||
<div className={`ml-auto flex items-center`}>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRightLong}
|
||||
className={`
|
||||
my-auto px-2 text-right text-gray-800 transition-transform
|
||||
dark:text-olympus-50
|
||||
group-hover:translate-x-2
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ import { UnitControlMenu } from "./panels/unitcontrolmenu";
|
||||
import { MainMenu } from "./panels/mainmenu";
|
||||
import { SideBar } from "./panels/sidebar";
|
||||
import { OptionsMenu } from "./panels/optionsmenu";
|
||||
import { NO_SUBSTATE, OlympusState, OlympusSubState, OptionsSubstate, UnitControlSubState } from "../constants/constants";
|
||||
import { NO_SUBSTATE, OlympusState, OlympusSubState, OptionsSubstate, SpawnSubState, UnitControlSubState } from "../constants/constants";
|
||||
import { getApp, setupApp } from "../olympusapp";
|
||||
import { LoginModal } from "./modals/loginmodal";
|
||||
|
||||
@@ -31,6 +31,8 @@ import { ImportExportModal } from "./modals/importexportmodal";
|
||||
import { WarningModal } from "./modals/warningmodal";
|
||||
import { TrainingModal } from "./modals/trainingmodal";
|
||||
import { AdminModal } from "./modals/adminmodal";
|
||||
import { ImageOverlayModal } from "./modals/imageoverlaymodal";
|
||||
import { LoadoutWizardModal } from "./modals/loadoutwizardmodal";
|
||||
|
||||
export function UI() {
|
||||
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
|
||||
@@ -74,6 +76,8 @@ export function UI() {
|
||||
<WarningModal open={appState === OlympusState.WARNING} />
|
||||
<TrainingModal open={appState === OlympusState.TRAINING} />
|
||||
<AdminModal open={appState === OlympusState.ADMIN} />
|
||||
<ImageOverlayModal open={appState === OlympusState.IMPORT_IMAGE_OVERLAY} />
|
||||
<LoadoutWizardModal open={appState === OlympusState.SPAWN && appSubState === SpawnSubState.LOADOUT_WIZARD} />
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { GAME_MASTER } from "../../constants/constants";
|
||||
import { UnitBlueprint } from "../../interfaces";
|
||||
import { UnitDatabaseLoadedEvent } from "../../events";
|
||||
import { SessionDataLoadedEvent, UnitDatabaseLoadedEvent } from "../../events";
|
||||
|
||||
export class UnitDatabase {
|
||||
blueprints: { [key: string]: UnitBlueprint } = {};
|
||||
|
||||
constructor() {}
|
||||
constructor() {
|
||||
SessionDataLoadedEvent.on((sessionData) => {
|
||||
// Check if the sessionData customloadouts contains any loadouts for units, and if so, update the blueprints
|
||||
if (sessionData.customLoadouts) {
|
||||
for (let unitName in sessionData.customLoadouts) {
|
||||
if (this.blueprints[unitName]) {
|
||||
if (!this.blueprints[unitName].loadouts) this.blueprints[unitName].loadouts = [];
|
||||
sessionData.customLoadouts[unitName].forEach((loadout) => {
|
||||
this.blueprints[unitName].loadouts?.push(loadout);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
load(url: string, category?: string) {
|
||||
if (url !== "") {
|
||||
@@ -204,7 +218,7 @@ export class UnitDatabase {
|
||||
getLoadoutNamesByRole(name: string, role: string) {
|
||||
var filteredBlueprints = this.getBlueprints();
|
||||
var loadoutsByRole: string[] = [];
|
||||
var loadouts = filteredBlueprints[name].loadouts;
|
||||
var loadouts = filteredBlueprints[name as any].loadouts;
|
||||
if (loadouts) {
|
||||
for (let loadout of loadouts) {
|
||||
if (loadout.roles.includes(role) || loadout.roles.includes("")) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -35,7 +35,7 @@ mist = {}
|
||||
-- don't change these
|
||||
mist.majorVersion = 4
|
||||
mist.minorVersion = 5
|
||||
mist.build = 122
|
||||
mist.build = 125
|
||||
|
||||
-- forward declaration of log shorthand
|
||||
local log
|
||||
@@ -695,7 +695,6 @@ do -- the main scope
|
||||
["FARP"] = "farps",
|
||||
["Fueltank"] = "fueltank_cargo",
|
||||
["Gate"] = "gate",
|
||||
["FARP Fuel Depot"] = "gsm rus",
|
||||
["Armed house"] = "home1_a",
|
||||
["FARP Command Post"] = "kp-ug",
|
||||
["Watch Tower Armed"] = "ohr-vyshka",
|
||||
@@ -704,7 +703,6 @@ do -- the main scope
|
||||
["Pipes big"] = "pipes_big_cargo",
|
||||
["Oil platform"] = "plavbaza",
|
||||
["Tetrapod"] = "tetrapod_cargo",
|
||||
["Fuel tank"] = "toplivo",
|
||||
["Trunks long"] = "trunks_long_cargo",
|
||||
["Trunks small"] = "trunks_small_cargo",
|
||||
["Passenger liner"] = "yastrebow",
|
||||
@@ -1152,6 +1150,7 @@ do -- the main scope
|
||||
end
|
||||
end
|
||||
end
|
||||
--dbLog:warn(newTable)
|
||||
--mist.debug.writeData(mist.utils.serialize,{'msg', newTable}, timer.getAbsTime() ..'Group.lua')
|
||||
newTable.timeAdded = timer.getAbsTime() -- only on the dynGroupsAdded table. For other reference, see start time
|
||||
--mist.debug.dumpDBs()
|
||||
@@ -1493,7 +1492,7 @@ do -- the main scope
|
||||
task.t = timer.getTime() + task.rep --schedule next run
|
||||
local err, errmsg = pcall(task.f, unpack(task.vars, 1, table.maxn(task.vars)))
|
||||
if not err then
|
||||
log:error('Error in scheduled function: $1' .. errmsg)
|
||||
log:error('Error in scheduled function: $1', errmsg)
|
||||
end
|
||||
--scheduledTasks[i].f(unpack(scheduledTasks[i].vars, 1, table.maxn(scheduledTasks[i].vars))) -- do the task
|
||||
i = i + 1
|
||||
@@ -1519,7 +1518,7 @@ do -- the main scope
|
||||
id = tostring(original_id) .. ' #' .. tostring(id_ind)
|
||||
id_ind = id_ind + 1
|
||||
end
|
||||
|
||||
local valid
|
||||
if mist.DBs.aliveUnits and mist.DBs.aliveUnits[val.object.id_] then
|
||||
--log:info('object found in alive_units')
|
||||
val.objectData = mist.utils.deepCopy(mist.DBs.aliveUnits[val.object.id_])
|
||||
@@ -1532,6 +1531,7 @@ do -- the main scope
|
||||
--trigger.action.outText('remove via death: ' .. Unit.getName(val.object),20)
|
||||
mist.DBs.activeHumans[Unit.getName(val.object)] = nil
|
||||
end]]
|
||||
valid = true
|
||||
elseif mist.DBs.removedAliveUnits and mist.DBs.removedAliveUnits[val.object.id_] then -- it didn't exist in alive_units, check old_alive_units
|
||||
--log:info('object found in old_alive_units')
|
||||
val.objectData = mist.utils.deepCopy(mist.DBs.removedAliveUnits[val.object.id_])
|
||||
@@ -1540,32 +1540,37 @@ do -- the main scope
|
||||
val.objectPos = pos.p
|
||||
end
|
||||
val.objectType = mist.DBs.removedAliveUnits[val.object.id_].category
|
||||
|
||||
valid = true
|
||||
else --attempt to determine if static object...
|
||||
--log:info('object not found in alive units or old alive units')
|
||||
local pos = Object.getPosition(val.object)
|
||||
if pos then
|
||||
local static_found = false
|
||||
for ind, static in pairs(mist.DBs.unitsByCat.static) do
|
||||
if ((pos.p.x - static.point.x)^2 + (pos.p.z - static.point.y)^2)^0.5 < 0.1 then --really, it should be zero...
|
||||
--log:info('correlated dead static object to position')
|
||||
val.objectData = static
|
||||
val.objectPos = pos.p
|
||||
val.objectType = 'static'
|
||||
static_found = true
|
||||
break
|
||||
if Object.isExist(val.object) then
|
||||
local pos = Object.getPosition(val.object)
|
||||
if pos then
|
||||
local static_found = false
|
||||
for ind, static in pairs(mist.DBs.unitsByCat.static) do
|
||||
if ((pos.p.x - static.point.x)^2 + (pos.p.z - static.point.y)^2)^0.5 < 0.1 then --really, it should be zero...
|
||||
--log:info('correlated dead static object to position')
|
||||
val.objectData = static
|
||||
val.objectPos = pos.p
|
||||
val.objectType = 'static'
|
||||
static_found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not static_found then
|
||||
val.objectPos = pos.p
|
||||
val.objectType = 'building'
|
||||
val.typeName = Object.getTypeName(val.object)
|
||||
end
|
||||
else
|
||||
val.objectType = 'unknown'
|
||||
end
|
||||
if not static_found then
|
||||
val.objectPos = pos.p
|
||||
val.objectType = 'building'
|
||||
val.typeName = Object.getTypeName(val.object)
|
||||
end
|
||||
else
|
||||
val.objectType = 'unknown'
|
||||
valid = true
|
||||
end
|
||||
end
|
||||
mist.DBs.deadObjects[id] = val
|
||||
if valid then
|
||||
mist.DBs.deadObjects[id] = val
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2019,7 +2024,7 @@ do -- the main scope
|
||||
|
||||
end
|
||||
end
|
||||
--mist.debug.writeData(mist.utils.serialize,{'msg', newGroup}, 'newGroupPushedToAddGroup.lua')
|
||||
--mist.debug.writeData(mist.utils.serialize,{'msg', newGroup}, newGroup.name ..'.lua')
|
||||
--log:warn(newGroup)
|
||||
-- sanitize table
|
||||
newGroup.groupName = nil
|
||||
@@ -3560,7 +3565,7 @@ function mist.getUnitsInMovingZones(unit_names, zone_unit_names, radius, zone_ty
|
||||
end
|
||||
|
||||
function mist.getUnitsLOS(unitset1, altoffset1, unitset2, altoffset2, radius)
|
||||
log:info("$1, $2, $3, $4, $5", unitset1, altoffset1, unitset2, altoffset2, radius)
|
||||
--log:info("$1, $2, $3, $4, $5", unitset1, altoffset1, unitset2, altoffset2, radius)
|
||||
radius = radius or math.huge
|
||||
local unit_info1 = {}
|
||||
local unit_info2 = {}
|
||||
@@ -3568,21 +3573,25 @@ function mist.getUnitsLOS(unitset1, altoffset1, unitset2, altoffset2, radius)
|
||||
-- get the positions all in one step, saves execution time.
|
||||
for unitset1_ind = 1, #unitset1 do
|
||||
local unit1 = Unit.getByName(unitset1[unitset1_ind])
|
||||
local lCat = Object.getCategory(unit1)
|
||||
if unit1 and ((lCat == 1 and unit1:isActive()) or lCat ~= 1) and unit:isExist() == true then
|
||||
unit_info1[#unit_info1 + 1] = {}
|
||||
unit_info1[#unit_info1].unit = unit1
|
||||
unit_info1[#unit_info1].pos = unit1:getPosition().p
|
||||
if unit1 then
|
||||
local lCat = Object.getCategory(unit1)
|
||||
if ((lCat == 1 and unit1:isActive()) or lCat ~= 1) and unit1:isExist() == true then
|
||||
unit_info1[#unit_info1 + 1] = {}
|
||||
unit_info1[#unit_info1].unit = unit1
|
||||
unit_info1[#unit_info1].pos = unit1:getPosition().p
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for unitset2_ind = 1, #unitset2 do
|
||||
local unit2 = Unit.getByName(unitset2[unitset2_ind])
|
||||
local lCat = Object.getCategory(unit2)
|
||||
if unit2 and ((lCat == 1 and unit2:isActive()) or lCat ~= 1) and unit:isExist() == true then
|
||||
unit_info2[#unit_info2 + 1] = {}
|
||||
unit_info2[#unit_info2].unit = unit2
|
||||
unit_info2[#unit_info2].pos = unit2:getPosition().p
|
||||
if unit2 then
|
||||
local lCat = Object.getCategory(unit2)
|
||||
if ((lCat == 1 and unit2:isActive()) or lCat ~= 1) and unit2:isExist() == true then
|
||||
unit_info2[#unit_info2 + 1] = {}
|
||||
unit_info2[#unit_info2].unit = unit2
|
||||
unit_info2[#unit_info2].pos = unit2:getPosition().p
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4012,13 +4021,14 @@ do -- group functions scope
|
||||
|
||||
if Group.getByName(gpName) and Group.getByName(gpName):isExist() == true then
|
||||
local newGroup = Group.getByName(gpName)
|
||||
local newData = {}
|
||||
local newData = mist.utils.deepCopy(dbData)
|
||||
newData.name = gpName
|
||||
newData.groupId = tonumber(newGroup:getID())
|
||||
newData.category = newGroup:getCategory()
|
||||
newData.groupName = gpName
|
||||
newData.hidden = dbData.hidden
|
||||
|
||||
|
||||
|
||||
if newData.category == 2 then
|
||||
newData.category = 'vehicle'
|
||||
elseif newData.category == 3 then
|
||||
@@ -5193,7 +5203,8 @@ do -- mist.util scope
|
||||
|
||||
function mist.utils.getHeadingPoints(point1, point2, north) -- sick of writing this out.
|
||||
if north then
|
||||
return mist.utils.getDir(mist.vec.sub(mist.utils.makeVec3(point2), mist.utils.makeVec3(point1)), (mist.utils.makeVec3(point1)))
|
||||
local p1 = mist.utils.get3DDist(point1)
|
||||
return mist.utils.getDir(mist.vec.sub(mist.utils.makeVec3(point2), p1), p1)
|
||||
else
|
||||
return mist.utils.getDir(mist.vec.sub(mist.utils.makeVec3(point2), mist.utils.makeVec3(point1)))
|
||||
end
|
||||
@@ -5837,8 +5848,8 @@ do -- mist.debug scope
|
||||
log:alert('insufficient libraries to run mist.debug.dump_G, you must disable the sanitization of the io and lfs libraries in ./Scripts/MissionScripting.lua')
|
||||
--trigger.action.outText(errmsg, 10)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
--- Write debug data to file.
|
||||
-- This function requires you to disable script sanitization
|
||||
-- in $DCS_ROOT\Scripts\MissionScripting.lua to access lfs and io
|
||||
@@ -7653,7 +7664,10 @@ do
|
||||
--log:warn(s)
|
||||
if type(s) == 'table' then
|
||||
local mType = s.markType
|
||||
if mType == 'panel' then
|
||||
--log:echo(s)
|
||||
|
||||
if mType == 'panel' then
|
||||
local markScope = s.markScope or "all"
|
||||
if markScope == 'coa' then
|
||||
trigger.action.markToCoalition(s.markId, s.text, s.pos, s.markFor, s.readOnly)
|
||||
elseif markScope == 'group' then
|
||||
@@ -7711,10 +7725,15 @@ do
|
||||
|
||||
local function validateColor(val)
|
||||
if type(val) == 'table' then
|
||||
for i = 1, #val do
|
||||
if type(val[i]) == 'number' and val[i] > 1 then
|
||||
val[i] = val[i]/255 -- convert RGB values from 0-255 to 0-1 equivilent.
|
||||
end
|
||||
for i = 1, 4 do
|
||||
if val[i] then
|
||||
if type(val[i]) == 'number' and val[i] > 1 then
|
||||
val[i] = val[i]/255 -- convert RGB values from 0-255 to 0-1 equivilent.
|
||||
end
|
||||
else
|
||||
val[i] = 0.8
|
||||
log:warn("index $1 of color to mist.marker.add was missing, defaulted to 0.8", i)
|
||||
end
|
||||
end
|
||||
elseif type(val) == 'string' then
|
||||
val = mist.utils.hexToRGB(val)
|
||||
@@ -7755,7 +7774,7 @@ do
|
||||
--log:info('create maker DB: $1', e.idx)
|
||||
mist.DBs.markList[e.idx] = {time = e.time, pos = e.pos, groupId = e.groupId, mType = 'panel', text = e.text, markId = e.idx, coalition = e.coalition}
|
||||
if e.unit then
|
||||
mist.DBs.markList[e.idx].unit = e.intiator:getName()
|
||||
mist.DBs.markList[e.idx].unit = e.initiator:getName()
|
||||
end
|
||||
--log:info(mist.marker.list[e.idx])
|
||||
end
|
||||
@@ -7778,7 +7797,7 @@ do
|
||||
else
|
||||
for mEntry, mData in pairs(mist.DBs.markList) do
|
||||
if id == mData.name or id == mData.id then
|
||||
return mData.id
|
||||
return mData.markId
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -7788,11 +7807,16 @@ do
|
||||
|
||||
|
||||
local function removeMark(id)
|
||||
--log:info("Removing Mark: $1", id
|
||||
--log:info("Removing Mark: $1", id)
|
||||
local removed = false
|
||||
if type(id) == 'table' then
|
||||
for ind, val in pairs(id) do
|
||||
local r = getMarkId(val)
|
||||
local r
|
||||
if val.markId then
|
||||
r = val.markId
|
||||
else
|
||||
r = getMarkId(val)
|
||||
end
|
||||
if r then
|
||||
trigger.action.removeMark(r)
|
||||
mist.DBs.markList[r] = nil
|
||||
@@ -7802,9 +7826,11 @@ do
|
||||
|
||||
else
|
||||
local r = getMarkId(id)
|
||||
trigger.action.removeMark(r)
|
||||
mist.DBs.markList[r] = nil
|
||||
removed = true
|
||||
if r then
|
||||
trigger.action.removeMark(r)
|
||||
mist.DBs.markList[r] = nil
|
||||
removed = true
|
||||
end
|
||||
end
|
||||
return removed
|
||||
end
|
||||
@@ -7926,6 +7952,7 @@ do
|
||||
|
||||
if markForCoa then
|
||||
if type(markForCoa) == 'string' then
|
||||
--log:warn("coa is string")
|
||||
if tonumber(markForCoa) then
|
||||
coa = coas[tonumber(markForCoa)]
|
||||
markScope = 'coa'
|
||||
@@ -7940,11 +7967,10 @@ do
|
||||
end
|
||||
elseif type(markForCoa) == 'number' and markForCoa >=-1 and markForCoa <= #coas then
|
||||
coa = markForCoa
|
||||
markScore = 'coa'
|
||||
--log:warn("coa is number")
|
||||
markScope = 'coa'
|
||||
end
|
||||
|
||||
|
||||
|
||||
markFor = coa
|
||||
elseif markFor then
|
||||
if type(markFor) == 'number' then -- groupId
|
||||
if mist.DBs.groupsById[markFor] then
|
||||
@@ -8053,7 +8079,7 @@ do
|
||||
end
|
||||
for i = 1, #markForTable do
|
||||
local newId = iterate()
|
||||
local data = {markId = newId, text = text, pos = pos[i], markFor = markForTable[i], markType = 'panel', name = name, readOnly = readOnly, time = timer.getTime()}
|
||||
local data = {markId = newId, text = text, pos = pos[i], markScope = markScope, markFor = markForTable[i], markType = 'panel', name = name, readOnly = readOnly, time = timer.getTime()}
|
||||
mist.DBs.markList[newId] = data
|
||||
table.insert(list, data)
|
||||
|
||||
@@ -8177,6 +8203,7 @@ do
|
||||
end
|
||||
|
||||
function mist.marker.remove(id)
|
||||
|
||||
return removeMark(id)
|
||||
end
|
||||
|
||||
@@ -8967,8 +8994,8 @@ do -- group tasks scope
|
||||
minR = mist.utils.get2DDist(avg, zone[i])
|
||||
end
|
||||
end
|
||||
--log:warn('Radius: $1', radius)
|
||||
--log:warn('minR: $1', minR)
|
||||
--log:warn('Radius: $1', radius)
|
||||
local lSpawnPos = {}
|
||||
for j = 1, 100 do
|
||||
newCoord = mist.getRandPointInCircle(avg, radius)
|
||||
@@ -9200,7 +9227,7 @@ do -- group tasks scope
|
||||
function mist.groupIsDead(groupName) -- copy more or less from on station
|
||||
local gp = Group.getByName(groupName)
|
||||
if gp then
|
||||
if #gp:getUnits() > 0 or gp:isExist() == true then
|
||||
if #gp:getUnits() > 0 and gp:isExist() == true then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
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()
|
||||
625
scripts/python/API/infantry_boarding.py
Normal file
625
scripts/python/API/infantry_boarding.py
Normal file
@@ -0,0 +1,625 @@
|
||||
import asyncio
|
||||
from asyncio import Semaphore
|
||||
import json
|
||||
from random import randrange
|
||||
from api import API, Unit, UnitSpawnTable
|
||||
from math import pi
|
||||
import logging
|
||||
|
||||
#Set some globals up
|
||||
alternate_time = 300
|
||||
before_can_re_embark_time = 300
|
||||
####Transport types#####
|
||||
transport_ground = {
|
||||
"M-113": {
|
||||
"max_capacity": 4,
|
||||
"max_embark_range": 50,
|
||||
"doors": 1,
|
||||
"door_positions": [(3.35,pi),(0,0)],
|
||||
"board_positions": [(15,pi),(0,0)],
|
||||
"door_argument_nos": None,
|
||||
"door_open_thresholds": None,
|
||||
"is_rear_loader": True,
|
||||
"boarding_distance": 5
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
"board_positions": [(15,-pi/2),(0,0),(15,pi/2),(0,0)],
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
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_transport")
|
||||
logger.setLevel(logging.INFO)
|
||||
handler = logging.StreamHandler()
|
||||
formatter = logging.Formatter('[%(asctime)s] %(name)s - %(levelname)s - %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
logger.addHandler(handler)
|
||||
|
||||
class Transporter(Unit):
|
||||
def __init__(self, Unit):
|
||||
self.unit = Unit
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
"is_transport": self.unit.is_transport,
|
||||
"max_capacity": self.unit.max_capacity,
|
||||
"current_capacity": self.unit.current_capacity,
|
||||
"max_embark_range": self.unit.max_embark_range,
|
||||
"boarding_distance": self.unit.boarding_distance,
|
||||
"current_cargo_weight": self.unit.current_cargo_weight,
|
||||
"unit_array": [unit.ID for unit in self.unit.unit_array],
|
||||
"en_boarding_queue": [unit.ID for unit in self.unit.en_boarding_queue],
|
||||
"doors": self.unit.doors,
|
||||
"door_positions": self.unit.door_positions,
|
||||
"board_positions": self.unit.board_positions,
|
||||
"door_argument_nos": self.unit.door_argument_nos,
|
||||
"door_open_thresholds": self.unit.door_open_thresholds,
|
||||
"is_rear_loader": self.unit.is_rear_loader,
|
||||
"will_disembark": self.unit.will_disembark
|
||||
}
|
||||
|
||||
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.doors = transport_helicopters["UH-1H"]["doors"]
|
||||
self.unit.door_positions = transport_helicopters["UH-1H"]["door_positions"]
|
||||
self.unit.board_positions = transport_helicopters["UH-1H"]["board_positions"]
|
||||
|
||||
self.unit.door_argument_nos = transport_helicopters["UH-1H"]["door_argument_nos"]
|
||||
self.unit.will_disembark = False
|
||||
self.unit.register_draw_argument(43) #Register draw argument 43 for UH-1H
|
||||
self.unit.register_draw_argument(44)
|
||||
self.unit.door_open_thresholds = transport_helicopters["UH-1H"]["door_open_thresholds"]
|
||||
self.unit.is_rear_loader = transport_helicopters["UH-1H"]["is_rear_loader"]
|
||||
else:
|
||||
self.unit.max_capacity = 8
|
||||
self.unit.max_embark_range = 100
|
||||
self.unit.boarding_distance = 5
|
||||
self.unit.current_capacity = 0
|
||||
self.unit.current_cargo_weight = 0
|
||||
self.unit.unit_array = []
|
||||
self.unit.en_boarding_queue = []
|
||||
self.unit.doors = 1
|
||||
self.unit.door_positions = [(5,pi),(0,0)]
|
||||
self.unit.board_positions = [(15,pi),(0,0)]
|
||||
self.unit.door_argument_nos = None
|
||||
self.unit.door_open_thresholds = None
|
||||
self.unit.will_disembark = False
|
||||
self.unit.is_rear_loader = True
|
||||
|
||||
elif self.unit.name in transport_ground:
|
||||
if self.unit.name == "M-113":
|
||||
self.unit.max_capacity = transport_ground["M-113"]["max_capacity"]
|
||||
self.unit.max_embark_range = transport_ground["M-113"]["max_embark_range"]
|
||||
self.unit.boarding_distance = transport_ground["M-113"]["boarding_distance"]
|
||||
self.unit.current_capacity = 0
|
||||
self.unit.current_cargo_weight = 0
|
||||
self.unit.unit_array = []
|
||||
self.unit.en_boarding_queue = []
|
||||
self.unit.doors = transport_ground["M-113"]["doors"]
|
||||
self.unit.door_positions = transport_ground["M-113"]["door_positions"]
|
||||
self.unit.board_positions = transport_ground["M-113"]["board_positions"]
|
||||
self.unit.door_argument_nos = transport_ground["M-113"]["door_argument_nos"]
|
||||
self.unit.door_open_thresholds = transport_ground["M-113"]["door_open_thresholds"]
|
||||
self.unit.will_disembark = False
|
||||
self.unit.is_rear_loader = transport_ground["M-113"]["is_rear_loader"]
|
||||
else:
|
||||
self.unit.max_capacity = 4
|
||||
self.unit.max_embark_range = 50
|
||||
self.unit.boarding_distance = 5
|
||||
self.unit.current_capacity = 0
|
||||
self.unit.current_cargo_weight = 0
|
||||
self.unit.unit_array = []
|
||||
self.unit.en_boarding_queue = []
|
||||
self.unit.doors = 1
|
||||
self.unit.door_positions = [(5,pi),(0,0)]
|
||||
self.unit.board_positions = [(15,pi),(0,0)]
|
||||
self.unit.door_argument_nos = None
|
||||
self.unit.door_open_thresholds = None
|
||||
self.unit.will_disembark = False
|
||||
self.unit.is_rear_loader = True
|
||||
|
||||
logger.info(f"Set unit '{self.unit.name}' as transport, with {self.unit.current_capacity} / {self.unit.max_capacity}.")
|
||||
|
||||
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(3)
|
||||
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 100m
|
||||
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(self.time_delay) #wait a bit before trying again
|
||||
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)
|
||||
logger.info(f"Unit {self} has reached its destination.")
|
||||
new_patrol = self.position.project_with_bearing_and_distance(1000, self.transport_spawn_heading)
|
||||
await asyncio.sleep(self.time_delay) #wait a bit before trying again
|
||||
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 to_json(self):
|
||||
return {
|
||||
"is_embarker": self.unit.is_embarker,
|
||||
"is_moving": self.unit.is_moving,
|
||||
"is_loadable": self.unit.is_loadable,
|
||||
"in_embark_queue": self.unit.in_embark_queue if hasattr(self.unit, 'in_embark_queue') else False,
|
||||
"transport_unit": self.unit.transport_unit.ID if hasattr(self.unit, 'transport_unit') and self.unit.transport_unit else None
|
||||
}
|
||||
|
||||
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.")
|
||||
self.unit.set_custom_string("I am an 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_to_door = self.position.distance_to(door)
|
||||
distance_to_centre = self.position.distance_to(self.transport_unit.position)
|
||||
if distance_to_door < distance_to_centre:
|
||||
bearing = self.position.bearing_to(door)
|
||||
if hasattr(self,'nudge'):
|
||||
nudge_factor = self.nudge
|
||||
else:
|
||||
nudge_factor = 0
|
||||
destination = self.position.project_with_bearing_and_distance(distance_to_door+nudge_factor, bearing)
|
||||
destination.threshold = 2
|
||||
# Set the destination for the unit
|
||||
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:# distance_to_door >= distance_to_centre:
|
||||
if self.transport_unit.is_rear_loader:
|
||||
in_front_of_transport = self.transport_unit.position.project_with_bearing_and_distance(15, self.transport_unit.heading-pi)
|
||||
else:
|
||||
in_front_of_transport = self.transport_unit.position.project_with_bearing_and_distance(15, self.transport_unit.heading)
|
||||
bearing = self.position.bearing_to(in_front_of_transport)
|
||||
destination = self.position.project_with_bearing_and_distance(distance_to_door, bearing)
|
||||
destination.threshold = 2
|
||||
self.set_path([destination])
|
||||
self.register_on_destination_reached_callback(
|
||||
self.on_destination_reached,
|
||||
destination,
|
||||
threshold=2.0,
|
||||
timeout=10.0
|
||||
)
|
||||
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
|
||||
elif transport.name in transport_ground:
|
||||
if transport.door_argument_nos is None and transport.doors > 0:
|
||||
return transport.position.project_with_bearing_and_distance(2,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 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 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 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:
|
||||
#check the speed and distance, slow down if close
|
||||
distance_to_transport = embarker.position.distance_to(embarker.transport_unit.position)
|
||||
if distance_to_transport > 10 and embarker.speed < 1.4:
|
||||
embarker.set_speed(10)
|
||||
elif distance_to_transport < 10 and embarker.speed >= 3:
|
||||
embarker.set_speed(2)
|
||||
elif distance_to_transport < 5 and embarker.speed >= 1.3:
|
||||
embarker.set_speed(1.3)
|
||||
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:
|
||||
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(transport.position) < 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.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.set_speed(1.3)
|
||||
if hasattr(embarker, 'nudge'):
|
||||
embarker.nudge = embarker.nudge + 2
|
||||
else:
|
||||
embarker.nudge = 2
|
||||
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):
|
||||
await asyncio.sleep(alternate_time)
|
||||
transport.will_disembark = True
|
||||
|
||||
async def set_as_not_disembarking(transport):
|
||||
await asyncio.sleep(alternate_time)
|
||||
transport.will_disembark = False
|
||||
|
||||
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: # check speed is less than 2 m/s and doors are open
|
||||
first_two_spawns = True # Track if we are handling the first two spawns
|
||||
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
|
||||
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=""
|
||||
)
|
||||
|
||||
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.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
|
||||
new_unit.time_delay = transporter.max_capacity*2 - transporter.current_capacity # Random delay between 10 and 30 seconds
|
||||
|
||||
api.spawn_ground_units([spawn_table], transporter.coalition, "", True, 0, execution_callback)
|
||||
to_remove.append(disembarker)
|
||||
transporter.en_boarding_queue = []
|
||||
transporter.current_capacity -= 1
|
||||
transporter.set_cargo_weight(transporter.current_cargo_weight - 100) # Assume 100kg per infantry with kit
|
||||
transporter.current_cargo_weight -= 100
|
||||
|
||||
# Add a delay between spawns
|
||||
if len(open_doors) > 1 and first_two_spawns:
|
||||
# Shorter delay for the first two spawns if both doors are open
|
||||
await asyncio.sleep(0.5)
|
||||
first_two_spawns = False
|
||||
else:
|
||||
# Normal delay for subsequent spawns or single-door spawns
|
||||
await asyncio.sleep(2.5)
|
||||
for disembarker in to_remove:
|
||||
transporter.unit_array.remove(disembarker)
|
||||
if transporter.current_capacity == 0:
|
||||
await set_as_not_disembarking(transporter)
|
||||
|
||||
logger.info(f"Spawned unit '{disembarker.name}' from open door of transport '{transporter.name}'.")
|
||||
except Exception as e:
|
||||
logger.error(f"Error in check_for_unloadable_units: {e}")
|
||||
|
||||
def check_for_loadable_units():
|
||||
units = api.get_units()
|
||||
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:
|
||||
transporter.en_boarding_queue.append(embarker)
|
||||
embarker.in_embark_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
|
||||
|
||||
|
||||
#############
|
||||
#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 = []
|
||||
|
||||
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
|
||||
|
||||
async def update_data():
|
||||
units = api.get_units()
|
||||
for unit in units.values():
|
||||
if unit.alive and hasattr(unit, 'is_transport'):
|
||||
stringified_json = json.dumps(Transporter(unit).to_json())
|
||||
unit.set_custom_string(stringified_json)
|
||||
elif unit.alive and hasattr(unit, 'is_embarker'):
|
||||
stringified_json = json.dumps(Embarker(unit).to_json())
|
||||
unit.set_custom_string(stringified_json)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
async def on_api_update(api: API):
|
||||
generate_transport_units()
|
||||
check_for_loadable_units()
|
||||
asyncio.create_task(load_loadable_units())
|
||||
asyncio.create_task(check_for_unloadable_units())
|
||||
asyncio.create_task(update_data())
|
||||
|
||||
if __name__ == "__main__":
|
||||
api = API()
|
||||
api.register_on_update_callback(on_api_update)
|
||||
api.register_on_startup_callback(on_api_startup)
|
||||
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}})
|
||||
@@ -4,8 +4,6 @@ import inspect
|
||||
import difflib
|
||||
from slpp import slpp as lua
|
||||
|
||||
SEARCH_FOLDER = sys.argv[2]
|
||||
|
||||
from dcs.weapons_data import Weapons
|
||||
from dcs.planes import *
|
||||
from dcs.helicopters import *
|
||||
@@ -121,10 +119,7 @@ if len(sys.argv) > 1:
|
||||
# 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:
|
||||
try:
|
||||
@@ -133,10 +128,6 @@ if len(sys.argv) > 1:
|
||||
unitmap = plane_map
|
||||
elif (sys.argv[1] == "helicopter"):
|
||||
unitmap = helicopter_map
|
||||
elif (sys.argv[1] == "groundunit"):
|
||||
unitmap = vehicle_map
|
||||
elif (sys.argv[1] == "navyunit"):
|
||||
unitmap = ship_map
|
||||
lowercase_keys = [key.lower() for key in unitmap.keys()]
|
||||
res = difflib.get_close_matches(unit_name.lower(), lowercase_keys)
|
||||
if len(res) > 0:
|
||||
@@ -156,15 +147,26 @@ if len(sys.argv) > 1:
|
||||
"roles": ["No task", rename_task(cls.task_default.name)]
|
||||
}
|
||||
database[unit_name]["loadouts"].append(empty_loadout)
|
||||
|
||||
pylon_usage = {}
|
||||
for pylon_name in cls.pylons:
|
||||
pylon_usage[pylon_name] = []
|
||||
# The pylon data is expressed as a class named PylonX, where X is the pylon_name
|
||||
pylon_cls_name = f'Pylon{pylon_name}'
|
||||
if hasattr(cls, pylon_cls_name):
|
||||
pylon_cls = getattr(cls, pylon_cls_name)
|
||||
# The pylon class has as many attributes as there are possible weapons for that pylon
|
||||
for attr_name in dir(pylon_cls):
|
||||
if not attr_name.startswith('__') and not callable(getattr(pylon_cls, attr_name)):
|
||||
weapon_data = getattr(pylon_cls, attr_name)
|
||||
if isinstance(weapon_data[1], dict) and "clsid" in weapon_data[1]:
|
||||
pylon_usage[pylon_name].append(weapon_data[1])
|
||||
|
||||
# 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]
|
||||
}
|
||||
for pylon_name in pylon_usage:
|
||||
pylon_data = pylon_usage[pylon_name]
|
||||
database[unit_name]["acceptedPayloads"][pylon_name] = pylon_usage[pylon_name]
|
||||
|
||||
# Loop on all the loadouts for that unit
|
||||
for payload_name in unit_payloads[unit_name]:
|
||||
|
||||
144
scripts/python/add_loadout_types.py
Normal file
144
scripts/python/add_loadout_types.py
Normal file
@@ -0,0 +1,144 @@
|
||||
"""Add a "type" entry to each loadout item in an aircraft/helicopter database JSON.
|
||||
|
||||
Usage: run from repository root (or adjust paths) using python.
|
||||
Creates a backup of the file before overwriting.
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import re
|
||||
|
||||
# Paths to database files to process (aircraft + helicopter)
|
||||
DB_PATHS = [
|
||||
Path(r"c:\Users\dpass\Documents\GitHub\DCSOlympus\mock-dcs\Mods\Services\Olympus\databases\units\aircraftdatabase.json"),
|
||||
Path(r"c:\Users\dpass\Documents\GitHub\DCSOlympus\mock-dcs\Mods\Services\Olympus\databases\units\helicopterdatabase.json"),
|
||||
]
|
||||
BACKUP_SUFFIX = ".bak"
|
||||
MAP_PATH = Path(r"c:\Users\dpass\Documents\GitHub\DCSOlympus\scripts\python\loadout_type_map.json")
|
||||
|
||||
# Simple keyword -> type mapping. Order matters: first match wins.
|
||||
TYPE_MAP = [
|
||||
# Expanded A/A missiles (common and many variants)
|
||||
(r"\bAIM-?\d+\b|Sidewinder|AIM-9|R-73|R-27|R-60|R-77|R-24R|R-24T|R-33|R-40RD|R-40TD|R530|R530F|SD-10A|SD-10|MICA|Matra|Magic|Super\s*530|PL-5|PL-8|PL-5E|PL-8B|R550|S530D|S530F|RVV|AA-", "A/A missile"),
|
||||
# A/G / anti-ship missiles
|
||||
(r"\bAGM-?\d+\b|Maverick|ASM|Anti-ship|Harpoon|Kh-35|Kh-31|Exocet|AGM-65|AGM-84|Kh-22|Kh-25MPU|Kh-58U|Kh-66|YJ-12|YJ-83|YJ-83K|C-802|CM802|KD-20|KD-63|KG-600", "A/G missile"),
|
||||
# Guided bombs / glide weapons
|
||||
(r"\bGBU-?\d+\b|JDAM|Laser Guided Bomb|GBU|JSOW|LS-6|MPRL|BROACH", "Guided bomb"),
|
||||
# General purpose bombs and mk-series
|
||||
(r"\bMk-?\d+\b|\bFAB-?\d+\b|500lb|2000lb|GP Bomb|GP Bombs|Bomb|MC Mk|S.A.P\.|GP Mk", "General purpose bomb"),
|
||||
# Cluster bombs
|
||||
(r"\bCBU-?\d+\b|Cluster Bomb|BLU-|SFW|CEM", "Cluster bomb"),
|
||||
# Practice / training munitions
|
||||
(r"\bBDU-?\w*\b|Practice Bomb|Captive Trg|CAP-?\d+|CATM-?\b", "Practice/Training munition"),
|
||||
# Unguided rockets and rocket pods
|
||||
(r"\bLAU-?\d+\b|Hydra|Hydra 70|70 mm|M156|M151|MK151|APKWS|S-5M|S-8|S-13|S-25|UB-16|UB-32|RP-3|B-13L|ORO-57K|R-?|RP-3|R-?P-?3", "Unguided rocket"),
|
||||
# Targeting pods and cameras
|
||||
(r"\bAN/AAQ-?\d+\b|AN/ASQ-?\d+|Laser Spot Tracker|LST/SCAM|Targeting Pod|LITENING|TGP|TGM-?\d+|LANTIRN|FLIR|Pod", "Targeting pod"),
|
||||
# ECM and jammer pods
|
||||
(r"\bALQ-?\d+\b|ECM Pod|ECM|Jammer|U22/A|U22A", "ECM pod"),
|
||||
# Flares and dispensers (chaff/flares/countermeasures)
|
||||
(r"\bALE-?40\b|BOZ-107|Dispenser|Disperser|Countermeasure Dispenser|BOZ|ALE-40|SUU-?\d+|flares|Flare|LUU-2|Flare|Dispenser\(Empty\)", "Flares/Dispensers"),
|
||||
# Training rounds / captive
|
||||
(r"\bCATM|CAP-?9|TGM-?\d+|CATM", "Training/trg round"),
|
||||
(r"\bTGM-?\d+|TGM|CATM", "Training/trg round"),
|
||||
# Fuel tanks (various naming conventions)
|
||||
(r"\bFuel Tank\b|Fuel tank|Drop Tank|External[- ]?tank|Auxiliary Drop Tank|Sargent Fletcher Fuel Tank|RP35 Pylon Fuel Tank|RPL \d+|Cylindrical Tip Tank|Elliptic Tip Tank|\b\d+\s*(?:gal|gallons|liters|litres|L|lt)\b|1150L|1400L|2000L|3000L", "Fuel tank"),
|
||||
# Practice of captive or other small categories
|
||||
(r"\bMk-82 AIR Ballute|Ballute", "General purpose bomb"),
|
||||
# Misc / smoke / oil tanks / containers
|
||||
(r"\bSmoke\b|Smoke Generator|Smoke System|White Smoke|red colorant|yellow colorant|Color Oil Tank|White Oil Tank", "Misc"),
|
||||
# Pylons, containers and luggage
|
||||
(r"\bPYLON|Pylon|MPS-410|CLB4-PYLON|Luggage Container|Container", "Pylon"),
|
||||
# Guns and cannon mounts
|
||||
(r"\bDEFA-553|Browning|7.62mm|12.7mm|GPMG|Gun|Cannon", "Gun"),
|
||||
# Fallback guided bomb entries covered specifically
|
||||
(r"\bGBU-12|GBU-10|GBU-31|GBU-38", "Guided bomb"),
|
||||
]
|
||||
|
||||
# Default type when no pattern matches
|
||||
DEFAULT_TYPE = "unknown"
|
||||
|
||||
|
||||
def detect_type(item_name: str) -> str:
|
||||
name = item_name or ""
|
||||
# normalize
|
||||
s = name
|
||||
# 1) try mapping file exact match
|
||||
if MAP_PATH.exists():
|
||||
try:
|
||||
with MAP_PATH.open('r', encoding='utf-8') as mf:
|
||||
mapping = json.load(mf)
|
||||
except Exception:
|
||||
mapping = {}
|
||||
# exact name match (case-sensitive), then case-insensitive key match
|
||||
if name in mapping and mapping[name]:
|
||||
return mapping[name]
|
||||
# case-insensitive exact
|
||||
lower_map = {k.lower(): v for k, v in mapping.items() if v}
|
||||
if name.lower() in lower_map:
|
||||
return lower_map[name.lower()]
|
||||
# substring mapping: if a mapping key is contained in the name, use it
|
||||
for k, v in mapping.items():
|
||||
if not v:
|
||||
continue
|
||||
if k.lower() in name.lower():
|
||||
return v
|
||||
|
||||
for pattern, t in TYPE_MAP:
|
||||
if re.search(pattern, s, re.IGNORECASE):
|
||||
return t
|
||||
return DEFAULT_TYPE
|
||||
|
||||
|
||||
def process_db(db_path: Path):
|
||||
if not db_path.exists():
|
||||
print(f"Database file not found: {db_path}")
|
||||
return
|
||||
|
||||
backup_path = db_path.with_suffix(db_path.suffix + BACKUP_SUFFIX)
|
||||
shutil.copy2(db_path, backup_path)
|
||||
print(f"Created backup: {backup_path}")
|
||||
|
||||
with db_path.open("r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
total_items = 0
|
||||
updated_items = 0
|
||||
type_counts = {}
|
||||
|
||||
# data is a dict of vehicles (aircraft or helicopter)
|
||||
for ac_name, ac in data.items():
|
||||
loadouts = ac.get("loadouts")
|
||||
if not isinstance(loadouts, list):
|
||||
continue
|
||||
for loadout in loadouts:
|
||||
items = loadout.get("items")
|
||||
if not isinstance(items, list):
|
||||
continue
|
||||
for item in items:
|
||||
total_items += 1
|
||||
name = item.get("name", "")
|
||||
t = detect_type(name)
|
||||
prev = item.get("type")
|
||||
if prev != t:
|
||||
item["type"] = t
|
||||
updated_items += 1
|
||||
type_counts[t] = type_counts.get(t, 0) + 1
|
||||
|
||||
# write back
|
||||
with db_path.open("w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||
|
||||
print(f"Processed {total_items} loadout items, updated {updated_items} entries for {db_path.name}.")
|
||||
print("Type counts:")
|
||||
for k, v in sorted(type_counts.items(), key=lambda x: -x[1]):
|
||||
print(f" {k}: {v}")
|
||||
|
||||
|
||||
def main():
|
||||
for p in DB_PATHS:
|
||||
process_db(p)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
65
scripts/python/loadout_type_map.json
Normal file
65
scripts/python/loadout_type_map.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"2 x FAB-250": "General purpose bomb",
|
||||
"2 x FAB-500": "General purpose bomb",
|
||||
"6 x AGM-86D on MER": "A/G missile",
|
||||
"8 x AGM-86D": "A/G missile",
|
||||
"<CLEAN>": "Empty",
|
||||
"AGM-114K * 2": "A/G missile",
|
||||
"AGM-114K Hellfire": "A/G missile",
|
||||
"AGM-154C - JSOW Unitary BROACH": "Guided bomb",
|
||||
"AGM-88C HARM - High Speed Anti-Radiation Missile": "A/G missile",
|
||||
"AIM-120B AMRAAM - Active Radar AAM": "A/A missile",
|
||||
"AIM-120C AMRAAM - Active Radar AAM": "A/A missile",
|
||||
"AIM-7E Sparrow Semi-Active Radar": "A/A missile",
|
||||
"AIM-7E-2 Sparrow Semi-Active Radar": "A/A missile",
|
||||
"AIM-7F": "A/A missile",
|
||||
"AIM-7M": "A/A missile",
|
||||
"AIM-7M Sparrow Semi-Active Radar": "A/A missile",
|
||||
"AIM-7MH Sparrow Semi-Active Radar": "A/A missile",
|
||||
"AKD-10": "A/G missile",
|
||||
"ALARM": "A/G missile",
|
||||
"AN-M3 - 2*Browning Machine Guns 12.7mm": "Gun",
|
||||
"AN/ASQ-213 HTS - HARM Targeting System": "Targeting pod",
|
||||
"APU-13U-2 with R-3R (AA-2 Atoll-C) - Semi Active AAM": "A/A missile",
|
||||
"APU-13U-2 with R-3S (AA-2 Atoll-B) - IR AAM": "A/A missile",
|
||||
"BAP-100 x 18": "Unguided rocket",
|
||||
"BLG-66-AC Belouga": "Cluster bomb",
|
||||
"C-701IR": "A/G missile",
|
||||
"C-701T": "A/G missile",
|
||||
"C-802AK": "A/G missile",
|
||||
"CLB4-PYLON-SAMP250HD": "Pylon",
|
||||
"CLB4-PYLON-SAMP400LD": "Pylon",
|
||||
"CM802AKG (DIS)": "A/G missile",
|
||||
"DEFA-553 - 30mm Revolver Cannon": "Gun",
|
||||
"ETHER": "Misc",
|
||||
"FAB-100SV": "General purpose bomb",
|
||||
"GB-6": "General purpose bomb",
|
||||
"GB-6-HE": "General purpose bomb",
|
||||
"K-13A": "A/A missile",
|
||||
"KD-20": "A/G missile",
|
||||
"KD-63": "A/G missile",
|
||||
"KG-600": "A/G missile",
|
||||
"Kh-22 (AS-4 Kitchen) - 1000kg, AShM, IN & Act/Pas Rdr": "A/G missile",
|
||||
"Kh-25MPU (Updated AS-12 Kegler) - 320kg, ARM, IN & Pas Rdr": "A/G missile",
|
||||
"Kh-58U (AS-11 Kilter) - 640kg, ARM, IN & Pas Rdr": "A/G missile",
|
||||
"Kh-66 Grom (21) - AGM, radar guided APU-68": "A/G missile",
|
||||
"LD-10": "Fuel tank",
|
||||
"LD-10 x 2": "Fuel tank",
|
||||
"LS-6-100 Dual": "Guided bomb",
|
||||
"LS-6-250 Dual": "Guided bomb",
|
||||
"LS-6-500": "Guided bomb",
|
||||
"Luggage Container": "Container",
|
||||
"MPRL - 4 x AGM-154C - JSOW Unitary BROACH": "Guided bomb",
|
||||
"MPS-410": "Pylon",
|
||||
"Mk-82AIR": "General purpose bomb",
|
||||
"PK-3 - 7.62mm GPMG": "Gun",
|
||||
"PL-5EII": "A/A missile",
|
||||
"PL-8B": "A/A missile",
|
||||
"R550 Magic 1 IR AAM": "A/A missile",
|
||||
"R550 Magic 2 IR AAM": "A/A missile",
|
||||
"S530D": "A/A missile",
|
||||
"S530F": "A/A missile",
|
||||
"TYPE-200A": "Flares/Dispensers",
|
||||
"TYPE-200A Dual": "Flares/Dispensers",
|
||||
"{6C0D552F-570B-42ff-9F6D-F10D9C1D4E1C}": "Misc"
|
||||
}
|
||||
@@ -3774,20 +3774,73 @@
|
||||
}
|
||||
},
|
||||
"F4U-1D": {
|
||||
"Drop tank 175 US gal.": {
|
||||
"1": 11,
|
||||
"2": 18,
|
||||
"3": 19,
|
||||
"4": 17
|
||||
},
|
||||
"HVAR x 8": {
|
||||
"1": 32,
|
||||
"2": 31,
|
||||
"3": 30
|
||||
"1": 31
|
||||
},
|
||||
"Bomb x 2, HVAR x 4": {
|
||||
"1": 32,
|
||||
"2": 31,
|
||||
"3": 30
|
||||
"M-64 bomb x 2, HVAR x 8": {
|
||||
"1": 31
|
||||
},
|
||||
"Tiny Tim x2, HVAR x 4": {
|
||||
"1": 32,
|
||||
"2": 31,
|
||||
"3": 30
|
||||
"M-64 bomb x 3, HVAR x 6": {
|
||||
"1": 31
|
||||
},
|
||||
"M-64 bomb x 2, HVAR x 4": {
|
||||
"1": 32
|
||||
},
|
||||
"M-64 bomb, M-65 bomb x 2": {
|
||||
"1": 32
|
||||
},
|
||||
"Tiny Tim x 2, HVAR x 4": {
|
||||
"1": 32
|
||||
},
|
||||
"Bat Bomb": {
|
||||
"1": 30
|
||||
},
|
||||
"Tiny Tim x 2": {
|
||||
"1": 30
|
||||
},
|
||||
"Bat Bomb, HVAR x 8": {
|
||||
"1": 30
|
||||
}
|
||||
},
|
||||
"F4U-1D_CW": {
|
||||
"Drop tank 175 US gal.": {
|
||||
"1": 11,
|
||||
"2": 18,
|
||||
"3": 19,
|
||||
"4": 17
|
||||
},
|
||||
"HVAR x 8": {
|
||||
"1": 31
|
||||
},
|
||||
"M-64 bomb x 2, HVAR x 8": {
|
||||
"1": 31
|
||||
},
|
||||
"M-64 bomb x 3, HVAR x 6": {
|
||||
"1": 31
|
||||
},
|
||||
"M-64 bomb x 2, HVAR x 4": {
|
||||
"1": 32
|
||||
},
|
||||
"M-64 bomb, M-65 bomb x 2": {
|
||||
"1": 32
|
||||
},
|
||||
"Tiny Tim x 2, HVAR x 4": {
|
||||
"1": 32
|
||||
},
|
||||
"Bat Bomb": {
|
||||
"1": 30
|
||||
},
|
||||
"Tiny Tim x 2": {
|
||||
"1": 30
|
||||
},
|
||||
"Bat Bomb, HVAR x 8": {
|
||||
"1": 30
|
||||
}
|
||||
},
|
||||
"F/A-18A": {
|
||||
@@ -4742,6 +4795,209 @@
|
||||
},
|
||||
"AEROBATIC": {}
|
||||
},
|
||||
"MiG-29 Fulcrum": {
|
||||
"2 * R-27T, 4 * R-73": {
|
||||
"1": 11,
|
||||
"2": 18,
|
||||
"3": 19,
|
||||
"4": 10
|
||||
},
|
||||
"2 * R-27R, 4 * R-73": {
|
||||
"1": 11,
|
||||
"2": 18,
|
||||
"3": 19,
|
||||
"4": 10
|
||||
},
|
||||
"2 * R-27T, 2 * R-73": {
|
||||
"1": 11,
|
||||
"2": 18,
|
||||
"3": 19,
|
||||
"4": 10
|
||||
},
|
||||
"2 * R-27R, 2 * R-73": {
|
||||
"1": 11,
|
||||
"2": 18,
|
||||
"3": 19,
|
||||
"4": 10
|
||||
},
|
||||
"6 * R-73": {
|
||||
"1": 11,
|
||||
"2": 18,
|
||||
"3": 19,
|
||||
"4": 10
|
||||
},
|
||||
"2 * R-27T, 4 * R-60M": {
|
||||
"1": 11,
|
||||
"2": 18,
|
||||
"3": 19,
|
||||
"4": 10
|
||||
},
|
||||
"2 * R-27R, 4 * R-60M": {
|
||||
"1": 11,
|
||||
"2": 18,
|
||||
"3": 19,
|
||||
"4": 10
|
||||
},
|
||||
"2 * R-27T, 2 * R-60M": {
|
||||
"1": 11,
|
||||
"2": 18,
|
||||
"3": 19,
|
||||
"4": 10
|
||||
},
|
||||
"2 * R-27R, 2 * R-60M": {
|
||||
"1": 11,
|
||||
"2": 18,
|
||||
"3": 19,
|
||||
"4": 10
|
||||
},
|
||||
"6 * R-60": {
|
||||
"1": 11,
|
||||
"2": 18,
|
||||
"3": 19,
|
||||
"4": 10
|
||||
},
|
||||
"4 * S-24, 2 * R-73": {
|
||||
"1": 34,
|
||||
"2": 30,
|
||||
"3": 31,
|
||||
"4": 32
|
||||
},
|
||||
"4 * S-24, 2 * R-60M ": {
|
||||
"1": 34,
|
||||
"2": 30,
|
||||
"3": 31,
|
||||
"4": 32
|
||||
},
|
||||
"4 * S-24": {
|
||||
"1": 34,
|
||||
"2": 30,
|
||||
"3": 31,
|
||||
"4": 32
|
||||
},
|
||||
"80 * S-8OFP": {
|
||||
"1": 31,
|
||||
"2": 32
|
||||
},
|
||||
"80 * S-8OFP, 2 * R-60M": {
|
||||
"1": 31,
|
||||
"2": 32
|
||||
},
|
||||
"80 * S-8KOM, 2 * R-73": {
|
||||
"1": 31,
|
||||
"2": 32
|
||||
},
|
||||
"4 * BetAB-500, 2 * R-60": {
|
||||
"1": 34,
|
||||
"2": 32
|
||||
},
|
||||
"4 * BetAB-500, 2 * R-73": {
|
||||
"1": 34,
|
||||
"2": 32
|
||||
},
|
||||
"4 * BetAB-500": {
|
||||
"1": 34,
|
||||
"2": 32
|
||||
},
|
||||
"4 * KMGU": {
|
||||
"1": 31
|
||||
},
|
||||
"4 * KMGU AO-2.5RT, 2 * R-60M": {
|
||||
"1": 31
|
||||
},
|
||||
"4 * KMGU PTAB-2.5KO, 2 * R-73": {
|
||||
"1": 31
|
||||
},
|
||||
"2 * R-27T": {
|
||||
"1": 11,
|
||||
"2": 18,
|
||||
"3": 19,
|
||||
"4": 10
|
||||
},
|
||||
"2 * R-27R": {
|
||||
"1": 11,
|
||||
"2": 18,
|
||||
"3": 19,
|
||||
"4": 10
|
||||
},
|
||||
"4 * RBK-250 PTAB-2.5M, 2 * R-73": {
|
||||
"1": 31
|
||||
},
|
||||
"4 * RBK-250 AO-1SCh, 2 * R-73": {
|
||||
"1": 31
|
||||
},
|
||||
"4 * RBK-500 PTAB-1M, 2 * R-73": {
|
||||
"1": 31
|
||||
},
|
||||
"4 * RBK-500 PTAB-10, 2 * R-73": {
|
||||
"1": 31
|
||||
},
|
||||
"4 * FAB-250M-62, 2 * R-73": {
|
||||
"1": 31,
|
||||
"2": 32
|
||||
},
|
||||
"4 * FAB-500M-62, 2 * R-73": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 30
|
||||
},
|
||||
"80 * S-8OFP, 2 * R-73": {
|
||||
"1": 31
|
||||
},
|
||||
"4 * BetAB-500ShP, 2 * R-73": {
|
||||
"1": 34
|
||||
},
|
||||
"80 * S-8TsM, 2 * R-73": {
|
||||
"1": 16
|
||||
},
|
||||
"6 * R-60M": {
|
||||
"1": 19
|
||||
},
|
||||
"2 * R-27ER, 4 * R-73": {
|
||||
"1": 19,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 10
|
||||
},
|
||||
"2 * R-27ET, 4 * R-73": {
|
||||
"1": 19,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 10
|
||||
},
|
||||
"2 * R-27ER, 4 * R-60M": {
|
||||
"1": 19,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 10
|
||||
},
|
||||
"2 * R-27ER": {
|
||||
"1": 19,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 10
|
||||
},
|
||||
"2 * R-27ET": {
|
||||
"1": 19,
|
||||
"2": 11,
|
||||
"3": 18,
|
||||
"4": 10
|
||||
},
|
||||
"2 * R-27ER, 4 * R-73, Fuel": {
|
||||
"1": 18
|
||||
},
|
||||
"2 * R-27ET, 4 * R-73, fuel": {
|
||||
"1": 18
|
||||
},
|
||||
"6 * R-73, Fuel": {
|
||||
"1": 18
|
||||
},
|
||||
"6 * R-60M, Fuel": {
|
||||
"1": 18
|
||||
},
|
||||
"2 * R-73, Fuel": {
|
||||
"1": 18
|
||||
}
|
||||
},
|
||||
"Mirage-F1B": {
|
||||
"Clean": {
|
||||
"1": 10,
|
||||
@@ -6437,6 +6693,125 @@
|
||||
"1": 18
|
||||
}
|
||||
},
|
||||
"Mi-28N": {
|
||||
"2xFAB-250": {
|
||||
"1": 32
|
||||
},
|
||||
"4xFuel tank": {
|
||||
"1": 15
|
||||
},
|
||||
"80xS-8": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"4xKMGU AP": {
|
||||
"1": 32
|
||||
},
|
||||
"4xUPK-23": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"16x9M114, 2xKMGU AT": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"4xFAB-500": {
|
||||
"1": 32
|
||||
},
|
||||
"16x9M114, 2xFAB-500": {
|
||||
"1": 32
|
||||
},
|
||||
"40xS-8": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"40xS-8 TsM": {
|
||||
"1": 16
|
||||
},
|
||||
"2xKMGU AP": {
|
||||
"1": 32
|
||||
},
|
||||
"2xUPK-23": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"16x9M114, 2xUPK-23": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"2xFAB-500": {
|
||||
"1": 32
|
||||
},
|
||||
"16x9M114, 40xS-8": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18,
|
||||
"4": 30
|
||||
},
|
||||
"16x9M114": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"20xS-13": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"16x9M114, 2xKMGU AP": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"4xFAB-250": {
|
||||
"1": 32
|
||||
},
|
||||
"4xKMGU AT": {
|
||||
"1": 32
|
||||
},
|
||||
"16x9M114, 40xS-8 TsM": {
|
||||
"1": 16
|
||||
},
|
||||
"80xS-8 TsM": {
|
||||
"1": 16
|
||||
},
|
||||
"2xKMGU AT": {
|
||||
"1": 32
|
||||
},
|
||||
"9x9M114": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"2xFuel tank": {
|
||||
"1": 15
|
||||
},
|
||||
"10xS-13": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"2xFAB-250, 16x9M114": {
|
||||
"1": 32
|
||||
},
|
||||
"16x9M114, 10xS-13": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18,
|
||||
"4": 30
|
||||
}
|
||||
},
|
||||
"Tu-95MS": {
|
||||
"Kh-65*6": {
|
||||
"1": 33
|
||||
}
|
||||
},
|
||||
"B-1B": {
|
||||
"Mk-82*84": {
|
||||
"1": 34,
|
||||
@@ -7625,120 +8000,6 @@
|
||||
}
|
||||
},
|
||||
"Mi-26": {},
|
||||
"Mi-28N": {
|
||||
"2xFAB-250": {
|
||||
"1": 32
|
||||
},
|
||||
"4xFuel tank": {
|
||||
"1": 15
|
||||
},
|
||||
"80xS-8": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"4xKMGU AP": {
|
||||
"1": 32
|
||||
},
|
||||
"4xUPK-23": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"16x9M114, 2xKMGU AT": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"4xFAB-500": {
|
||||
"1": 32
|
||||
},
|
||||
"16x9M114, 2xFAB-500": {
|
||||
"1": 32
|
||||
},
|
||||
"40xS-8": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"40xS-8 TsM": {
|
||||
"1": 16
|
||||
},
|
||||
"2xKMGU AP": {
|
||||
"1": 32
|
||||
},
|
||||
"2xUPK-23": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"16x9M114, 2xUPK-23": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"2xFAB-500": {
|
||||
"1": 32
|
||||
},
|
||||
"16x9M114, 40xS-8": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18,
|
||||
"4": 30
|
||||
},
|
||||
"16x9M114": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"20xS-13": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"16x9M114, 2xKMGU AP": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"4xFAB-250": {
|
||||
"1": 32
|
||||
},
|
||||
"4xKMGU AT": {
|
||||
"1": 32
|
||||
},
|
||||
"16x9M114, 40xS-8 TsM": {
|
||||
"1": 16
|
||||
},
|
||||
"80xS-8 TsM": {
|
||||
"1": 16
|
||||
},
|
||||
"2xKMGU AT": {
|
||||
"1": 32
|
||||
},
|
||||
"9x9M114": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"2xFuel tank": {
|
||||
"1": 15
|
||||
},
|
||||
"10xS-13": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18
|
||||
},
|
||||
"2xFAB-250, 16x9M114": {
|
||||
"1": 32
|
||||
},
|
||||
"16x9M114, 10xS-13": {
|
||||
"1": 31,
|
||||
"2": 32,
|
||||
"3": 18,
|
||||
"4": 30
|
||||
}
|
||||
},
|
||||
"Mi-8MT": {
|
||||
"4 x B8": {
|
||||
"1": 32
|
||||
@@ -9158,11 +9419,6 @@
|
||||
"2": 34
|
||||
}
|
||||
},
|
||||
"Tu-95MS": {
|
||||
"Kh-65*6": {
|
||||
"1": 33
|
||||
}
|
||||
},
|
||||
"UH-1H": {
|
||||
"M134 Minigun*2, XM158*2": {
|
||||
"1": 32,
|
||||
|
||||
@@ -1939,6 +1939,11 @@
|
||||
]
|
||||
},
|
||||
"F4U-1D": {
|
||||
"6": [
|
||||
"{175_USgal_Corsair_droptank_aux}",
|
||||
"{AN-M64}",
|
||||
"{ASM_N_2}"
|
||||
],
|
||||
"1": [
|
||||
"{HVAR_USN_Mk28_Mod4_Corsair}"
|
||||
],
|
||||
@@ -1951,9 +1956,47 @@
|
||||
"4": [
|
||||
"{HVAR_USN_Mk28_Mod4_Corsair}"
|
||||
],
|
||||
"11": [
|
||||
"{HVAR_USN_Mk28_Mod4_Corsair}"
|
||||
],
|
||||
"10": [
|
||||
"{HVAR_USN_Mk28_Mod4_Corsair}"
|
||||
],
|
||||
"9": [
|
||||
"{HVAR_USN_Mk28_Mod4_Corsair}"
|
||||
],
|
||||
"8": [
|
||||
"{HVAR_USN_Mk28_Mod4_Corsair}"
|
||||
],
|
||||
"5": [
|
||||
"{AN-M64}",
|
||||
"{AN_M65}",
|
||||
"{Tiny_Tim_Corsair_L}"
|
||||
],
|
||||
"7": [
|
||||
"{AN-M64}",
|
||||
"{AN_M65}",
|
||||
"{Tiny_Tim_Corsair_R}"
|
||||
]
|
||||
},
|
||||
"F4U-1D_CW": {
|
||||
"6": [
|
||||
"{175_USgal_Corsair_droptank_aux}",
|
||||
"{AN-M64}",
|
||||
"{ASM_N_2}"
|
||||
],
|
||||
"1": [
|
||||
"{HVAR_USN_Mk28_Mod4_Corsair}"
|
||||
],
|
||||
"2": [
|
||||
"{HVAR_USN_Mk28_Mod4_Corsair}"
|
||||
],
|
||||
"3": [
|
||||
"{HVAR_USN_Mk28_Mod4_Corsair}"
|
||||
],
|
||||
"4": [
|
||||
"{HVAR_USN_Mk28_Mod4_Corsair}"
|
||||
],
|
||||
"11": [
|
||||
"{HVAR_USN_Mk28_Mod4_Corsair}"
|
||||
],
|
||||
@@ -1963,13 +2006,18 @@
|
||||
"9": [
|
||||
"{HVAR_USN_Mk28_Mod4_Corsair}"
|
||||
],
|
||||
"7": [
|
||||
"{AN-M64}",
|
||||
"{Tiny_Tim_Corsair}"
|
||||
"8": [
|
||||
"{HVAR_USN_Mk28_Mod4_Corsair}"
|
||||
],
|
||||
"5": [
|
||||
"{AN-M64}",
|
||||
"{Tiny_Tim_Corsair}"
|
||||
"{AN_M65}",
|
||||
"{Tiny_Tim_Corsair_L}"
|
||||
],
|
||||
"7": [
|
||||
"{AN-M64}",
|
||||
"{AN_M65}",
|
||||
"{Tiny_Tim_Corsair_R}"
|
||||
]
|
||||
},
|
||||
"F/A-18A": {
|
||||
@@ -2718,6 +2766,103 @@
|
||||
"{SMOKE_WHITE}"
|
||||
]
|
||||
},
|
||||
"MiG-29 Fulcrum": {
|
||||
"1": [
|
||||
"{FBC29BFE-3D24-4C64-B81D-941239D12249}",
|
||||
"{682A481F-0CB5-4693-A382-D00DD4A156D7}",
|
||||
"{R_60}"
|
||||
],
|
||||
"2": [
|
||||
"{FBC29BFE-3D24-4C64-B81D-941239D12249}",
|
||||
"{682A481F-0CB5-4693-A382-D00DD4A156D7}",
|
||||
"{R_60}",
|
||||
"{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}",
|
||||
"B-8M1 - 20 S-8OFP2",
|
||||
"{F72F47E5-C83A-4B85-96ED-D3E46671EE9A}",
|
||||
"{35B698AC-9FEF-4EC4-AD29-484A0085F62B}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74884}",
|
||||
"{4203753F-8198-4E85-9924-6F8FF679F9FF}",
|
||||
"{RBK_250_275_AO_1SCH}",
|
||||
"{7AEC222D-C523-425e-B714-719C0D1EB14D}",
|
||||
"{D5435F26-F120-4FA3-9867-34ACE562EF1B}",
|
||||
"{FAB_250_M62}",
|
||||
"{37DCC01E-9E02-432F-B61D-10C166CA2798}",
|
||||
"{BD289E34-DF84-4C5E-9220-4B14C346E79D}",
|
||||
"{3DFB7320-AB0E-11d7-9897-000476191836}"
|
||||
],
|
||||
"3": [
|
||||
"{88DAC840-9F75-4531-8689-B46E64E42E53}",
|
||||
"{9B25D316-0434-4954-868F-D51DB1A38DF0}",
|
||||
"{FBC29BFE-3D24-4C64-B81D-941239D12249}",
|
||||
"{R_60}",
|
||||
"{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}",
|
||||
"B-8M1 - 20 S-8OFP2",
|
||||
"{F72F47E5-C83A-4B85-96ED-D3E46671EE9A}",
|
||||
"{35B698AC-9FEF-4EC4-AD29-484A0085F62B}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74884}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74881}",
|
||||
"{4203753F-8198-4E85-9924-6F8FF679F9FF}",
|
||||
"{RBK_250_275_AO_1SCH}",
|
||||
"{7AEC222D-C523-425e-B714-719C0D1EB14D}",
|
||||
"{D5435F26-F120-4FA3-9867-34ACE562EF1B}",
|
||||
"{FAB_250_M62}",
|
||||
"{37DCC01E-9E02-432F-B61D-10C166CA2798}",
|
||||
"{BD289E34-DF84-4C5E-9220-4B14C346E79D}",
|
||||
"{3DFB7320-AB0E-11d7-9897-000476191836}",
|
||||
"{682A481F-0CB5-4693-A382-D00DD4A156D7}",
|
||||
"{E8069896-8435-4B90-95C0-01A03AE6E400}",
|
||||
"{B79C379A-9E87-4E50-A1EE-7F7E29C2E87A}"
|
||||
],
|
||||
"5": [
|
||||
"{88DAC840-9F75-4531-8689-B46E64E42E53}",
|
||||
"{9B25D316-0434-4954-868F-D51DB1A38DF0}",
|
||||
"{FBC29BFE-3D24-4C64-B81D-941239D12249}",
|
||||
"{R_60}",
|
||||
"{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}",
|
||||
"B-8M1 - 20 S-8OFP2",
|
||||
"{F72F47E5-C83A-4B85-96ED-D3E46671EE9A}",
|
||||
"{35B698AC-9FEF-4EC4-AD29-484A0085F62B}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74884}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74881}",
|
||||
"{4203753F-8198-4E85-9924-6F8FF679F9FF}",
|
||||
"{RBK_250_275_AO_1SCH}",
|
||||
"{7AEC222D-C523-425e-B714-719C0D1EB14D}",
|
||||
"{D5435F26-F120-4FA3-9867-34ACE562EF1B}",
|
||||
"{FAB_250_M62}",
|
||||
"{37DCC01E-9E02-432F-B61D-10C166CA2798}",
|
||||
"{BD289E34-DF84-4C5E-9220-4B14C346E79D}",
|
||||
"{3DFB7320-AB0E-11d7-9897-000476191836}",
|
||||
"{682A481F-0CB5-4693-A382-D00DD4A156D7}",
|
||||
"{E8069896-8435-4B90-95C0-01A03AE6E400}",
|
||||
"{B79C379A-9E87-4E50-A1EE-7F7E29C2E87A}"
|
||||
],
|
||||
"6": [
|
||||
"{FBC29BFE-3D24-4C64-B81D-941239D12249}",
|
||||
"{682A481F-0CB5-4693-A382-D00DD4A156D7}",
|
||||
"{R_60}",
|
||||
"{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}",
|
||||
"B-8M1 - 20 S-8OFP2",
|
||||
"{F72F47E5-C83A-4B85-96ED-D3E46671EE9A}",
|
||||
"{35B698AC-9FEF-4EC4-AD29-484A0085F62B}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74884}",
|
||||
"{4203753F-8198-4E85-9924-6F8FF679F9FF}",
|
||||
"{RBK_250_275_AO_1SCH}",
|
||||
"{7AEC222D-C523-425e-B714-719C0D1EB14D}",
|
||||
"{D5435F26-F120-4FA3-9867-34ACE562EF1B}",
|
||||
"{FAB_250_M62}",
|
||||
"{37DCC01E-9E02-432F-B61D-10C166CA2798}",
|
||||
"{BD289E34-DF84-4C5E-9220-4B14C346E79D}",
|
||||
"{3DFB7320-AB0E-11d7-9897-000476191836}"
|
||||
],
|
||||
"7": [
|
||||
"{FBC29BFE-3D24-4C64-B81D-941239D12249}",
|
||||
"{682A481F-0CB5-4693-A382-D00DD4A156D7}",
|
||||
"{R_60}"
|
||||
],
|
||||
"4": [
|
||||
"{PTB_1500_MIG29A}"
|
||||
]
|
||||
},
|
||||
"Mirage-F1B": {
|
||||
"1": [
|
||||
"<CLEAN>",
|
||||
@@ -4365,6 +4510,59 @@
|
||||
"{FAS}"
|
||||
]
|
||||
},
|
||||
"Mi-28N": {
|
||||
"2": [
|
||||
"{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
|
||||
"{PTB_450}",
|
||||
"{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74884}",
|
||||
"{05544F1A-C39C-466b-BC37-5BD1D52E57BB}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74881}",
|
||||
"{37DCC01E-9E02-432F-B61D-10C166CA2798}",
|
||||
"B_8V20A_CM",
|
||||
"{FC56DF80-9B09-44C5-8976-DCFAFF219062}"
|
||||
],
|
||||
"3": [
|
||||
"{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
|
||||
"{PTB_450}",
|
||||
"{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74884}",
|
||||
"{05544F1A-C39C-466b-BC37-5BD1D52E57BB}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74881}",
|
||||
"{37DCC01E-9E02-432F-B61D-10C166CA2798}",
|
||||
"B_8V20A_CM",
|
||||
"{FC56DF80-9B09-44C5-8976-DCFAFF219062}"
|
||||
],
|
||||
"1": [
|
||||
"{PTB_450}",
|
||||
"{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74884}",
|
||||
"{05544F1A-C39C-466b-BC37-5BD1D52E57BB}",
|
||||
"{57232979-8B0F-4db7-8D9A-55197E06B0F5}",
|
||||
"{37DCC01E-9E02-432F-B61D-10C166CA2798}",
|
||||
"{FC56DF80-9B09-44C5-8976-DCFAFF219062}",
|
||||
"{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74881}",
|
||||
"B_8V20A_CM"
|
||||
],
|
||||
"4": [
|
||||
"{PTB_450}",
|
||||
"{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74884}",
|
||||
"{05544F1A-C39C-466b-BC37-5BD1D52E57BB}",
|
||||
"{57232979-8B0F-4db7-8D9A-55197E06B0F5}",
|
||||
"{37DCC01E-9E02-432F-B61D-10C166CA2798}",
|
||||
"{FC56DF80-9B09-44C5-8976-DCFAFF219062}",
|
||||
"{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74881}",
|
||||
"B_8V20A_CM"
|
||||
]
|
||||
},
|
||||
"Tu-95MS": {
|
||||
"1": [
|
||||
"{0290F5DE-014A-4BB1-9843-D717749B1DED}"
|
||||
]
|
||||
},
|
||||
"B-1B": {
|
||||
"1": [
|
||||
"MK_82*28",
|
||||
@@ -5365,54 +5563,6 @@
|
||||
]
|
||||
},
|
||||
"Mi-26": {},
|
||||
"Mi-28N": {
|
||||
"2": [
|
||||
"{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
|
||||
"{PTB_450}",
|
||||
"{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74884}",
|
||||
"{05544F1A-C39C-466b-BC37-5BD1D52E57BB}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74881}",
|
||||
"{37DCC01E-9E02-432F-B61D-10C166CA2798}",
|
||||
"B_8V20A_CM",
|
||||
"{FC56DF80-9B09-44C5-8976-DCFAFF219062}"
|
||||
],
|
||||
"3": [
|
||||
"{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
|
||||
"{PTB_450}",
|
||||
"{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74884}",
|
||||
"{05544F1A-C39C-466b-BC37-5BD1D52E57BB}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74881}",
|
||||
"{37DCC01E-9E02-432F-B61D-10C166CA2798}",
|
||||
"B_8V20A_CM",
|
||||
"{FC56DF80-9B09-44C5-8976-DCFAFF219062}"
|
||||
],
|
||||
"1": [
|
||||
"{PTB_450}",
|
||||
"{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74884}",
|
||||
"{05544F1A-C39C-466b-BC37-5BD1D52E57BB}",
|
||||
"{57232979-8B0F-4db7-8D9A-55197E06B0F5}",
|
||||
"{37DCC01E-9E02-432F-B61D-10C166CA2798}",
|
||||
"{FC56DF80-9B09-44C5-8976-DCFAFF219062}",
|
||||
"{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74881}",
|
||||
"B_8V20A_CM"
|
||||
],
|
||||
"4": [
|
||||
"{PTB_450}",
|
||||
"{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74884}",
|
||||
"{05544F1A-C39C-466b-BC37-5BD1D52E57BB}",
|
||||
"{57232979-8B0F-4db7-8D9A-55197E06B0F5}",
|
||||
"{37DCC01E-9E02-432F-B61D-10C166CA2798}",
|
||||
"{FC56DF80-9B09-44C5-8976-DCFAFF219062}",
|
||||
"{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
|
||||
"{96A7F676-F956-404A-AD04-F33FB2C74881}",
|
||||
"B_8V20A_CM"
|
||||
]
|
||||
},
|
||||
"Mi-8MT": {
|
||||
"5": [
|
||||
"{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}",
|
||||
@@ -7042,11 +7192,6 @@
|
||||
"{E1AAE713-5FC3-4CAA-9FF5-3FDCFB899E33}"
|
||||
]
|
||||
},
|
||||
"Tu-95MS": {
|
||||
"1": [
|
||||
"{0290F5DE-014A-4BB1-9843-D717749B1DED}"
|
||||
]
|
||||
},
|
||||
"UH-1H": {
|
||||
"1": [
|
||||
"M134_L"
|
||||
|
||||
@@ -6617,26 +6617,116 @@ 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}"},
|
||||
["F4U-1D"]={["Drop tank 175 US gal."]={[6] = {["CLSID"]="{175_USgal_Corsair_droptank_aux}"}},
|
||||
["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}"},
|
||||
[8] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"}},
|
||||
["M-64 bomb x 2, 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}"},
|
||||
[5] = {["CLSID"]="{AN-M64}"},
|
||||
[7] = {["CLSID"]="{AN-M64}"},
|
||||
[8] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[9] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[10] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[11] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"}},
|
||||
["M-64 bomb x 3, HVAR x 6"]={[2] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[3] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[4] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[5] = {["CLSID"]="{AN-M64}"},
|
||||
[6] = {["CLSID"]="{AN-M64}"},
|
||||
[7] = {["CLSID"]="{AN-M64}"},
|
||||
[8] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[9] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[10] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"}},
|
||||
["M-64 bomb x 2, HVAR x 4"]={[3] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[4] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[5] = {["CLSID"]="{AN_M65}"},
|
||||
[7] = {["CLSID"]="{AN_M65}"},
|
||||
[8] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[9] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"}},
|
||||
["Bomb x 2, HVAR x 4"]={[7] = {["CLSID"]="{AN-M64}"},
|
||||
["M-64 bomb, M-65 bomb x 2"]={[5] = {["CLSID"]="{AN_M65}"},
|
||||
[6] = {["CLSID"]="{AN-M64}"},
|
||||
[7] = {["CLSID"]="{AN_M65}"}},
|
||||
["Tiny Tim x 2, HVAR x 4"]={[3] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[4] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[5] = {["CLSID"]="{Tiny_Tim_Corsair_L}"},
|
||||
[7] = {["CLSID"]="{Tiny_Tim_Corsair_R}"},
|
||||
[8] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[9] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"}},
|
||||
["Bat Bomb"]={[6] = {["CLSID"]="{ASM_N_2}"}},
|
||||
["Tiny Tim x 2"]={[5] = {["CLSID"]="{Tiny_Tim_Corsair_L}"},
|
||||
[7] = {["CLSID"]="{Tiny_Tim_Corsair_R}"}},
|
||||
["Bat Bomb, 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}"},
|
||||
[9] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[5] = {["CLSID"]="{AN-M64}"}},
|
||||
["Tiny Tim x2, HVAR x 4"]={[7] = {["CLSID"]="{Tiny_Tim_Corsair}"},
|
||||
[6] = {["CLSID"]="{ASM_N_2}"},
|
||||
[8] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[9] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[10] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[11] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"}}},
|
||||
["F4U-1D_CW"]={["Drop tank 175 US gal."]={[6] = {["CLSID"]="{175_USgal_Corsair_droptank_aux}"}},
|
||||
["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}"},
|
||||
[11] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[10] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[9] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[5] = {["CLSID"]="{Tiny_Tim_Corsair}"}}},
|
||||
[8] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"}},
|
||||
["M-64 bomb x 2, 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}"},
|
||||
[5] = {["CLSID"]="{AN-M64}"},
|
||||
[7] = {["CLSID"]="{AN-M64}"},
|
||||
[8] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[9] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[10] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[11] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"}},
|
||||
["M-64 bomb x 3, HVAR x 6"]={[2] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[3] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[4] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[5] = {["CLSID"]="{AN-M64}"},
|
||||
[6] = {["CLSID"]="{AN-M64}"},
|
||||
[7] = {["CLSID"]="{AN-M64}"},
|
||||
[8] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[9] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[10] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"}},
|
||||
["M-64 bomb x 2, HVAR x 4"]={[3] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[4] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[5] = {["CLSID"]="{AN_M65}"},
|
||||
[7] = {["CLSID"]="{AN_M65}"},
|
||||
[8] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[9] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"}},
|
||||
["M-64 bomb, M-65 bomb x 2"]={[5] = {["CLSID"]="{AN_M65}"},
|
||||
[6] = {["CLSID"]="{AN-M64}"},
|
||||
[7] = {["CLSID"]="{AN_M65}"}},
|
||||
["Tiny Tim x 2, HVAR x 4"]={[3] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[4] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[5] = {["CLSID"]="{Tiny_Tim_Corsair_L}"},
|
||||
[7] = {["CLSID"]="{Tiny_Tim_Corsair_R}"},
|
||||
[8] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[9] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"}},
|
||||
["Bat Bomb"]={[6] = {["CLSID"]="{ASM_N_2}"}},
|
||||
["Tiny Tim x 2"]={[5] = {["CLSID"]="{Tiny_Tim_Corsair_L}"},
|
||||
[7] = {["CLSID"]="{Tiny_Tim_Corsair_R}"}},
|
||||
["Bat Bomb, 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}"},
|
||||
[6] = {["CLSID"]="{ASM_N_2}"},
|
||||
[8] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[9] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[10] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_Corsair}"},
|
||||
[11] = {["CLSID"]="{HVAR_USN_Mk28_Mod4_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,6 +8212,239 @@ 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}"}}},
|
||||
["MiG-29 Fulcrum"]={["2 * R-27T, 4 * R-73"]={[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[2] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[3] = {["CLSID"]="{88DAC840-9F75-4531-8689-B46E64E42E53}"},
|
||||
[5] = {["CLSID"]="{88DAC840-9F75-4531-8689-B46E64E42E53}"},
|
||||
[6] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"}},
|
||||
["2 * R-27R, 4 * R-73"]={[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[2] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[3] = {["CLSID"]="{9B25D316-0434-4954-868F-D51DB1A38DF0}"},
|
||||
[5] = {["CLSID"]="{9B25D316-0434-4954-868F-D51DB1A38DF0}"},
|
||||
[6] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"}},
|
||||
["2 * R-27T, 2 * R-73"]={[2] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[3] = {["CLSID"]="{88DAC840-9F75-4531-8689-B46E64E42E53}"},
|
||||
[5] = {["CLSID"]="{88DAC840-9F75-4531-8689-B46E64E42E53}"},
|
||||
[6] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"}},
|
||||
["2 * R-27R, 2 * R-73"]={[2] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[3] = {["CLSID"]="{9B25D316-0434-4954-868F-D51DB1A38DF0}"},
|
||||
[5] = {["CLSID"]="{9B25D316-0434-4954-868F-D51DB1A38DF0}"},
|
||||
[6] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"}},
|
||||
["6 * R-73"]={[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[2] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[3] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[5] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[6] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"}},
|
||||
["2 * R-27T, 4 * R-60M"]={[1] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[2] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[3] = {["CLSID"]="{88DAC840-9F75-4531-8689-B46E64E42E53}"},
|
||||
[5] = {["CLSID"]="{88DAC840-9F75-4531-8689-B46E64E42E53}"},
|
||||
[6] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[7] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"}},
|
||||
["2 * R-27R, 4 * R-60M"]={[1] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[2] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[3] = {["CLSID"]="{9B25D316-0434-4954-868F-D51DB1A38DF0}"},
|
||||
[5] = {["CLSID"]="{9B25D316-0434-4954-868F-D51DB1A38DF0}"},
|
||||
[6] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[7] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"}},
|
||||
["2 * R-27T, 2 * R-60M"]={[2] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[3] = {["CLSID"]="{88DAC840-9F75-4531-8689-B46E64E42E53}"},
|
||||
[5] = {["CLSID"]="{88DAC840-9F75-4531-8689-B46E64E42E53}"},
|
||||
[6] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"}},
|
||||
["2 * R-27R, 2 * R-60M"]={[2] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[3] = {["CLSID"]="{9B25D316-0434-4954-868F-D51DB1A38DF0}"},
|
||||
[5] = {["CLSID"]="{9B25D316-0434-4954-868F-D51DB1A38DF0}"},
|
||||
[6] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"}},
|
||||
["6 * R-60"]={[1] = {["CLSID"]="{R_60}"},
|
||||
[2] = {["CLSID"]="{R_60}"},
|
||||
[3] = {["CLSID"]="{R_60}"},
|
||||
[5] = {["CLSID"]="{R_60}"},
|
||||
[6] = {["CLSID"]="{R_60}"},
|
||||
[7] = {["CLSID"]="{R_60}"}},
|
||||
["4 * S-24, 2 * R-73"]={[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[2] = {["CLSID"]="{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}"},
|
||||
[3] = {["CLSID"]="{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}"},
|
||||
[5] = {["CLSID"]="{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}"},
|
||||
[6] = {["CLSID"]="{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}"},
|
||||
[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"}},
|
||||
["4 * S-24, 2 * R-60M "]={[1] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[2] = {["CLSID"]="{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}"},
|
||||
[3] = {["CLSID"]="{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}"},
|
||||
[5] = {["CLSID"]="{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}"},
|
||||
[6] = {["CLSID"]="{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}"},
|
||||
[7] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"}},
|
||||
["4 * S-24"]={[2] = {["CLSID"]="{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}"},
|
||||
[3] = {["CLSID"]="{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}"},
|
||||
[5] = {["CLSID"]="{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}"},
|
||||
[6] = {["CLSID"]="{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}"}},
|
||||
["80 * S-8OFP"]={[2] = {["CLSID"]="B-8M1 - 20 S-8OFP2"},
|
||||
[3] = {["CLSID"]="B-8M1 - 20 S-8OFP2"},
|
||||
[5] = {["CLSID"]="B-8M1 - 20 S-8OFP2"},
|
||||
[6] = {["CLSID"]="B-8M1 - 20 S-8OFP2"}},
|
||||
["80 * S-8OFP, 2 * R-60M"]={[1] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[2] = {["CLSID"]="B-8M1 - 20 S-8OFP2"},
|
||||
[3] = {["CLSID"]="B-8M1 - 20 S-8OFP2"},
|
||||
[5] = {["CLSID"]="B-8M1 - 20 S-8OFP2"},
|
||||
[6] = {["CLSID"]="B-8M1 - 20 S-8OFP2"},
|
||||
[7] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"}},
|
||||
["80 * S-8KOM, 2 * R-73"]={[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[2] = {["CLSID"]="{F72F47E5-C83A-4B85-96ED-D3E46671EE9A}"},
|
||||
[3] = {["CLSID"]="{F72F47E5-C83A-4B85-96ED-D3E46671EE9A}"},
|
||||
[5] = {["CLSID"]="{F72F47E5-C83A-4B85-96ED-D3E46671EE9A}"},
|
||||
[6] = {["CLSID"]="{F72F47E5-C83A-4B85-96ED-D3E46671EE9A}"},
|
||||
[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"}},
|
||||
["4 * BetAB-500, 2 * R-60"]={[1] = {["CLSID"]="{R_60}"},
|
||||
[2] = {["CLSID"]="{35B698AC-9FEF-4EC4-AD29-484A0085F62B}"},
|
||||
[3] = {["CLSID"]="{35B698AC-9FEF-4EC4-AD29-484A0085F62B}"},
|
||||
[5] = {["CLSID"]="{35B698AC-9FEF-4EC4-AD29-484A0085F62B}"},
|
||||
[6] = {["CLSID"]="{35B698AC-9FEF-4EC4-AD29-484A0085F62B}"},
|
||||
[7] = {["CLSID"]="{R_60}"}},
|
||||
["4 * BetAB-500, 2 * R-73"]={[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[2] = {["CLSID"]="{35B698AC-9FEF-4EC4-AD29-484A0085F62B}"},
|
||||
[3] = {["CLSID"]="{35B698AC-9FEF-4EC4-AD29-484A0085F62B}"},
|
||||
[5] = {["CLSID"]="{35B698AC-9FEF-4EC4-AD29-484A0085F62B}"},
|
||||
[6] = {["CLSID"]="{35B698AC-9FEF-4EC4-AD29-484A0085F62B}"},
|
||||
[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"}},
|
||||
["4 * BetAB-500"]={[2] = {["CLSID"]="{35B698AC-9FEF-4EC4-AD29-484A0085F62B}"},
|
||||
[3] = {["CLSID"]="{35B698AC-9FEF-4EC4-AD29-484A0085F62B}"},
|
||||
[5] = {["CLSID"]="{35B698AC-9FEF-4EC4-AD29-484A0085F62B}"},
|
||||
[6] = {["CLSID"]="{35B698AC-9FEF-4EC4-AD29-484A0085F62B}"}},
|
||||
["4 * KMGU"]={[2] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"},
|
||||
[3] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"},
|
||||
[5] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"},
|
||||
[6] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"}},
|
||||
["4 * KMGU AO-2.5RT, 2 * R-60M"]={[1] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[2] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"},
|
||||
[3] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"},
|
||||
[5] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"},
|
||||
[6] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"},
|
||||
[7] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"}},
|
||||
["4 * KMGU PTAB-2.5KO, 2 * R-73"]={[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[2] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"},
|
||||
[3] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74881}"},
|
||||
[5] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74881}"},
|
||||
[6] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"},
|
||||
[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"}},
|
||||
["2 * R-27T"]={[3] = {["CLSID"]="{88DAC840-9F75-4531-8689-B46E64E42E53}"},
|
||||
[5] = {["CLSID"]="{88DAC840-9F75-4531-8689-B46E64E42E53}"}},
|
||||
["2 * R-27R"]={[3] = {["CLSID"]="{9B25D316-0434-4954-868F-D51DB1A38DF0}"},
|
||||
[5] = {["CLSID"]="{9B25D316-0434-4954-868F-D51DB1A38DF0}"}},
|
||||
["4 * RBK-250 PTAB-2.5M, 2 * R-73"]={[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[6] = {["CLSID"]="{4203753F-8198-4E85-9924-6F8FF679F9FF}"},
|
||||
[2] = {["CLSID"]="{4203753F-8198-4E85-9924-6F8FF679F9FF}"},
|
||||
[5] = {["CLSID"]="{4203753F-8198-4E85-9924-6F8FF679F9FF}"},
|
||||
[3] = {["CLSID"]="{4203753F-8198-4E85-9924-6F8FF679F9FF}"}},
|
||||
["4 * RBK-250 AO-1SCh, 2 * R-73"]={[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[6] = {["CLSID"]="{RBK_250_275_AO_1SCH}"},
|
||||
[2] = {["CLSID"]="{RBK_250_275_AO_1SCH}"},
|
||||
[5] = {["CLSID"]="{RBK_250_275_AO_1SCH}"},
|
||||
[3] = {["CLSID"]="{RBK_250_275_AO_1SCH}"}},
|
||||
["4 * RBK-500 PTAB-1M, 2 * R-73"]={[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[6] = {["CLSID"]="{7AEC222D-C523-425e-B714-719C0D1EB14D}"},
|
||||
[2] = {["CLSID"]="{7AEC222D-C523-425e-B714-719C0D1EB14D}"},
|
||||
[5] = {["CLSID"]="{7AEC222D-C523-425e-B714-719C0D1EB14D}"},
|
||||
[3] = {["CLSID"]="{7AEC222D-C523-425e-B714-719C0D1EB14D}"}},
|
||||
["4 * RBK-500 PTAB-10, 2 * R-73"]={[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[6] = {["CLSID"]="{D5435F26-F120-4FA3-9867-34ACE562EF1B}"},
|
||||
[2] = {["CLSID"]="{D5435F26-F120-4FA3-9867-34ACE562EF1B}"},
|
||||
[5] = {["CLSID"]="{D5435F26-F120-4FA3-9867-34ACE562EF1B}"},
|
||||
[3] = {["CLSID"]="{D5435F26-F120-4FA3-9867-34ACE562EF1B}"}},
|
||||
["4 * FAB-250M-62, 2 * R-73"]={[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[6] = {["CLSID"]="{FAB_250_M62}"},
|
||||
[2] = {["CLSID"]="{FAB_250_M62}"},
|
||||
[5] = {["CLSID"]="{FAB_250_M62}"},
|
||||
[3] = {["CLSID"]="{FAB_250_M62}"}},
|
||||
["4 * FAB-500M-62, 2 * R-73"]={[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[6] = {["CLSID"]="{37DCC01E-9E02-432F-B61D-10C166CA2798}"},
|
||||
[2] = {["CLSID"]="{37DCC01E-9E02-432F-B61D-10C166CA2798}"},
|
||||
[5] = {["CLSID"]="{37DCC01E-9E02-432F-B61D-10C166CA2798}"},
|
||||
[3] = {["CLSID"]="{37DCC01E-9E02-432F-B61D-10C166CA2798}"}},
|
||||
["80 * S-8OFP, 2 * R-73"]={[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[6] = {["CLSID"]="B-8M1 - 20 S-8OFP2"},
|
||||
[2] = {["CLSID"]="B-8M1 - 20 S-8OFP2"},
|
||||
[5] = {["CLSID"]="B-8M1 - 20 S-8OFP2"},
|
||||
[3] = {["CLSID"]="B-8M1 - 20 S-8OFP2"}},
|
||||
["4 * BetAB-500ShP, 2 * R-73"]={[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[6] = {["CLSID"]="{BD289E34-DF84-4C5E-9220-4B14C346E79D}"},
|
||||
[2] = {["CLSID"]="{BD289E34-DF84-4C5E-9220-4B14C346E79D}"},
|
||||
[5] = {["CLSID"]="{BD289E34-DF84-4C5E-9220-4B14C346E79D}"},
|
||||
[3] = {["CLSID"]="{BD289E34-DF84-4C5E-9220-4B14C346E79D}"}},
|
||||
["80 * S-8TsM, 2 * R-73"]={[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[6] = {["CLSID"]="{3DFB7320-AB0E-11d7-9897-000476191836}"},
|
||||
[2] = {["CLSID"]="{3DFB7320-AB0E-11d7-9897-000476191836}"},
|
||||
[5] = {["CLSID"]="{3DFB7320-AB0E-11d7-9897-000476191836}"},
|
||||
[3] = {["CLSID"]="{3DFB7320-AB0E-11d7-9897-000476191836}"}},
|
||||
["6 * R-60M"]={[7] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[1] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[6] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[2] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[5] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[3] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"}},
|
||||
["2 * R-27ER, 4 * R-73"]={[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[6] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[2] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[5] = {["CLSID"]="{E8069896-8435-4B90-95C0-01A03AE6E400}"},
|
||||
[3] = {["CLSID"]="{E8069896-8435-4B90-95C0-01A03AE6E400}"}},
|
||||
["2 * R-27ET, 4 * R-73"]={[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[6] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[2] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[5] = {["CLSID"]="{B79C379A-9E87-4E50-A1EE-7F7E29C2E87A}"},
|
||||
[3] = {["CLSID"]="{B79C379A-9E87-4E50-A1EE-7F7E29C2E87A}"}},
|
||||
["2 * R-27ER, 4 * R-60M"]={[7] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[1] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[6] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[2] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[5] = {["CLSID"]="{E8069896-8435-4B90-95C0-01A03AE6E400}"},
|
||||
[3] = {["CLSID"]="{E8069896-8435-4B90-95C0-01A03AE6E400}"}},
|
||||
["2 * R-27ER"]={[5] = {["CLSID"]="{E8069896-8435-4B90-95C0-01A03AE6E400}"},
|
||||
[3] = {["CLSID"]="{E8069896-8435-4B90-95C0-01A03AE6E400}"}},
|
||||
["2 * R-27ET"]={[5] = {["CLSID"]="{B79C379A-9E87-4E50-A1EE-7F7E29C2E87A}"},
|
||||
[3] = {["CLSID"]="{B79C379A-9E87-4E50-A1EE-7F7E29C2E87A}"}},
|
||||
["2 * R-27ER, 4 * R-73, Fuel"]={[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[6] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[2] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[5] = {["CLSID"]="{E8069896-8435-4B90-95C0-01A03AE6E400}"},
|
||||
[3] = {["CLSID"]="{E8069896-8435-4B90-95C0-01A03AE6E400}"},
|
||||
[4] = {["CLSID"]="{PTB_1500_MIG29A}"}},
|
||||
["2 * R-27ET, 4 * R-73, fuel"]={[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[6] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[2] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[5] = {["CLSID"]="{B79C379A-9E87-4E50-A1EE-7F7E29C2E87A}"},
|
||||
[3] = {["CLSID"]="{B79C379A-9E87-4E50-A1EE-7F7E29C2E87A}"},
|
||||
[4] = {["CLSID"]="{PTB_1500_MIG29A}"}},
|
||||
["6 * R-73, Fuel"]={[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[6] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[2] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[5] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[3] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[4] = {["CLSID"]="{PTB_1500_MIG29A}"}},
|
||||
["6 * R-60M, Fuel"]={[7] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[1] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[6] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[2] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[5] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[3] = {["CLSID"]="{682A481F-0CB5-4693-A382-D00DD4A156D7}"},
|
||||
[4] = {["CLSID"]="{PTB_1500_MIG29A}"}},
|
||||
["2 * R-73, Fuel"]={[7] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[1] = {["CLSID"]="{FBC29BFE-3D24-4C64-B81D-941239D12249}"},
|
||||
[4] = {["CLSID"]="{PTB_1500_MIG29A}"}}},
|
||||
["Mirage-F1B"]={["Clean"]={[1] = {["CLSID"]="<CLEAN>"},
|
||||
[2] = {["CLSID"]="<CLEAN>"},
|
||||
[3] = {["CLSID"]="<CLEAN>"},
|
||||
@@ -10490,6 +10813,96 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[4] = {["CLSID"]="{MBDA_MistralG}"},
|
||||
[5] = {["CLSID"]="{FAS}"},
|
||||
[6] = {["CLSID"]="{IR_Deflector}"}}},
|
||||
["Mi-28N"]={["2xFAB-250"]={[2] = {["CLSID"]="{3C612111-C7AD-476E-8A8E-2485812F4E5C}"},
|
||||
[3] = {["CLSID"]="{3C612111-C7AD-476E-8A8E-2485812F4E5C}"}},
|
||||
["4xFuel tank"]={[1] = {["CLSID"]="{PTB_450}"},
|
||||
[2] = {["CLSID"]="{PTB_450}"},
|
||||
[3] = {["CLSID"]="{PTB_450}"},
|
||||
[4] = {["CLSID"]="{PTB_450}"}},
|
||||
["80xS-8"]={[1] = {["CLSID"]="{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}"},
|
||||
[2] = {["CLSID"]="{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}"},
|
||||
[3] = {["CLSID"]="{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}"},
|
||||
[4] = {["CLSID"]="{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}"}},
|
||||
["4xKMGU AP"]={[1] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"},
|
||||
[2] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"},
|
||||
[3] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"},
|
||||
[4] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"}},
|
||||
["4xUPK-23"]={[1] = {["CLSID"]="{05544F1A-C39C-466b-BC37-5BD1D52E57BB}"},
|
||||
[2] = {["CLSID"]="{05544F1A-C39C-466b-BC37-5BD1D52E57BB}"},
|
||||
[3] = {["CLSID"]="{05544F1A-C39C-466b-BC37-5BD1D52E57BB}"},
|
||||
[4] = {["CLSID"]="{05544F1A-C39C-466b-BC37-5BD1D52E57BB}"}},
|
||||
["16x9M114, 2xKMGU AT"]={[1] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"},
|
||||
[2] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74881}"},
|
||||
[3] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74881}"},
|
||||
[4] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"}},
|
||||
["4xFAB-500"]={[1] = {["CLSID"]="{37DCC01E-9E02-432F-B61D-10C166CA2798}"},
|
||||
[2] = {["CLSID"]="{37DCC01E-9E02-432F-B61D-10C166CA2798}"},
|
||||
[3] = {["CLSID"]="{37DCC01E-9E02-432F-B61D-10C166CA2798}"},
|
||||
[4] = {["CLSID"]="{37DCC01E-9E02-432F-B61D-10C166CA2798}"}},
|
||||
["16x9M114, 2xFAB-500"]={[1] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"},
|
||||
[2] = {["CLSID"]="{37DCC01E-9E02-432F-B61D-10C166CA2798}"},
|
||||
[3] = {["CLSID"]="{37DCC01E-9E02-432F-B61D-10C166CA2798}"},
|
||||
[4] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"}},
|
||||
["40xS-8"]={[2] = {["CLSID"]="{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}"},
|
||||
[3] = {["CLSID"]="{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}"}},
|
||||
["40xS-8 TsM"]={[2] = {["CLSID"]="B_8V20A_CM"},
|
||||
[3] = {["CLSID"]="B_8V20A_CM"}},
|
||||
["2xKMGU AP"]={[2] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"},
|
||||
[3] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"}},
|
||||
["2xUPK-23"]={[2] = {["CLSID"]="{05544F1A-C39C-466b-BC37-5BD1D52E57BB}"},
|
||||
[3] = {["CLSID"]="{05544F1A-C39C-466b-BC37-5BD1D52E57BB}"}},
|
||||
["16x9M114, 2xUPK-23"]={[1] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"},
|
||||
[2] = {["CLSID"]="{05544F1A-C39C-466b-BC37-5BD1D52E57BB}"},
|
||||
[3] = {["CLSID"]="{05544F1A-C39C-466b-BC37-5BD1D52E57BB}"},
|
||||
[4] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"}},
|
||||
["2xFAB-500"]={[2] = {["CLSID"]="{37DCC01E-9E02-432F-B61D-10C166CA2798}"},
|
||||
[3] = {["CLSID"]="{37DCC01E-9E02-432F-B61D-10C166CA2798}"}},
|
||||
["16x9M114, 40xS-8"]={[1] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"},
|
||||
[2] = {["CLSID"]="{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}"},
|
||||
[3] = {["CLSID"]="{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}"},
|
||||
[4] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"}},
|
||||
["16x9M114"]={[1] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"},
|
||||
[4] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"}},
|
||||
["20xS-13"]={[1] = {["CLSID"]="{FC56DF80-9B09-44C5-8976-DCFAFF219062}"},
|
||||
[2] = {["CLSID"]="{FC56DF80-9B09-44C5-8976-DCFAFF219062}"},
|
||||
[3] = {["CLSID"]="{FC56DF80-9B09-44C5-8976-DCFAFF219062}"},
|
||||
[4] = {["CLSID"]="{FC56DF80-9B09-44C5-8976-DCFAFF219062}"}},
|
||||
["16x9M114, 2xKMGU AP"]={[1] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"},
|
||||
[2] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"},
|
||||
[3] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"},
|
||||
[4] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"}},
|
||||
["4xFAB-250"]={[1] = {["CLSID"]="{3C612111-C7AD-476E-8A8E-2485812F4E5C}"},
|
||||
[2] = {["CLSID"]="{3C612111-C7AD-476E-8A8E-2485812F4E5C}"},
|
||||
[3] = {["CLSID"]="{3C612111-C7AD-476E-8A8E-2485812F4E5C}"},
|
||||
[4] = {["CLSID"]="{3C612111-C7AD-476E-8A8E-2485812F4E5C}"}},
|
||||
["4xKMGU AT"]={[1] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74881}"},
|
||||
[2] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74881}"},
|
||||
[3] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74881}"},
|
||||
[4] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74881}"}},
|
||||
["16x9M114, 40xS-8 TsM"]={[1] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"},
|
||||
[2] = {["CLSID"]="B_8V20A_CM"},
|
||||
[3] = {["CLSID"]="B_8V20A_CM"},
|
||||
[4] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"}},
|
||||
["80xS-8 TsM"]={[1] = {["CLSID"]="B_8V20A_CM"},
|
||||
[2] = {["CLSID"]="B_8V20A_CM"},
|
||||
[3] = {["CLSID"]="B_8V20A_CM"},
|
||||
[4] = {["CLSID"]="B_8V20A_CM"}},
|
||||
["2xKMGU AT"]={[2] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74881}"},
|
||||
[3] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74881}"}},
|
||||
["9x9M114"]={[1] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"}},
|
||||
["2xFuel tank"]={[2] = {["CLSID"]="{PTB_450}"},
|
||||
[3] = {["CLSID"]="{PTB_450}"}},
|
||||
["10xS-13"]={[2] = {["CLSID"]="{FC56DF80-9B09-44C5-8976-DCFAFF219062}"},
|
||||
[3] = {["CLSID"]="{FC56DF80-9B09-44C5-8976-DCFAFF219062}"}},
|
||||
["2xFAB-250, 16x9M114"]={[1] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"},
|
||||
[2] = {["CLSID"]="{3C612111-C7AD-476E-8A8E-2485812F4E5C}"},
|
||||
[3] = {["CLSID"]="{3C612111-C7AD-476E-8A8E-2485812F4E5C}"},
|
||||
[4] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"}},
|
||||
["16x9M114, 10xS-13"]={[1] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"},
|
||||
[2] = {["CLSID"]="{FC56DF80-9B09-44C5-8976-DCFAFF219062}"},
|
||||
[3] = {["CLSID"]="{FC56DF80-9B09-44C5-8976-DCFAFF219062}"},
|
||||
[4] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"}}},
|
||||
["Tu-95MS"]={["Kh-65*6"]={[1] = {["CLSID"]="{0290F5DE-014A-4BB1-9843-D717749B1DED}"}}},
|
||||
["B-1B"]={["Mk-82*84"]={[1] = {["CLSID"]="MK_82*28"},
|
||||
[2] = {["CLSID"]="MK_82*28"},
|
||||
[3] = {["CLSID"]="MK_82*28"}},
|
||||
@@ -12278,95 +12691,6 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[5] = {["CLSID"]="{B919B0F4-7C25-455E-9A02-CEA51DB895E3}"},
|
||||
[6] = {["CLSID"]="{B919B0F4-7C25-455E-9A02-CEA51DB895E3}"}}},
|
||||
["Mi-26"]={},
|
||||
["Mi-28N"]={["2xFAB-250"]={[2] = {["CLSID"]="{3C612111-C7AD-476E-8A8E-2485812F4E5C}"},
|
||||
[3] = {["CLSID"]="{3C612111-C7AD-476E-8A8E-2485812F4E5C}"}},
|
||||
["4xFuel tank"]={[1] = {["CLSID"]="{PTB_450}"},
|
||||
[2] = {["CLSID"]="{PTB_450}"},
|
||||
[3] = {["CLSID"]="{PTB_450}"},
|
||||
[4] = {["CLSID"]="{PTB_450}"}},
|
||||
["80xS-8"]={[1] = {["CLSID"]="{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}"},
|
||||
[2] = {["CLSID"]="{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}"},
|
||||
[3] = {["CLSID"]="{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}"},
|
||||
[4] = {["CLSID"]="{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}"}},
|
||||
["4xKMGU AP"]={[1] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"},
|
||||
[2] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"},
|
||||
[3] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"},
|
||||
[4] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"}},
|
||||
["4xUPK-23"]={[1] = {["CLSID"]="{05544F1A-C39C-466b-BC37-5BD1D52E57BB}"},
|
||||
[2] = {["CLSID"]="{05544F1A-C39C-466b-BC37-5BD1D52E57BB}"},
|
||||
[3] = {["CLSID"]="{05544F1A-C39C-466b-BC37-5BD1D52E57BB}"},
|
||||
[4] = {["CLSID"]="{05544F1A-C39C-466b-BC37-5BD1D52E57BB}"}},
|
||||
["16x9M114, 2xKMGU AT"]={[1] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"},
|
||||
[2] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74881}"},
|
||||
[3] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74881}"},
|
||||
[4] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"}},
|
||||
["4xFAB-500"]={[1] = {["CLSID"]="{37DCC01E-9E02-432F-B61D-10C166CA2798}"},
|
||||
[2] = {["CLSID"]="{37DCC01E-9E02-432F-B61D-10C166CA2798}"},
|
||||
[3] = {["CLSID"]="{37DCC01E-9E02-432F-B61D-10C166CA2798}"},
|
||||
[4] = {["CLSID"]="{37DCC01E-9E02-432F-B61D-10C166CA2798}"}},
|
||||
["16x9M114, 2xFAB-500"]={[1] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"},
|
||||
[2] = {["CLSID"]="{37DCC01E-9E02-432F-B61D-10C166CA2798}"},
|
||||
[3] = {["CLSID"]="{37DCC01E-9E02-432F-B61D-10C166CA2798}"},
|
||||
[4] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"}},
|
||||
["40xS-8"]={[2] = {["CLSID"]="{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}"},
|
||||
[3] = {["CLSID"]="{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}"}},
|
||||
["40xS-8 TsM"]={[2] = {["CLSID"]="B_8V20A_CM"},
|
||||
[3] = {["CLSID"]="B_8V20A_CM"}},
|
||||
["2xKMGU AP"]={[2] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"},
|
||||
[3] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"}},
|
||||
["2xUPK-23"]={[2] = {["CLSID"]="{05544F1A-C39C-466b-BC37-5BD1D52E57BB}"},
|
||||
[3] = {["CLSID"]="{05544F1A-C39C-466b-BC37-5BD1D52E57BB}"}},
|
||||
["16x9M114, 2xUPK-23"]={[1] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"},
|
||||
[2] = {["CLSID"]="{05544F1A-C39C-466b-BC37-5BD1D52E57BB}"},
|
||||
[3] = {["CLSID"]="{05544F1A-C39C-466b-BC37-5BD1D52E57BB}"},
|
||||
[4] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"}},
|
||||
["2xFAB-500"]={[2] = {["CLSID"]="{37DCC01E-9E02-432F-B61D-10C166CA2798}"},
|
||||
[3] = {["CLSID"]="{37DCC01E-9E02-432F-B61D-10C166CA2798}"}},
|
||||
["16x9M114, 40xS-8"]={[1] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"},
|
||||
[2] = {["CLSID"]="{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}"},
|
||||
[3] = {["CLSID"]="{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}"},
|
||||
[4] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"}},
|
||||
["16x9M114"]={[1] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"},
|
||||
[4] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"}},
|
||||
["20xS-13"]={[1] = {["CLSID"]="{FC56DF80-9B09-44C5-8976-DCFAFF219062}"},
|
||||
[2] = {["CLSID"]="{FC56DF80-9B09-44C5-8976-DCFAFF219062}"},
|
||||
[3] = {["CLSID"]="{FC56DF80-9B09-44C5-8976-DCFAFF219062}"},
|
||||
[4] = {["CLSID"]="{FC56DF80-9B09-44C5-8976-DCFAFF219062}"}},
|
||||
["16x9M114, 2xKMGU AP"]={[1] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"},
|
||||
[2] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"},
|
||||
[3] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74884}"},
|
||||
[4] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"}},
|
||||
["4xFAB-250"]={[1] = {["CLSID"]="{3C612111-C7AD-476E-8A8E-2485812F4E5C}"},
|
||||
[2] = {["CLSID"]="{3C612111-C7AD-476E-8A8E-2485812F4E5C}"},
|
||||
[3] = {["CLSID"]="{3C612111-C7AD-476E-8A8E-2485812F4E5C}"},
|
||||
[4] = {["CLSID"]="{3C612111-C7AD-476E-8A8E-2485812F4E5C}"}},
|
||||
["4xKMGU AT"]={[1] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74881}"},
|
||||
[2] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74881}"},
|
||||
[3] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74881}"},
|
||||
[4] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74881}"}},
|
||||
["16x9M114, 40xS-8 TsM"]={[1] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"},
|
||||
[2] = {["CLSID"]="B_8V20A_CM"},
|
||||
[3] = {["CLSID"]="B_8V20A_CM"},
|
||||
[4] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"}},
|
||||
["80xS-8 TsM"]={[1] = {["CLSID"]="B_8V20A_CM"},
|
||||
[2] = {["CLSID"]="B_8V20A_CM"},
|
||||
[3] = {["CLSID"]="B_8V20A_CM"},
|
||||
[4] = {["CLSID"]="B_8V20A_CM"}},
|
||||
["2xKMGU AT"]={[2] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74881}"},
|
||||
[3] = {["CLSID"]="{96A7F676-F956-404A-AD04-F33FB2C74881}"}},
|
||||
["9x9M114"]={[1] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"}},
|
||||
["2xFuel tank"]={[2] = {["CLSID"]="{PTB_450}"},
|
||||
[3] = {["CLSID"]="{PTB_450}"}},
|
||||
["10xS-13"]={[2] = {["CLSID"]="{FC56DF80-9B09-44C5-8976-DCFAFF219062}"},
|
||||
[3] = {["CLSID"]="{FC56DF80-9B09-44C5-8976-DCFAFF219062}"}},
|
||||
["2xFAB-250, 16x9M114"]={[1] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"},
|
||||
[2] = {["CLSID"]="{3C612111-C7AD-476E-8A8E-2485812F4E5C}"},
|
||||
[3] = {["CLSID"]="{3C612111-C7AD-476E-8A8E-2485812F4E5C}"},
|
||||
[4] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"}},
|
||||
["16x9M114, 10xS-13"]={[1] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"},
|
||||
[2] = {["CLSID"]="{FC56DF80-9B09-44C5-8976-DCFAFF219062}"},
|
||||
[3] = {["CLSID"]="{FC56DF80-9B09-44C5-8976-DCFAFF219062}"},
|
||||
[4] = {["CLSID"]="{57232979-8B0F-4db7-8D9A-55197E06B0F5}"}}},
|
||||
["Mi-8MT"]={["4 x B8"]={[5] = {["CLSID"]="{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}"},
|
||||
[4] = {["CLSID"]="{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}"},
|
||||
[3] = {["CLSID"]="{6A4B9E69-64FE-439a-9163-3A87FB6A4D81}"},
|
||||
@@ -15390,7 +15714,6 @@ Olympus.unitPayloads = {["A-10A"]={["MK-84*2 , LAU-68*2 , AGM-65K*2"]={[1] = {["
|
||||
[2] = {["CLSID"]="{E1AAE713-5FC3-4CAA-9FF5-3FDCFB899E33}"},
|
||||
[1] = {["CLSID"]="{E1AAE713-5FC3-4CAA-9FF5-3FDCFB899E33}"}},
|
||||
["FAB-250*33"]={[3] = {["CLSID"]="{BDAD04AA-4D4A-4E51-B958-180A89F963CF}"}}},
|
||||
["Tu-95MS"]={["Kh-65*6"]={[1] = {["CLSID"]="{0290F5DE-014A-4BB1-9843-D717749B1DED}"}}},
|
||||
["UH-1H"]={["M134 Minigun*2, XM158*2"]={[1] = {["CLSID"]="M134_L"},
|
||||
[2] = {["CLSID"]="XM158_MK5"},
|
||||
[5] = {["CLSID"]="XM158_MK5"},
|
||||
|
||||
1
scripts/python/unknown_loadout_items.txt
Normal file
1
scripts/python/unknown_loadout_items.txt
Normal file
@@ -0,0 +1 @@
|
||||
AB 250-2 - 144 x SD-2, 250kg CBU with HE submunitions
|
||||
Reference in New Issue
Block a user