fix: Fixed multiple errors in scenic AAA modes

This commit is contained in:
Davide Passoni 2025-03-14 16:45:46 +01:00
parent 5acc0e8ac5
commit f0826bbdba
17 changed files with 437 additions and 160 deletions

View File

@ -62,6 +62,8 @@ public:
bool hasFreshData(unsigned long long time);
bool checkFreshness(unsigned char datumIndex, unsigned long long time);
unsigned int computeTotalAmmo();
/********** Setters **********/
virtual void setCategory(string newValue) { updateValue(category, newValue, DataIndex::category); }
virtual void setAlive(bool newValue) { updateValue(alive, newValue, DataIndex::alive); }
@ -240,26 +242,29 @@ protected:
unsigned char shotsIntensity = 2;
unsigned char health = 100;
double timeToNextTasking = 0;
double barrelHeight = 1.0; /* m */
double muzzleVelocity = 860; /* m/s */
double aimTime = 10; /* s */
unsigned int shotsToFire = 10;
double shotsBaseInterval = 15; /* s */
double shotsBaseScatter = 2; /* degs */
double engagementRange = 10000; /* m */
double targetingRange = 0; /* m */
double aimMethodRange = 0; /* m */
double acquisitionRange = 0; /* m */
double barrelHeight = 0;
double muzzleVelocity = 0;
double aimTime = 0;
unsigned int shotsToFire = 0;
double shotsBaseInterval = 0;
double shotsBaseScatter = 0;
double engagementRange = 0;
double targetingRange = 0;
double aimMethodRange = 0;
double acquisitionRange = 0;
/********** Other **********/
unsigned int taskCheckCounter = 0;
unsigned int internalCounter = 0;
Unit* missOnPurposeTarget = nullptr;
bool hasTaskAssigned = false;
double initialFuel = 0;
map<unsigned char, unsigned long long> updateTimeMap;
unsigned long long lastLoopTime = 0;
bool enableTaskFailedCheck = false;
unsigned long nextTaskingMilliseconds = 0;
unsigned int totalShellsFired = 0;
unsigned int shellsFiredAtTasking = 0;
unsigned int oldAmmo = 0;
/********** Private methods **********/
virtual void AIloop() = 0;

View File

@ -113,4 +113,10 @@ class Bomb : public Weapon
{
public:
Bomb(json::value json, unsigned int ID);
};
class Shell : public Weapon
{
public:
Shell(json::value json, unsigned int ID);
};

View File

@ -168,6 +168,18 @@ void GroundUnit::setState(unsigned char newState)
void GroundUnit::AIloop()
{
srand(static_cast<unsigned int>(time(NULL)) + ID);
unsigned long timeNow = std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1);
double currentAmmo = computeTotalAmmo();
/* Out of ammo */
if (currentAmmo <= shotsToFire && state != State::IDLE) {
setState(State::IDLE);
}
/* Account for unit reloading */
if (currentAmmo < oldAmmo)
totalShellsFired += oldAmmo - currentAmmo;
oldAmmo = currentAmmo;
switch (state) {
case State::IDLE: {
@ -241,7 +253,11 @@ void GroundUnit::AIloop()
case State::SIMULATE_FIRE_FIGHT: {
string taskString = "";
if (internalCounter == 0 && targetPosition != Coords(NULL) && scheduler->getLoad() < 30) {
if (
(totalShellsFired - shellsFiredAtTasking >= shotsToFire || timeNow >= nextTaskingMilliseconds) &&
targetPosition != Coords(NULL) &&
scheduler->getLoad() < 30
) {
/* Get the distance and bearing to the target */
Coords scatteredTargetPosition = targetPosition;
double distance;
@ -249,9 +265,12 @@ void GroundUnit::AIloop()
double bearing2;
Geodesic::WGS84().Inverse(getPosition().lat, getPosition().lng, scatteredTargetPosition.lat, scatteredTargetPosition.lng, distance, bearing1, bearing2);
/* Apply a scatter to the aim */
bearing1 += RANDOM_MINUS_ONE_TO_ONE * (ShotsScatter::LOW - shotsScatter + 1) * 10;
/* Compute the scattered position applying a random scatter to the shot */
double scatterDistance = distance * tan(10 /* degs */ * (ShotsScatter::LOW - shotsScatter) / 57.29577 + 2 / 57.29577 /* degs */) * RANDOM_MINUS_ONE_TO_ONE;
Geodesic::WGS84().Direct(scatteredTargetPosition.lat, scatteredTargetPosition.lng, bearing1 + 90, scatterDistance, scatteredTargetPosition.lat, scatteredTargetPosition.lng);
Geodesic::WGS84().Direct(scatteredTargetPosition.lat, scatteredTargetPosition.lng, bearing1, scatterDistance, scatteredTargetPosition.lat, scatteredTargetPosition.lng);
/* Recover the data from the database */
bool indirectFire = false;
@ -267,9 +286,10 @@ void GroundUnit::AIloop()
log(unitName + "(" + name + ")" + " simulating fire fight with indirect fire");
std::ostringstream taskSS;
taskSS.precision(10);
taskSS << "{id = 'FireAtPoint', lat = " << scatteredTargetPosition.lat << ", lng = " << scatteredTargetPosition.lng << ", radius = 100}";
taskSS << "{id = 'FireAtPoint', lat = " << scatteredTargetPosition.lat << ", lng = " << scatteredTargetPosition.lng << ", radius = 0.01}";
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
scheduler->appendCommand(command);
shellsFiredAtTasking = totalShellsFired;
setHasTask(true);
}
/* Otherwise use the aim method */
@ -281,18 +301,17 @@ void GroundUnit::AIloop()
}
/* Wait an amout of time depending on the shots intensity */
internalCounter = static_cast<unsigned int>(((ShotsIntensity::HIGH - shotsIntensity) * shotsBaseInterval + aimTime) / FRAMERATE_TIME_INTERVAL);
nextTaskingMilliseconds = timeNow + static_cast<unsigned long>(2 * aimTime * 1000);
}
if (targetPosition == Coords(NULL))
setState(State::IDLE);
/* Fallback if something went wrong */
if (internalCounter == 0)
internalCounter = static_cast<unsigned int>(3 / FRAMERATE_TIME_INTERVAL);
internalCounter--;
if (timeNow >= nextTaskingMilliseconds)
nextTaskingMilliseconds = timeNow + static_cast<unsigned long>(3 * 1000);
setTimeToNextTasking(internalCounter * FRAMERATE_TIME_INTERVAL);
setTimeToNextTasking(((nextTaskingMilliseconds - timeNow) / 1000.0));
if (taskString.length() > 0)
setTask(taskString);
@ -303,7 +322,8 @@ void GroundUnit::AIloop()
string taskString = "";
/* Only perform scenic functions when the scheduler is "free" */
if (((!getHasTask() && scheduler->getLoad() < 30) || internalCounter == 0)) {
if ((totalShellsFired - shellsFiredAtTasking >= shotsToFire || timeNow >= nextTaskingMilliseconds) &&
scheduler->getLoad() < 30) {
double distance = 0;
unsigned char unitCoalition = coalition == 0 ? getOperateAs() : coalition;
unsigned char targetCoalition = unitCoalition == 2 ? 1 : 2;
@ -341,17 +361,18 @@ void GroundUnit::AIloop()
taskString += "Scenic AAA. Bearing: " + to_string((int)round(randomBearing)) + "deg";
}
taskString += ". Aim point elevation " + to_string((int) round(barrelElevation)) + "m AGL";
taskString += ". Aim point elevation " + to_string((int) round(barrelElevation - position.alt)) + "m AGL";
std::ostringstream taskSS;
taskSS.precision(10);
taskSS << "{id = 'FireAtPoint', lat = " << lat << ", lng = " << lng << ", alt = " << barrelElevation << ", radius = 0.001, expendQty = " << shotsToFire << " }";
taskSS << "{id = 'FireAtPoint', lat = " << lat << ", lng = " << lng << ", alt = " << barrelElevation << ", radius = 0.001 }";
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
scheduler->appendCommand(command);
shellsFiredAtTasking = totalShellsFired;
setHasTask(true);
/* Wait an amout of time depending on the shots intensity */
internalCounter = static_cast<unsigned int>(((ShotsIntensity::HIGH - shotsIntensity) * shotsBaseInterval + aimTime) / FRAMERATE_TIME_INTERVAL);
nextTaskingMilliseconds = timeNow + static_cast<unsigned long>(2 * aimTime * 1000);
}
else {
if (target == nullptr)
@ -361,11 +382,10 @@ void GroundUnit::AIloop()
}
}
if (internalCounter == 0)
internalCounter = static_cast<unsigned int>(3 / FRAMERATE_TIME_INTERVAL);
internalCounter--;
if (timeNow >= nextTaskingMilliseconds)
nextTaskingMilliseconds = timeNow + static_cast<unsigned long>(3 * 1000);
setTimeToNextTasking(internalCounter * FRAMERATE_TIME_INTERVAL);
setTimeToNextTasking((nextTaskingMilliseconds - timeNow) / 1000.0);
if (taskString.length() > 0)
setTask(taskString);
@ -385,7 +405,8 @@ void GroundUnit::AIloop()
if (canAAA) {
/* Only perform scenic functions when the scheduler is "free" */
/* Only run this when the internal counter reaches 0 to avoid excessive computations when no nearby target */
if (scheduler->getLoad() < 30 && internalCounter == 0) {
if ((totalShellsFired - shellsFiredAtTasking >= shotsToFire || timeNow >= nextTaskingMilliseconds) &&
scheduler->getLoad() < 30) {
double distance = 0;
unsigned char unitCoalition = coalition == 0 ? getOperateAs() : coalition;
unsigned char targetCoalition = unitCoalition == 2 ? 1 : 2;
@ -422,9 +443,10 @@ void GroundUnit::AIloop()
taskSS << "{id = 'AttackUnit', unitID = " << target->getID() << " }";
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
scheduler->appendCommand(command);
shellsFiredAtTasking = totalShellsFired;
setHasTask(true);
internalCounter = static_cast<unsigned int>((correctedAimTime + (ShotsIntensity::HIGH - shotsIntensity) * shotsBaseInterval + 2) / FRAMERATE_TIME_INTERVAL);
nextTaskingMilliseconds = timeNow + static_cast<unsigned long>(2 * aimTime * 1000);
}
/* Else, do miss on purpose */
else {
@ -443,14 +465,15 @@ void GroundUnit::AIloop()
/* If the unit is closer than the engagement range, use the fire at point method */
std::ostringstream taskSS;
taskSS.precision(10);
taskSS << "{id = 'FireAtPoint', lat = " << aimLat << ", lng = " << aimLng << ", alt = " << aimAlt << ", radius = 0.001, expendQty = " << shotsToFire << " }";
taskSS << "{id = 'FireAtPoint', lat = " << aimLat << ", lng = " << aimLng << ", alt = " << aimAlt << ", radius = 0.001 }";
taskString += ". Aiming altitude " + to_string((int)round((aimAlt - position.alt) / 0.3048)) + "ft AGL";
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
scheduler->appendCommand(command);
shellsFiredAtTasking = totalShellsFired;
setHasTask(true);
setTargetPosition(Coords(aimLat, aimLng, target->getPosition().alt));
internalCounter = static_cast<unsigned int>((correctedAimTime + (ShotsIntensity::HIGH - shotsIntensity) * shotsBaseInterval + 2) / FRAMERATE_TIME_INTERVAL);
nextTaskingMilliseconds = timeNow + static_cast<unsigned long>(2 * aimTime * 1000);
}
else if (distance < aimMethodRange) {
taskString += ". Range is less than aim method range (" + to_string((int)round(aimMethodRange / 0.3048)) + "ft), using AIM method.";
@ -460,7 +483,7 @@ void GroundUnit::AIloop()
taskString += aimMethodTask;
setTargetPosition(Coords(aimLat, aimLng, target->getPosition().alt));
internalCounter = static_cast<unsigned int>((correctedAimTime + (ShotsIntensity::HIGH - shotsIntensity) * shotsBaseInterval + 2) / FRAMERATE_TIME_INTERVAL);
nextTaskingMilliseconds = timeNow + static_cast<unsigned long>(2 * aimTime * 1000);
}
else {
taskString += ". Target is not in range of weapon, waking up unit to get ready for tasking.";
@ -471,11 +494,12 @@ void GroundUnit::AIloop()
taskSS << "{id = 'FireAtPoint', lat = " << 0 << ", lng = " << 0 << ", alt = " << 0 << ", radius = 0.001, expendQty = " << 0 << " }";
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
scheduler->appendCommand(command);
shellsFiredAtTasking = totalShellsFired;
setHasTask(true);
setTargetPosition(Coords(NULL));
/* Don't wait too long before checking again */
internalCounter = static_cast<unsigned int>(5 / FRAMERATE_TIME_INTERVAL);
nextTaskingMilliseconds = timeNow + static_cast<unsigned long>(5 * 1000);
}
}
missOnPurposeTarget = target;
@ -488,24 +512,24 @@ void GroundUnit::AIloop()
}
/* If no valid target was detected */
if (internalCounter == 0) {
if (timeNow >= nextTaskingMilliseconds) {
double alertnessTimeConstant = 10; /* s */
if (database.has_object_field(to_wstring(name))) {
json::value databaseEntry = database[to_wstring(name)];
if (databaseEntry.has_number_field(L"alertnessTimeConstant"))
alertnessTimeConstant = databaseEntry[L"alertnessTimeConstant"].as_number().to_double();
}
internalCounter = static_cast<unsigned int>((5 + RANDOM_ZERO_TO_ONE * alertnessTimeConstant) / FRAMERATE_TIME_INTERVAL);
nextTaskingMilliseconds = timeNow + static_cast<unsigned long>((5 + RANDOM_ZERO_TO_ONE * alertnessTimeConstant) * 1000L);
missOnPurposeTarget = nullptr;
setTargetPosition(Coords(NULL));
}
internalCounter--;
}
else {
setState(State::IDLE);
}
setTimeToNextTasking(internalCounter * FRAMERATE_TIME_INTERVAL);
setTimeToNextTasking((nextTaskingMilliseconds - timeNow) / 1000.0);
if (taskString.length() > 0)
setTask(taskString);
@ -528,20 +552,6 @@ string GroundUnit::aimAtPoint(Coords aimTarget) {
/* Aim point distance */
double r = 15; /* m */
/* Default gun values */
double barrelHeight = 1.0; /* m */
double muzzleVelocity = 860; /* m/s */
double shotsBaseScatter = 5; /* degs */
if (database.has_object_field(to_wstring(name))) {
json::value databaseEntry = database[to_wstring(name)];
if (databaseEntry.has_number_field(L"barrelHeight") && databaseEntry.has_number_field(L"muzzleVelocity")) {
barrelHeight = databaseEntry[L"barrelHeight"].as_number().to_double();
muzzleVelocity = databaseEntry[L"muzzleVelocity"].as_number().to_double();
}
if (databaseEntry.has_number_field(L"shotsBaseScatter"))
shotsBaseScatter = databaseEntry[L"shotsBaseScatter"].as_number().to_double();
}
/* Compute the elevation angle of the gun*/
double deltaHeight = (aimTarget.alt - (position.alt + barrelHeight));
double alpha = 9.81 / 2 * dist * dist / (muzzleVelocity * muzzleVelocity);
@ -564,6 +574,7 @@ string GroundUnit::aimAtPoint(Coords aimTarget) {
taskSS << "{id = 'FireAtPoint', lat = " << lat << ", lng = " << lng << ", alt = " << position.alt + barrelElevation + barrelHeight << ", radius = 0.001}";
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
scheduler->appendCommand(command);
shellsFiredAtTasking = totalShellsFired;
setHasTask(true);
}
else {

View File

@ -825,3 +825,11 @@ void Unit::setHasTaskAssigned(bool newHasTaskAssigned) {
void Unit::triggerUpdate(unsigned char datumIndex) {
updateTimeMap[datumIndex] = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
}
unsigned int Unit::computeTotalAmmo()
{
unsigned int totalShells = 0;
for (auto const& ammoItem : ammo)
totalShells += ammoItem.quantity;
return totalShells;
}

View File

@ -113,4 +113,11 @@ Bomb::Bomb(json::value json, unsigned int ID) : Weapon(json, ID)
{
log("New Bomb created with ID: " + to_string(ID));
setCategory("Bomb");
};
/* Shell */
Shell::Shell(json::value json, unsigned int ID) : Weapon(json, ID)
{
log("New Shell created with ID: " + to_string(ID));
setCategory("Shell");
};

View File

@ -41,6 +41,8 @@ void WeaponsManager::update(json::value& json, double dt)
weapons[ID] = dynamic_cast<Weapon*>(new Missile(p.second, ID));
else if (category.compare("Bomb") == 0)
weapons[ID] = dynamic_cast<Weapon*>(new Bomb(p.second, ID));
else if (category.compare("Shell") == 0)
weapons[ID] = dynamic_cast<Weapon*>(new Shell(p.second, ID));
/* Initialize the weapon if creation was successfull */
if (weapons.count(ID) != 0) {

View File

@ -129,7 +129,7 @@
"canRearm": false,
"muzzleVelocity": 1000,
"barrelHeight": 2,
"aimTime": 5,
"aimTime": 7,
"shotsToFire": 10,
"cost": null,
"tags": "Optical, Radar, CA",
@ -587,7 +587,7 @@
"abilities": "Combined arms, Amphibious, Transport, AA",
"canTargetPoint": true,
"canRearm": false,
"aimTime": 5,
"aimTime": 6,
"shotsToFire": 5,
"tags": "CA",
"markerFile": "groundunit-apc",
@ -819,7 +819,7 @@
"canRearm": false,
"barrelHeight": 2.2,
"muzzleVelocity": 900,
"aimTime": 5,
"aimTime": 6,
"shotsToFire": 5,
"tags": "CA",
"markerFile": "groundunit-apc",
@ -955,7 +955,7 @@
"acquisitionRange": 3000,
"engagementRange": 1000,
"description": "Armoured car, MRAP. Wheeled. Amphibious. 12.7 mm machine gun.",
"abilities": "Combined arms, Amphibious, AA",
"abilities": "Combined arms, Amphibious",
"canTargetPoint": true,
"canRearm": false,
"barrelHeight": 2,
@ -969,7 +969,7 @@
"shotsBaseInterval": 6,
"shotsBaseScatter": 10,
"alertnessTimeConstant": 4,
"canAAA": true
"canAAA": false
},
"Dog Ear radar": {
"name": "Dog Ear radar",
@ -1188,7 +1188,7 @@
"abilities": "Combined arms, AA",
"canTargetPoint": true,
"canRearm": false,
"aimTime": 8,
"aimTime": 7,
"shotsToFire": 5,
"cost": null,
"tags": "Radar, CA",
@ -2280,7 +2280,7 @@
"abilities": "AA, Embark",
"canTargetPoint": true,
"canRearm": false,
"aimTime": 5,
"aimTime": 3,
"shotsToFire": 5,
"tags": "Russian type 1",
"markerFile": "groundunit-infantry",
@ -2789,7 +2789,7 @@
"shotsBaseInterval": 6,
"shotsToFire": 5,
"tags": "CA",
"aimTime": 5,
"aimTime": 7,
"aimMethodRange": 3000,
"shotsBaseScatter": 5,
"alertnessTimeConstant": 4,
@ -2931,7 +2931,7 @@
"aimMethodRange": 300,
"shotsBaseScatter": 5,
"alertnessTimeConstant": 3,
"canAAA": true
"canAAA": false
},
"M-109": {
"name": "M-109",
@ -3096,7 +3096,7 @@
"canRearm": false,
"barrelHeight": 2.8,
"muzzleVelocity": 950,
"aimTime": 5,
"aimTime": 2.5,
"shotsToFire": 5,
"tags": "CA",
"markerFile": "groundunit-apc",
@ -3265,7 +3265,7 @@
"markerFile": "groundunit-tactical",
"targetingRange": 100,
"aimMethodRange": 2500,
"aimTime": 5,
"aimTime": 3,
"shotsToFire": 5,
"shotsBaseInterval": 5,
"shotsBaseScatter": 5,
@ -3368,7 +3368,7 @@
"canRearm": false,
"barrelHeight": 3,
"muzzleVelocity": 900,
"aimTime": 5,
"aimTime": 2.7,
"shotsToFire": 5,
"tags": "CA",
"markerFile": "groundunit-apc",
@ -3800,7 +3800,7 @@
"canRearm": false,
"barrelHeight": 2.05,
"muzzleVelocity": 800,
"aimTime": 5,
"aimTime": 3,
"shotsToFire": 5,
"tags": "CA",
"markerFile": "groundunit-apc",
@ -3845,13 +3845,13 @@
},
"acquisitionRange": 3000,
"engagementRange": 1000,
"description": "Marder Infantry FIghting Vehicle. Tracked. Amphibious. 20 mm gun and 7.62 mm machine gun.",
"description": "Marder Infantry Fighting Vehicle. Tracked. Amphibious. 20 mm gun and 7.62 mm machine gun.",
"abilities": "Combined arms, Transport, Amphibious",
"canTargetPoint": true,
"canRearm": false,
"barrelHeight": 2.82,
"muzzleVelocity": 900,
"aimTime": 5,
"aimTime": 9,
"shotsToFire": 5,
"tags": "CA",
"markerFile": "groundunit-apc",
@ -3917,7 +3917,7 @@
"acquisitionRange": 2500,
"engagementRange": 300,
"description": "Russian paratrooper carrying AKS-74.",
"abilities": "AA, Embark",
"abilities": "Embark",
"canTargetPoint": true,
"canRearm": false,
"barrelHeight": 0.9,
@ -3926,7 +3926,7 @@
"shotsToFire": 5,
"tags": "Russian Para",
"markerFile": "groundunit-infantry",
"canAAA": true,
"canAAA": false,
"targetingRange": 100,
"aimMethodRange": 2000,
"shotsBaseInterval": 5,
@ -3955,7 +3955,7 @@
"shotsToFire": 1,
"tags": "Russian Para",
"markerFile": "groundunit-infantry",
"canAAA": true,
"canAAA": false,
"targetingRange": 50,
"aimMethodRange": 750,
"shotsBaseInterval": 5,
@ -5613,7 +5613,7 @@
"abilities": "AA, Embark",
"canTargetPoint": true,
"canRearm": false,
"aimTime": 5,
"aimTime": 2.5,
"shotsToFire": 5,
"tags": "Russian type 4",
"markerFile": "groundunit-infantry",
@ -5679,7 +5679,7 @@
"acquisitionRange": 2500,
"engagementRange": 300,
"description": "Solider carrying M249.",
"abilities": "AA, Embark",
"abilities": "Embark",
"canTargetPoint": true,
"canRearm": false,
"muzzleVelocity": 915,
@ -5688,7 +5688,7 @@
"barrelHeight": 0.25,
"tags": "US",
"markerFile": "groundunit-infantry",
"canAAA": true,
"canAAA": false,
"targetingRange": 100,
"aimMethodRange": 2000,
"shotsBaseInterval": 5,
@ -5758,7 +5758,7 @@
"acquisitionRange": 2500,
"engagementRange": 300,
"description": "Solider carrying M4.",
"abilities": "AA, Embark",
"abilities": "Embark",
"canTargetPoint": true,
"canRearm": false,
"barrelHeight": 0.95,
@ -5767,7 +5767,7 @@
"shotsToFire": 5,
"tags": "Georgia",
"markerFile": "groundunit-infantry",
"canAAA": true,
"canAAA": false,
"targetingRange": 100,
"aimMethodRange": 2000,
"shotsBaseInterval": 5,
@ -5829,7 +5829,7 @@
"acquisitionRange": 2500,
"engagementRange": 300,
"description": "Solider carrying M4.",
"abilities": "AA, Embark",
"abilities": "Embark",
"canTargetPoint": true,
"canRearm": false,
"barrelHeight": 1,
@ -5838,7 +5838,7 @@
"shotsToFire": 5,
"tags": "US",
"markerFile": "groundunit-infantry",
"canAAA": true,
"canAAA": false,
"targetingRange": 100,
"aimMethodRange": 2000,
"shotsBaseInterval": 5,
@ -5909,7 +5909,7 @@
"shotsToFire": 1,
"tags": "Russian",
"markerFile": "groundunit-infantry",
"canAAA": true,
"canAAA": false,
"targetingRange": 50,
"aimMethodRange": 750,
"shotsBaseInterval": 5,
@ -6200,7 +6200,7 @@
"aimMethodRange": 2500,
"shotsBaseScatter": 5,
"alertnessTimeConstant": 6,
"canAAA": true
"canAAA": false
},
"T-72B": {
"name": "T-72B",
@ -6234,7 +6234,7 @@
"muzzleVelocity": 700,
"aimMethodRange": 2500,
"alertnessTimeConstant": 4,
"canAAA": true,
"canAAA": false,
"tags": "CA"
},
"T-80UD": {
@ -6310,7 +6310,7 @@
"aimMethodRange": 3000,
"shotsBaseScatter": 5,
"alertnessTimeConstant": 5,
"canAAA": true
"canAAA": false
},
"T-90": {
"name": "T-90",
@ -6374,7 +6374,7 @@
"shotsToFire": 5,
"shotsBaseInterval": 8,
"tags": "CA",
"canAAA": true,
"canAAA": false,
"alertnessTimeConstant": 3,
"shotsBaseScatter": 5,
"aimMethodRange": 3000
@ -6406,7 +6406,7 @@
"shotsBaseInterval": 5,
"shotsBaseScatter": 5,
"alertnessTimeConstant": 5,
"canAAA": true
"canAAA": false
},
"Tigr_233036": {
"name": "Tigr_233036",
@ -6757,7 +6757,7 @@
"canTargetPoint": true,
"canRearm": false,
"shotsToFire": 5,
"aimTime": 8,
"aimTime": 6,
"muzzleVelocity": 1000,
"barrelHeight": 3,
"cost": null,
@ -6795,7 +6795,7 @@
"cost": null,
"barrelHeight": 3,
"muzzleVelocity": 1000,
"aimTime": 8,
"aimTime": 6,
"shotsToFire": 5,
"tags": "CA",
"markerFile": "groundunit-aaa",
@ -7028,7 +7028,7 @@
"cost": null,
"barrelHeight": 2.5,
"muzzleVelocity": 900,
"aimTime": 5,
"aimTime": 8,
"shotsToFire": 5,
"tags": "Radar, CA",
"markerFile": "groundunit-aaa",
@ -7240,7 +7240,7 @@
"canRearm": false,
"barrelHeight": 1.8,
"muzzleVelocity": 1000,
"aimTime": 9,
"aimTime": 13,
"shotsToFire": 5,
"cost": null,
"tags": "Radar, CA",
@ -7325,7 +7325,7 @@
"canTargetPoint": true,
"canRearm": false,
"shotsToFire": 5,
"aimTime": 9,
"aimTime": 6,
"muzzleVelocity": 1000,
"barrelHeight": 1.5,
"cost": null,
@ -7377,7 +7377,7 @@
"canTargetPoint": true,
"canRearm": false,
"shotsToFire": 5,
"aimTime": 9,
"aimTime": 6,
"muzzleVelocity": 1000,
"barrelHeight": 1.5,
"cost": null,
@ -7413,7 +7413,7 @@
"canTargetPoint": true,
"canRearm": false,
"shotsToFire": 5,
"aimTime": 9,
"aimTime": 6,
"muzzleVelocity": 1000,
"barrelHeight": 1.5,
"cost": null,
@ -7795,7 +7795,7 @@
"acquisitionRange": 2500,
"engagementRange": 300,
"description": "Insurgent solider carrying AK-74.",
"abilities": "AA, Embark",
"abilities": "Embark",
"canTargetPoint": true,
"canRearm": false,
"barrelHeight": 0.9,
@ -7804,7 +7804,7 @@
"shotsToFire": 5,
"tags": "Insurgent",
"markerFile": "groundunit-infantry",
"canAAA": true,
"canAAA": false,
"aimMethodRange": 2000,
"targetingRange": 100,
"shotsBaseInterval": 5,
@ -7958,7 +7958,7 @@
"canRearm": false,
"barrelHeight": 0.9,
"muzzleVelocity": 900,
"aimTime": 5,
"aimTime": 3,
"shotsToFire": 5,
"tags": "Russian type 2",
"markerFile": "groundunit-infantry",
@ -8047,7 +8047,7 @@
"acquisitionRange": 2500,
"engagementRange": 300,
"description": "Russian solider carrying AK-74.",
"abilities": "AA, Embark",
"abilities": "Embark",
"canTargetPoint": true,
"canRearm": false,
"barrelHeight": 0.9,
@ -8056,7 +8056,7 @@
"shotsToFire": 5,
"tags": "Russian type 3",
"markerFile": "groundunit-infantry",
"canAAA": true,
"canAAA": false,
"aimMethodRange": 2000,
"targetingRange": 100,
"shotsBaseInterval": 5,
@ -8227,7 +8227,7 @@
"tags": "CA",
"aimMethodRange": 3000,
"shotsBaseScatter": 5,
"canAAA": true,
"canAAA": false,
"alertnessTimeConstant": 3
},
"LiAZ Bus": {
@ -8619,24 +8619,25 @@
"countries": "All"
}
},
"acquisitionRange": 16000,
"engagementRange": 2000,
"acquisitionRange": 30000,
"engagementRange": 21000,
"description": "KS-19. 100 mm AAA gun. Fixed manually aimed large calibre anti aircraft gun.",
"abilities": "AA",
"canTargetPoint": false,
"canRearm": false,
"muzzleVelocity": 1000,
"aimTime": 25,
"aimTime": 15,
"shotsToFire": 5,
"barrelHeight": 5,
"cost": null,
"markerFile": "groundunit-aaa",
"canAAA": true,
"targetingRange": 100,
"aimMethodRange": 15000,
"aimMethodRange": 100,
"shotsBaseInterval": 5,
"shotsBaseScatter": 5,
"alertnessTimeConstant": 5
"alertnessTimeConstant": 5,
"flak": true
},
"SON_9": {
"name": "SON_9",
@ -8969,7 +8970,7 @@
"cost": null,
"barrelHeight": 2,
"muzzleVelocity": 1000,
"aimTime": 5,
"aimTime": 6,
"shotsToFire": 5,
"tags": "CA",
"markerFile": "groundunit-aaa",
@ -9016,7 +9017,7 @@
"cost": null,
"barrelHeight": 2,
"muzzleVelocity": 1000,
"aimTime": 5,
"aimTime": 6,
"shotsToFire": 5,
"tags": "CA",
"markerFile": "groundunit-aaa",
@ -9329,7 +9330,7 @@
"canRearm": false,
"barrelHeight": 2.6,
"muzzleVelocity": 900,
"aimTime": 5,
"aimTime": 3,
"shotsToFire": 5,
"tags": "CA",
"markerFile": "groundunit-apc",
@ -9520,7 +9521,7 @@
"canRearm": false,
"muzzleVelocity": 1200,
"barrelHeight": 3,
"aimTime": 20,
"aimTime": 10,
"shotsToFire": 5,
"cost": null,
"tags": "CA",
@ -9558,7 +9559,7 @@
"canTargetPoint": true,
"canRearm": false,
"muzzleVelocity": 1000,
"aimTime": 5,
"aimTime": 10,
"shotsToFire": 5,
"barrelHeight": 2,
"cost": null,
@ -9645,7 +9646,7 @@
"barrelHeight": 2.7,
"shotsBaseInterval": 8,
"shotsToFire": 5,
"aimTime": 5,
"aimTime": 3,
"shotsBaseScatter": 5,
"alertnessTimeConstant": 3,
"aimMethodRange": 3000,
@ -9771,7 +9772,7 @@
"canRearm": false,
"barrelHeight": 2.8,
"muzzleVelocity": 900,
"aimTime": 5,
"aimTime": 4,
"shotsToFire": 5,
"tags": "CA",
"markerFile": "groundunit-apc",
@ -10634,7 +10635,7 @@
"countries": "All"
}
},
"aimTime": 25,
"aimTime": 15,
"shotsToFire": 2,
"acquisitionRange": 15000,
"engagementRange": 12000,
@ -10649,9 +10650,10 @@
"canAAA": true,
"shotsBaseInterval": 10,
"shotsBaseScatter": 5,
"aimMethodRange": 15000,
"targetingRange": 200,
"alertnessTimeConstant": 15
"aimMethodRange": 100,
"targetingRange": 100,
"alertnessTimeConstant": 15,
"flak": true
},
"Pz_IV_H": {
"name": "Pz_IV_H",
@ -10768,7 +10770,7 @@
"aimMethodRange": 3000,
"barrelHeight": 2.7,
"muzzleVelocity": 700,
"aimTime": 5,
"aimTime": 3,
"shotsToFire": 5,
"shotsBaseInterval": 6,
"tags": "CA",
@ -10879,7 +10881,7 @@
"barrelHeight": 2.7,
"shotsBaseInterval": 7,
"tags": "CA",
"aimTime": 5,
"aimTime": 6,
"shotsToFire": 5,
"aimMethodRange": 3000,
"shotsBaseScatter": 5,
@ -10957,7 +10959,7 @@
"barrelHeight": 2.7,
"shotsBaseInterval": 7,
"shotsToFire": 5,
"aimTime": 5,
"aimTime": 8,
"tags": "CA",
"aimMethodRange": 3000,
"shotsBaseScatter": 5,
@ -11195,7 +11197,7 @@
"alertnessTimeConstant": 3,
"shotsBaseScatter": 5,
"aimMethodRange": 3000,
"canAAA": true
"canAAA": false
},
"ZBD04A": {
"name": "ZBD04A",
@ -11357,7 +11359,7 @@
"aimMethodRange": 3000,
"shotsBaseScatter": 5,
"alertnessTimeConstant": 6,
"canAAA": true
"canAAA": false
},
"Kubelwagen_82": {
"name": "Kubelwagen_82",
@ -11860,7 +11862,7 @@
"canTargetPoint": true,
"canRearm": false,
"markerFile": "groundunit-aaa",
"canAAA": true
"canAAA": false
},
"Flakscheinwerfer_37": {
"name": "Flakscheinwerfer_37",
@ -11896,7 +11898,8 @@
"canTargetPoint": true,
"canRearm": false,
"markerFile": "groundunit-aaa",
"canAAA": true
"canAAA": true,
"flak": true
},
"Maschinensatz_33": {
"name": "Maschinensatz_33",
@ -11932,7 +11935,7 @@
"canTargetPoint": true,
"canRearm": false,
"markerFile": "groundunit-aaa",
"canAAA": true
"canAAA": false
},
"soldier_mauser98": {
"name": "soldier_mauser98",
@ -12516,7 +12519,8 @@
"canTargetPoint": true,
"canRearm": false,
"markerFile": "groundunit-aaa",
"canAAA": true
"canAAA": true,
"aimTime": 6
},
"Allies_Director": {
"name": "Allies_Director",
@ -12886,7 +12890,8 @@
"canTargetPoint": true,
"canRearm": false,
"markerFile": "groundunit-aaa",
"canAAA": true
"canAAA": true,
"aimTime": 6
},
"M1_37mm": {
"name": "M1_37mm",
@ -12922,7 +12927,8 @@
"canTargetPoint": true,
"canRearm": false,
"markerFile": "groundunit-aaa",
"canAAA": true
"canAAA": true,
"aimTime": 5
},
"DR_50Ton_Flat_Wagon": {
"name": "DR_50Ton_Flat_Wagon",
@ -13069,7 +13075,8 @@
"name": "Winter",
"countries": "All"
}
}
},
"flak": true
},
"flak36": {
"name": "flak36",
@ -13097,7 +13104,8 @@
"name": "Winter",
"countries": "All"
}
}
},
"flak": true
},
"flak37": {
"name": "flak37",
@ -13125,7 +13133,8 @@
"name": "Winter",
"countries": "All"
}
}
},
"flak": true
},
"flak38": {
"name": "flak38",
@ -13153,7 +13162,8 @@
"name": "Winter",
"countries": "All"
}
}
},
"flak": true
},
"flak41": {
"name": "flak41",
@ -13181,7 +13191,8 @@
"name": "Winter",
"countries": "All"
}
}
},
"flak": true
},
"HEMTT_C-RAM_Phalanx": {
"name": "HEMTT_C-RAM_Phalanx",

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="50"
height="50"
viewBox="0 0 50 50"
fill="none"
version="1.1"
id="svg1"
sodipodi:docname="shell.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="16.26"
inkscape:cx="25.03075"
inkscape:cy="25"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
d="m 24.992747,19.632095 c 1.718432,1.191442 1.737049,6.906456 1.741766,10.585026 h -3.483451 c 0.07653,-3.377681 -0.03261,-9.113527 1.741685,-10.585026 z"
fill="#5ca7ff"
stroke="#082e44"
stroke-width="0.814233"
id="path1"
sodipodi:nodetypes="cccc"
style="fill:#424242;fill-opacity:1;stroke:#000000;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="50"
height="50"
viewBox="0 0 50 50"
fill="none"
version="1.1"
id="svg1"
sodipodi:docname="shell.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="16.26"
inkscape:cx="25.03075"
inkscape:cy="25"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
d="m 24.992747,19.632095 c 1.718432,1.191442 1.737049,6.906456 1.741766,10.585026 h -3.483451 c 0.07653,-3.377681 -0.03261,-9.113527 1.741685,-10.585026 z"
fill="#5ca7ff"
stroke="#082e44"
stroke-width="0.814233"
id="path1"
sodipodi:nodetypes="cccc"
style="fill:#424242;fill-opacity:1;stroke:#000000;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="50"
height="50"
viewBox="0 0 50 50"
fill="none"
version="1.1"
id="svg1"
sodipodi:docname="shell.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="16.26"
inkscape:cx="25.03075"
inkscape:cy="25"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
d="m 24.992747,19.632095 c 1.718432,1.191442 1.737049,6.906456 1.741766,10.585026 h -3.483451 c 0.07653,-3.377681 -0.03261,-9.113527 1.741685,-10.585026 z"
fill="#5ca7ff"
stroke="#082e44"
stroke-width="0.814233"
id="path1"
sodipodi:nodetypes="cccc"
style="fill:#424242;fill-opacity:1;stroke:#000000;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="50"
height="50"
viewBox="0 0 50 50"
fill="none"
version="1.1"
id="svg1"
sodipodi:docname="shell.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="16.26"
inkscape:cx="25.03075"
inkscape:cy="25"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
d="m 24.992747,19.632095 c 1.718432,1.191442 1.737049,6.906456 1.741766,10.585026 h -3.483451 c 0.07653,-3.377681 -0.03261,-9.113527 1.741685,-10.585026 z"
fill="#5ca7ff"
stroke="#082e44"
stroke-width="0.814233"
id="path1"
sodipodi:nodetypes="cccc"
style="fill:#424242;fill-opacity:1;stroke:#000000;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="50"
height="50"
viewBox="0 0 50 50"
fill="none"
version="1.1"
id="svg1"
sodipodi:docname="shell.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="16.26"
inkscape:cx="25.03075"
inkscape:cy="25"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
d="m 24.992747,19.632095 c 1.718432,1.191442 1.737049,6.906456 1.741766,10.585026 h -3.483451 c 0.07653,-3.377681 -0.03261,-9.113527 1.741685,-10.585026 z"
fill="#5ca7ff"
stroke="#082e44"
stroke-width="0.814233"
id="path1"
sodipodi:nodetypes="cccc"
style="fill:#424242;fill-opacity:1;stroke:#000000;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -507,11 +507,6 @@ export class Map extends L.Map {
altKey: false,
ctrlKey: false,
});
/* Periodically check if the camera control endpoint is available */
this.#cameraControlTimer = window.setInterval(() => {
this.#checkCameraPort();
}, 1000);
}
setLayerName(layerName: string) {
@ -1298,33 +1293,6 @@ export class Map extends L.Map {
return minimapBoundaries;
}
#setSlaveDCSCameraAvailable(newSlaveDCSCameraAvailable: boolean) {
this.#slaveDCSCameraAvailable = newSlaveDCSCameraAvailable;
}
/* Check if the camera control plugin is available. Right now this will only change the color of the button, no changes in functionality */
#checkCameraPort() {
if (this.#cameraOptionsXmlHttp?.readyState !== 4) this.#cameraOptionsXmlHttp?.abort();
this.#cameraOptionsXmlHttp = new XMLHttpRequest();
/* Using 127.0.0.1 instead of localhost because the LuaSocket version used in DCS only listens to IPv4. This avoids the lag caused by the
browser if it first tries to send the request on the IPv6 address for localhost */
this.#cameraOptionsXmlHttp.open("OPTIONS", `http://127.0.0.1:${this.#cameraControlPort}`);
this.#cameraOptionsXmlHttp.onload = (res: any) => {
if (this.#cameraOptionsXmlHttp !== null && this.#cameraOptionsXmlHttp.status == 204) this.#setSlaveDCSCameraAvailable(true);
else this.#setSlaveDCSCameraAvailable(false);
};
this.#cameraOptionsXmlHttp.onerror = (res: any) => {
this.#setSlaveDCSCameraAvailable(false);
};
this.#cameraOptionsXmlHttp.ontimeout = (res: any) => {
this.#setSlaveDCSCameraAvailable(false);
};
this.#cameraOptionsXmlHttp.timeout = 500;
this.#cameraOptionsXmlHttp.send("");
}
#drawIPToTargetLine() {
if (this.#targetPoint && this.#IPPoint) {
if (!this.#IPToTargetLine) {

View File

@ -188,6 +188,8 @@ export abstract class Unit extends CustomMarker {
#targetingRange: number = 0;
#aimMethodRange: number = 0;
#acquisitionRange: number = 0;
#totalAmmo: number = 0;
#previousTotalAmmo: number = 0;
/* Inputs timers */
#debounceTimeout: number | null = null;
@ -654,6 +656,8 @@ export abstract class Unit extends CustomMarker {
break;
case DataIndexes.ammo:
this.#ammo = dataExtractor.extractAmmo();
this.#previousTotalAmmo = this.#totalAmmo;
this.#totalAmmo = this.#ammo.reduce((prev: number, ammo: Ammo) => prev + ammo.quantity, 0);
break;
case DataIndexes.contacts:
this.#contacts = dataExtractor.extractContacts();

View File

@ -43,6 +43,7 @@ export abstract class Weapon extends CustomMarker {
static getConstructor(type: string) {
if (type === "Missile") return Missile;
if (type === "Bomb") return Bomb;
if (type === "Shell") return Shell;
}
constructor(ID: number) {
@ -330,3 +331,40 @@ export class Bomb extends Weapon {
};
}
}
export class Shell extends Weapon {
constructor(ID: number) {
super(ID);
}
getCategory() {
return "Shell";
}
getMarkerCategory() {
if (this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC)) return "shell";
else return "aircraft";
}
getIconOptions() {
return {
showState: false,
showVvi:
!this.belongsToCommandedCoalition() &&
!this.getDetectionMethods().some((value) => [VISUAL, OPTIC].includes(value)) &&
this.getDetectionMethods().some((value) => [RADAR, IRST, DLINK].includes(value)),
showHealth: false,
showHotgroup: false,
showUnitIcon: this.belongsToCommandedCoalition() || this.getDetectionMethods().some((value) => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value)),
showShortLabel: false,
showFuel: false,
showAmmo: false,
showSummary:
!this.belongsToCommandedCoalition() &&
!this.getDetectionMethods().some((value) => [VISUAL, OPTIC].includes(value)) &&
this.getDetectionMethods().some((value) => [RADAR, IRST, DLINK].includes(value)),
showCallsign: false,
rotateToHeading: this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC),
};
}
}

View File

@ -38,7 +38,7 @@ export class WeaponsManager {
/** Add a new weapon to the manager
*
* @param ID ID of the new weapon
* @param category Either "Missile" or "Bomb". Determines what class will be used to create the new unit accordingly.
* @param category Either "Missile", "Bomb" or "Shell". Determines what class will be used to create the new unit accordingly.
*/
addWeapon(ID: number, category: string) {
if (category) {

View File

@ -1415,6 +1415,8 @@ function Olympus.setWeaponsData(arg, time)
table["category"] = "Missile"
elseif weapon:getDesc().category == Weapon.Category.BOMB then
table["category"] = "Bomb"
elseif weapon:getDesc().category == Weapon.Category.SHELL then
table["category"] = "Shell"
end
else
weapons[ID] = {isAlive = false}