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/databases/units/groundunitdatabase.json b/databases/units/groundunitdatabase.json index 43537996..e140977a 100644 --- a/databases/units/groundunitdatabase.json +++ b/databases/units/groundunitdatabase.json @@ -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", 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}