|
|
|
|
@@ -245,116 +245,132 @@ void GroundUnit::AIloop()
|
|
|
|
|
canAAA = databaseEntry[L"canAAA"].as_bool();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!canAAA)
|
|
|
|
|
setState(State::IDLE);
|
|
|
|
|
if (canAAA) {
|
|
|
|
|
/* Only run this when the internal counter reaches 0 to avoid excessive computations when no nearby target */
|
|
|
|
|
if (internalCounter == 0 && getOperateAs() > 0) {
|
|
|
|
|
double distance = 0;
|
|
|
|
|
unsigned char targetCoalition = getOperateAs() == 2 ? 1 : 2;
|
|
|
|
|
|
|
|
|
|
/* Default gun values */
|
|
|
|
|
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 */
|
|
|
|
|
|
|
|
|
|
/* Only run this when the internal counter reaches 0 to avoid excessive computations when no nearby target */
|
|
|
|
|
if (internalCounter == 0 && getOperateAs() > 0) {
|
|
|
|
|
double distance = 0;
|
|
|
|
|
unsigned char targetCoalition = getOperateAs() == 2 ? 1 : 2;
|
|
|
|
|
Unit* target = unitsManager->getClosestUnit(this, targetCoalition, {"Aircraft", "Helicopter"}, distance);
|
|
|
|
|
|
|
|
|
|
/* Default gun values */
|
|
|
|
|
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 */
|
|
|
|
|
|
|
|
|
|
if (database.has_object_field(to_wstring(name))) {
|
|
|
|
|
json::value databaseEntry = database[to_wstring(name)];
|
|
|
|
|
if (databaseEntry.has_number_field(L"barrelHeight"))
|
|
|
|
|
barrelHeight = databaseEntry[L"barrelHeight"].as_number().to_double();
|
|
|
|
|
if (databaseEntry.has_number_field(L"muzzleVelocity"))
|
|
|
|
|
muzzleVelocity = databaseEntry[L"muzzleVelocity"].as_number().to_double();
|
|
|
|
|
if (databaseEntry.has_number_field(L"aimTime"))
|
|
|
|
|
aimTime = databaseEntry[L"aimTime"].as_number().to_double();
|
|
|
|
|
if (databaseEntry.has_number_field(L"shotsToFire"))
|
|
|
|
|
shotsToFire = databaseEntry[L"shotsToFire"].as_number().to_uint32();
|
|
|
|
|
if (databaseEntry.has_number_field(L"engagementRange"))
|
|
|
|
|
engagementRange = databaseEntry[L"engagementRange"].as_number().to_double();
|
|
|
|
|
if (databaseEntry.has_number_field(L"shotsBaseInterval"))
|
|
|
|
|
shotsBaseInterval = databaseEntry[L"shotsBaseInterval"].as_number().to_double();
|
|
|
|
|
if (databaseEntry.has_number_field(L"shotsBaseScatter"))
|
|
|
|
|
shotsBaseScatter = databaseEntry[L"shotsBaseScatter"].as_number().to_double();
|
|
|
|
|
if (databaseEntry.has_number_field(L"targetingRange"))
|
|
|
|
|
targetingRange = databaseEntry[L"targetingRange"].as_number().to_double();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Only do if we have a valid target close enough for AAA */
|
|
|
|
|
if (target != nullptr && distance < 3 * engagementRange) {
|
|
|
|
|
/* Approximate the flight time */
|
|
|
|
|
if (muzzleVelocity != 0)
|
|
|
|
|
aimTime += distance / muzzleVelocity;
|
|
|
|
|
|
|
|
|
|
internalCounter = (aimTime + (3 - shotsIntensity) * shotsBaseInterval + 2) / FRAMERATE_TIME_INTERVAL;
|
|
|
|
|
|
|
|
|
|
/* If the target is in targeting range and we are in highest precision mode, target it */
|
|
|
|
|
if (distance < targetingRange && shotsScatter == 3) {
|
|
|
|
|
/* Send the command */
|
|
|
|
|
std::ostringstream taskSS;
|
|
|
|
|
taskSS.precision(10);
|
|
|
|
|
taskSS << "{id = 'AttackUnit', unitID = " << target->getID() << " }";
|
|
|
|
|
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
|
|
|
|
|
scheduler->appendCommand(command);
|
|
|
|
|
setHasTask(true);
|
|
|
|
|
/* Load gun values from database */
|
|
|
|
|
if (database.has_object_field(to_wstring(name))) {
|
|
|
|
|
json::value databaseEntry = database[to_wstring(name)];
|
|
|
|
|
if (databaseEntry.has_number_field(L"barrelHeight"))
|
|
|
|
|
barrelHeight = databaseEntry[L"barrelHeight"].as_number().to_double();
|
|
|
|
|
if (databaseEntry.has_number_field(L"muzzleVelocity"))
|
|
|
|
|
muzzleVelocity = databaseEntry[L"muzzleVelocity"].as_number().to_double();
|
|
|
|
|
if (databaseEntry.has_number_field(L"aimTime"))
|
|
|
|
|
aimTime = databaseEntry[L"aimTime"].as_number().to_double();
|
|
|
|
|
if (databaseEntry.has_number_field(L"shotsToFire"))
|
|
|
|
|
shotsToFire = databaseEntry[L"shotsToFire"].as_number().to_uint32();
|
|
|
|
|
if (databaseEntry.has_number_field(L"engagementRange"))
|
|
|
|
|
engagementRange = databaseEntry[L"engagementRange"].as_number().to_double();
|
|
|
|
|
if (databaseEntry.has_number_field(L"shotsBaseInterval"))
|
|
|
|
|
shotsBaseInterval = databaseEntry[L"shotsBaseInterval"].as_number().to_double();
|
|
|
|
|
if (databaseEntry.has_number_field(L"shotsBaseScatter"))
|
|
|
|
|
shotsBaseScatter = databaseEntry[L"shotsBaseScatter"].as_number().to_double();
|
|
|
|
|
if (databaseEntry.has_number_field(L"targetingRange"))
|
|
|
|
|
targetingRange = databaseEntry[L"targetingRange"].as_number().to_double();
|
|
|
|
|
if (databaseEntry.has_number_field(L"aimMethodRange"))
|
|
|
|
|
aimMethodRange = databaseEntry[L"aimMethodRange"].as_number().to_double();
|
|
|
|
|
}
|
|
|
|
|
/* Else, do miss on purpose */
|
|
|
|
|
else {
|
|
|
|
|
/* Compute where the target will be in aimTime seconds. */
|
|
|
|
|
double aimDistance = target->getHorizontalVelocity() * aimTime;
|
|
|
|
|
double aimLat = 0;
|
|
|
|
|
double aimLng = 0;
|
|
|
|
|
Geodesic::WGS84().Direct(target->getPosition().lat, target->getPosition().lng, target->getHeading() * 57.29577, aimDistance, aimLat, aimLng); /* TODO make util function */
|
|
|
|
|
double aimAlt = target->getPosition().alt + target->getVerticalVelocity() * aimTime;
|
|
|
|
|
|
|
|
|
|
/* Send the command */
|
|
|
|
|
if (distance > engagementRange) {
|
|
|
|
|
aimAtPoint(Coords(aimLat, aimLng, aimAlt));
|
|
|
|
|
log("Aiming at point!");
|
|
|
|
|
/* Get all the units in range and select one at random */
|
|
|
|
|
double range = aimMethodRange > engagementRange ? aimMethodRange : engagementRange;
|
|
|
|
|
map<Unit*, double> targets = unitsManager->getUnitsInRange(this, targetCoalition, { "Aircraft", "Helicopter" }, range);
|
|
|
|
|
Unit* target = nullptr;
|
|
|
|
|
unsigned int index = static_cast<unsigned int>((RANDOM_ZERO_TO_ONE * (targets.size() - 1)));
|
|
|
|
|
for (auto const& p : targets) {
|
|
|
|
|
if (index-- == 0) {
|
|
|
|
|
target = p.first;
|
|
|
|
|
distance = p.second;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* Compute a random scattering depending on the distance and the selected shots scatter */
|
|
|
|
|
double scatterDistance = distance * tan(shotsBaseScatter * (3 - shotsScatter) / 57.29577) * RANDOM_MINUS_ONE_TO_ONE;
|
|
|
|
|
double scatterAngle = 180 * RANDOM_MINUS_ONE_TO_ONE;
|
|
|
|
|
Geodesic::WGS84().Direct(aimLat, aimLng, scatterAngle, scatterDistance, aimLat, aimLng); /* TODO make util function */
|
|
|
|
|
aimAlt = aimAlt + distance * tan(shotsBaseScatter * (3 - shotsScatter) / 57.29577) * RANDOM_MINUS_ONE_TO_ONE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Only do if we have a valid target close enough for AAA */
|
|
|
|
|
if (target != nullptr) {
|
|
|
|
|
/* Approximate the flight time */
|
|
|
|
|
if (muzzleVelocity != 0)
|
|
|
|
|
aimTime += distance / muzzleVelocity;
|
|
|
|
|
|
|
|
|
|
internalCounter = (aimTime + (ShotsIntensity::HIGH - shotsIntensity) * shotsBaseInterval + 2) / FRAMERATE_TIME_INTERVAL;
|
|
|
|
|
|
|
|
|
|
/* If the target is in targeting range and we are in highest precision mode, target it */
|
|
|
|
|
if (distance < targetingRange && shotsScatter == ShotsScatter::LOW) {
|
|
|
|
|
/* Send the command */
|
|
|
|
|
std::ostringstream taskSS;
|
|
|
|
|
taskSS.precision(10);
|
|
|
|
|
taskSS << "{id = 'FireAtPoint', lat = " << aimLat << ", lng = " << aimLng << ", alt = " << aimAlt << ", radius = 0.001, expendQty = " << shotsToFire << " }";
|
|
|
|
|
taskSS << "{id = 'AttackUnit', unitID = " << target->getID() << " }";
|
|
|
|
|
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
|
|
|
|
|
scheduler->appendCommand(command);
|
|
|
|
|
setHasTask(true);
|
|
|
|
|
}
|
|
|
|
|
/* Else, do miss on purpose */
|
|
|
|
|
else {
|
|
|
|
|
/* Compute where the target will be in aimTime seconds. */
|
|
|
|
|
double aimDistance = target->getHorizontalVelocity() * aimTime;
|
|
|
|
|
double aimLat = 0;
|
|
|
|
|
double aimLng = 0;
|
|
|
|
|
Geodesic::WGS84().Direct(target->getPosition().lat, target->getPosition().lng, target->getHeading() * 57.29577, aimDistance, aimLat, aimLng); /* TODO make util to convert degrees and radians function */
|
|
|
|
|
double aimAlt = target->getPosition().alt + target->getVerticalVelocity() * aimTime;
|
|
|
|
|
|
|
|
|
|
setTargetPosition(Coords(aimLat, aimLng, target->getPosition().alt));
|
|
|
|
|
/* Send the command */
|
|
|
|
|
if (distance > engagementRange) {
|
|
|
|
|
aimAtPoint(Coords(aimLat, aimLng, aimAlt));
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* Compute a random scattering depending on the distance and the selected shots scatter */
|
|
|
|
|
double scatterDistance = distance * tan(shotsBaseScatter * (ShotsScatter::LOW - shotsScatter) / 57.29577) * RANDOM_MINUS_ONE_TO_ONE;
|
|
|
|
|
double scatterAngle = 180 * RANDOM_MINUS_ONE_TO_ONE;
|
|
|
|
|
Geodesic::WGS84().Direct(aimLat, aimLng, scatterAngle, scatterDistance, aimLat, aimLng); /* TODO make util function */
|
|
|
|
|
aimAlt = aimAlt + distance * tan(shotsBaseScatter * (ShotsScatter::LOW - shotsScatter) / 57.29577) * RANDOM_MINUS_ONE_TO_ONE;
|
|
|
|
|
|
|
|
|
|
std::ostringstream taskSS;
|
|
|
|
|
taskSS.precision(10);
|
|
|
|
|
taskSS << "{id = 'FireAtPoint', lat = " << aimLat << ", lng = " << aimLng << ", alt = " << aimAlt << ", radius = 0.001, expendQty = " << shotsToFire << " }";
|
|
|
|
|
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
|
|
|
|
|
scheduler->appendCommand(command);
|
|
|
|
|
setHasTask(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setTargetPosition(Coords(aimLat, aimLng, target->getPosition().alt));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
missOnPurposeTarget = target;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
if (getHasTask())
|
|
|
|
|
resetTask();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
missOnPurposeTarget = target;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
if (getHasTask())
|
|
|
|
|
resetTask();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If no valid target was detected */
|
|
|
|
|
if (internalCounter == 0) {
|
|
|
|
|
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();
|
|
|
|
|
/* If no valid target was detected */
|
|
|
|
|
if (internalCounter == 0) {
|
|
|
|
|
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 = (5 + RANDOM_ZERO_TO_ONE * alertnessTimeConstant) / FRAMERATE_TIME_INTERVAL;
|
|
|
|
|
missOnPurposeTarget = nullptr;
|
|
|
|
|
setTargetPosition(Coords(NULL));
|
|
|
|
|
}
|
|
|
|
|
internalCounter = (5 + RANDOM_ZERO_TO_ONE * alertnessTimeConstant) / FRAMERATE_TIME_INTERVAL;
|
|
|
|
|
missOnPurposeTarget = nullptr;
|
|
|
|
|
setTargetPosition(Coords(NULL));
|
|
|
|
|
internalCounter--;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
setState(State::IDLE);
|
|
|
|
|
}
|
|
|
|
|
internalCounter--;
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
@@ -363,6 +379,57 @@ void GroundUnit::AIloop()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void GroundUnit::aimAtPoint(Coords aimTarget, double horizontalScatterMultiplier, double verticalScatterMultiplier) {
|
|
|
|
|
double dist;
|
|
|
|
|
double bearing1;
|
|
|
|
|
double bearing2;
|
|
|
|
|
Geodesic::WGS84().Inverse(position.lat, position.lng, aimTarget.lat, aimTarget.lng, dist, bearing1, bearing2);
|
|
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
double inner = dist * dist - 4 * alpha * (alpha + deltaHeight);
|
|
|
|
|
|
|
|
|
|
/* Check we can reach the target*/
|
|
|
|
|
if (inner > 0) {
|
|
|
|
|
/* Compute elevation and bearing */
|
|
|
|
|
double barrelElevation = r * tan(atan((dist - sqrt(inner)) / (2 * alpha)) + RANDOM_MINUS_ONE_TO_ONE * (ShotsScatter::LOW - shotsScatter) * verticalScatterMultiplier);
|
|
|
|
|
|
|
|
|
|
double lat = 0;
|
|
|
|
|
double lng = 0;
|
|
|
|
|
double randomBearing = bearing1 + RANDOM_MINUS_ONE_TO_ONE * (ShotsScatter::LOW - shotsScatter) * horizontalScatterMultiplier;
|
|
|
|
|
Geodesic::WGS84().Direct(position.lat, position.lng, randomBearing, r, lat, lng);
|
|
|
|
|
|
|
|
|
|
std::ostringstream taskSS;
|
|
|
|
|
taskSS.precision(10);
|
|
|
|
|
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);
|
|
|
|
|
setHasTask(true);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
log("Target out of range for " + unitName + "(" + name + ")");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GroundUnit::changeSpeed(string change)
|
|
|
|
|
{
|
|
|
|
|
if (change.compare("stop") == 0)
|
|
|
|
|
@@ -392,45 +459,3 @@ void GroundUnit::setFollowRoads(bool newFollowRoads, bool force)
|
|
|
|
|
resetActiveDestination(); /* Reset active destination to apply option*/
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GroundUnit::aimAtPoint(Coords aimTarget, double horizontalScatterMultiplier, double verticalScatterMultiplier) {
|
|
|
|
|
double dist;
|
|
|
|
|
double bearing1;
|
|
|
|
|
double bearing2;
|
|
|
|
|
Geodesic::WGS84().Inverse(position.lat, position.lng, aimTarget.lat, aimTarget.lng, dist, bearing1, bearing2);
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//double barrelElevation = r * (9.81 * dist / (2 * muzzleVelocity * muzzleVelocity) + (aimTarget.alt - (position.alt + barrelHeight)) / dist); /* m */
|
|
|
|
|
double deltaHeight = (aimTarget.alt - (position.alt + barrelHeight));
|
|
|
|
|
double alpha = 9.81 / 2 * dist * dist / (muzzleVelocity * muzzleVelocity);
|
|
|
|
|
double inner = dist * dist - 4 * alpha * (alpha + deltaHeight);
|
|
|
|
|
if (inner > 0) {
|
|
|
|
|
double barrelElevation = r * tan(atan((dist - sqrt(inner)) / (2 * alpha)) + RANDOM_MINUS_ONE_TO_ONE * (3 - shotsScatter) * verticalScatterMultiplier);
|
|
|
|
|
|
|
|
|
|
double lat = 0;
|
|
|
|
|
double lng = 0;
|
|
|
|
|
double randomBearing = bearing1 + RANDOM_MINUS_ONE_TO_ONE * (3 - shotsScatter) * horizontalScatterMultiplier;
|
|
|
|
|
Geodesic::WGS84().Direct(position.lat, position.lng, randomBearing, r, lat, lng);
|
|
|
|
|
|
|
|
|
|
std::ostringstream taskSS;
|
|
|
|
|
taskSS.precision(10);
|
|
|
|
|
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);
|
|
|
|
|
setHasTask(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|