diff --git a/backend/core/include/unit.h b/backend/core/include/unit.h index ce2f1b83..a8a972c4 100644 --- a/backend/core/include/unit.h +++ b/backend/core/include/unit.h @@ -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 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; diff --git a/backend/core/include/weapon.h b/backend/core/include/weapon.h index 31d65b84..2fed5f08 100644 --- a/backend/core/include/weapon.h +++ b/backend/core/include/weapon.h @@ -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); }; \ No newline at end of file diff --git a/backend/core/src/groundunit.cpp b/backend/core/src/groundunit.cpp index 194bdfb0..3923ce03 100644 --- a/backend/core/src/groundunit.cpp +++ b/backend/core/src/groundunit.cpp @@ -168,6 +168,18 @@ void GroundUnit::setState(unsigned char newState) void GroundUnit::AIloop() { srand(static_cast(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(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(((ShotsIntensity::HIGH - shotsIntensity) * shotsBaseInterval + aimTime) / FRAMERATE_TIME_INTERVAL); + nextTaskingMilliseconds = timeNow + static_cast(2 * aimTime * 1000); } if (targetPosition == Coords(NULL)) setState(State::IDLE); /* Fallback if something went wrong */ - if (internalCounter == 0) - internalCounter = static_cast(3 / FRAMERATE_TIME_INTERVAL); - internalCounter--; + if (timeNow >= nextTaskingMilliseconds) + nextTaskingMilliseconds = timeNow + static_cast(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(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(((ShotsIntensity::HIGH - shotsIntensity) * shotsBaseInterval + aimTime) / FRAMERATE_TIME_INTERVAL); + nextTaskingMilliseconds = timeNow + static_cast(2 * aimTime * 1000); } else { if (target == nullptr) @@ -361,11 +382,10 @@ void GroundUnit::AIloop() } } - if (internalCounter == 0) - internalCounter = static_cast(3 / FRAMERATE_TIME_INTERVAL); - internalCounter--; + if (timeNow >= nextTaskingMilliseconds) + nextTaskingMilliseconds = timeNow + static_cast(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(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); })); scheduler->appendCommand(command); + shellsFiredAtTasking = totalShellsFired; setHasTask(true); - internalCounter = static_cast((correctedAimTime + (ShotsIntensity::HIGH - shotsIntensity) * shotsBaseInterval + 2) / FRAMERATE_TIME_INTERVAL); + nextTaskingMilliseconds = timeNow + static_cast(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(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((correctedAimTime + (ShotsIntensity::HIGH - shotsIntensity) * shotsBaseInterval + 2) / FRAMERATE_TIME_INTERVAL); + nextTaskingMilliseconds = timeNow + static_cast(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((correctedAimTime + (ShotsIntensity::HIGH - shotsIntensity) * shotsBaseInterval + 2) / FRAMERATE_TIME_INTERVAL); + nextTaskingMilliseconds = timeNow + static_cast(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(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(5 / FRAMERATE_TIME_INTERVAL); + nextTaskingMilliseconds = timeNow + static_cast(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((5 + RANDOM_ZERO_TO_ONE * alertnessTimeConstant) / FRAMERATE_TIME_INTERVAL); + nextTaskingMilliseconds = timeNow + static_cast((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(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); })); scheduler->appendCommand(command); + shellsFiredAtTasking = totalShellsFired; setHasTask(true); } else { diff --git a/backend/core/src/unit.cpp b/backend/core/src/unit.cpp index 48e1383f..32e72846 100644 --- a/backend/core/src/unit.cpp +++ b/backend/core/src/unit.cpp @@ -825,3 +825,11 @@ void Unit::setHasTaskAssigned(bool newHasTaskAssigned) { void Unit::triggerUpdate(unsigned char datumIndex) { updateTimeMap[datumIndex] = duration_cast(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; +} diff --git a/backend/core/src/weapon.cpp b/backend/core/src/weapon.cpp index d826bacb..c1e77712 100644 --- a/backend/core/src/weapon.cpp +++ b/backend/core/src/weapon.cpp @@ -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"); }; \ No newline at end of file diff --git a/backend/core/src/weaponsmanager.cpp b/backend/core/src/weaponsmanager.cpp index cc0c21c3..5343456a 100644 --- a/backend/core/src/weaponsmanager.cpp +++ b/backend/core/src/weaponsmanager.cpp @@ -41,6 +41,8 @@ void WeaponsManager::update(json::value& json, double dt) weapons[ID] = dynamic_cast(new Missile(p.second, ID)); else if (category.compare("Bomb") == 0) weapons[ID] = dynamic_cast(new Bomb(p.second, ID)); + else if (category.compare("Shell") == 0) + weapons[ID] = dynamic_cast(new Shell(p.second, ID)); /* Initialize the weapon if creation was successfull */ if (weapons.count(ID) != 0) { diff --git a/frontend/react/public/images/units/map/awacs/blue/shell.svg b/frontend/react/public/images/units/map/awacs/blue/shell.svg new file mode 100644 index 00000000..72ebcfb5 --- /dev/null +++ b/frontend/react/public/images/units/map/awacs/blue/shell.svg @@ -0,0 +1,43 @@ + + + + + + diff --git a/frontend/react/public/images/units/map/awacs/neutral/shell.svg b/frontend/react/public/images/units/map/awacs/neutral/shell.svg new file mode 100644 index 00000000..72ebcfb5 --- /dev/null +++ b/frontend/react/public/images/units/map/awacs/neutral/shell.svg @@ -0,0 +1,43 @@ + + + + + + diff --git a/frontend/react/public/images/units/map/normal/blue/shell.svg b/frontend/react/public/images/units/map/normal/blue/shell.svg new file mode 100644 index 00000000..72ebcfb5 --- /dev/null +++ b/frontend/react/public/images/units/map/normal/blue/shell.svg @@ -0,0 +1,43 @@ + + + + + + diff --git a/frontend/react/public/images/units/map/normal/neutral/shell.svg b/frontend/react/public/images/units/map/normal/neutral/shell.svg new file mode 100644 index 00000000..72ebcfb5 --- /dev/null +++ b/frontend/react/public/images/units/map/normal/neutral/shell.svg @@ -0,0 +1,43 @@ + + + + + + diff --git a/frontend/react/public/images/units/map/normal/red/shell.svg b/frontend/react/public/images/units/map/normal/red/shell.svg new file mode 100644 index 00000000..72ebcfb5 --- /dev/null +++ b/frontend/react/public/images/units/map/normal/red/shell.svg @@ -0,0 +1,43 @@ + + + + + + diff --git a/frontend/react/src/map/map.ts b/frontend/react/src/map/map.ts index 1b50c1a5..bee24c70 100644 --- a/frontend/react/src/map/map.ts +++ b/frontend/react/src/map/map.ts @@ -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) { diff --git a/frontend/react/src/unit/unit.ts b/frontend/react/src/unit/unit.ts index 8be5071b..917e33bc 100644 --- a/frontend/react/src/unit/unit.ts +++ b/frontend/react/src/unit/unit.ts @@ -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(); diff --git a/frontend/react/src/weapon/weapon.ts b/frontend/react/src/weapon/weapon.ts index b37bad0d..0181c9e0 100644 --- a/frontend/react/src/weapon/weapon.ts +++ b/frontend/react/src/weapon/weapon.ts @@ -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), + }; + } +} diff --git a/frontend/react/src/weapon/weaponsmanager.ts b/frontend/react/src/weapon/weaponsmanager.ts index c328c98f..f308c833 100644 --- a/frontend/react/src/weapon/weaponsmanager.ts +++ b/frontend/react/src/weapon/weaponsmanager.ts @@ -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) { diff --git a/scripts/lua/backend/OlympusCommand.lua b/scripts/lua/backend/OlympusCommand.lua index af9f1ce9..4a0d2385 100644 --- a/scripts/lua/backend/OlympusCommand.lua +++ b/scripts/lua/backend/OlympusCommand.lua @@ -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}