From 11a5fec1951e20cdf4bd69b83da48f5f95436508 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Mon, 30 Oct 2023 15:56:10 +0100 Subject: [PATCH] Added effect of vertical velocity and of scatter and intensity --- client/src/constants/constants.ts | 2 + client/src/interfaces.ts | 2 + client/src/unit/unit.ts | 8 +++ scripts/OlympusCommand.lua | 5 +- src/core/include/datatypes.h | 2 + src/core/include/unit.h | 11 +++- src/core/src/airunit.cpp | 10 ++++ src/core/src/groundunit.cpp | 89 ++++++++++++++++++++----------- src/core/src/navyunit.cpp | 4 ++ src/core/src/unit.cpp | 14 ++++- src/core/src/unitsmanager.cpp | 5 +- 11 files changed, 116 insertions(+), 36 deletions(-) diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts index b2c5834b..37a1f040 100644 --- a/client/src/constants/constants.ts +++ b/client/src/constants/constants.ts @@ -189,6 +189,8 @@ export enum DataIndexes { hasTask, position, speed, + horizontalVelocity, + verticalVelocity, heading, isActiveTanker, isActiveAWACS, diff --git a/client/src/interfaces.ts b/client/src/interfaces.ts index 61bd3409..e795091d 100644 --- a/client/src/interfaces.ts +++ b/client/src/interfaces.ts @@ -152,6 +152,8 @@ export interface UnitData { hasTask: boolean; position: LatLng; speed: number; + horizontalVelocity: number; + verticalVelocity: number; heading: number; isActiveTanker: boolean; isActiveAWACS: boolean; diff --git a/client/src/unit/unit.ts b/client/src/unit/unit.ts index 1679d1d2..4e75929f 100644 --- a/client/src/unit/unit.ts +++ b/client/src/unit/unit.ts @@ -34,6 +34,8 @@ export class Unit extends CustomMarker { #hasTask: boolean = false; #position: LatLng = new LatLng(0, 0, 0); #speed: number = 0; + #horizontalVelocity: number = 0; + #verticalVelocity: number = 0; #heading: number = 0; #isActiveTanker: boolean = false; #isActiveAWACS: boolean = false; @@ -111,6 +113,8 @@ export class Unit extends CustomMarker { getHasTask() { return this.#hasTask }; getPosition() { return this.#position }; getSpeed() { return this.#speed }; + getHorizontalVelocity() { return this.#horizontalVelocity }; + getVerticalVelocity() { return this.#verticalVelocity }; getHeading() { return this.#heading }; getIsActiveTanker() { return this.#isActiveTanker }; getIsActiveAWACS() { return this.#isActiveAWACS }; @@ -224,6 +228,8 @@ export class Unit extends CustomMarker { case DataIndexes.hasTask: this.#hasTask = dataExtractor.extractBool(); break; case DataIndexes.position: this.#position = dataExtractor.extractLatLng(); updateMarker = true; break; case DataIndexes.speed: this.#speed = dataExtractor.extractFloat64(); updateMarker = true; break; + case DataIndexes.horizontalVelocity: this.#horizontalVelocity = dataExtractor.extractFloat64(); break; + case DataIndexes.verticalVelocity: this.#verticalVelocity = dataExtractor.extractFloat64(); break; case DataIndexes.heading: this.#heading = dataExtractor.extractFloat64(); updateMarker = true; break; case DataIndexes.isActiveTanker: this.#isActiveTanker = dataExtractor.extractBool(); break; case DataIndexes.isActiveAWACS: this.#isActiveAWACS = dataExtractor.extractBool(); break; @@ -290,6 +296,8 @@ export class Unit extends CustomMarker { hasTask: this.#hasTask, position: this.#position, speed: this.#speed, + horizontalVelocity: this.#horizontalVelocity, + verticalVelocity: this.#verticalVelocity, heading: this.#heading, isActiveTanker: this.#isActiveTanker, isActiveAWACS: this.#isActiveAWACS, diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index e77dd00e..87bf92d5 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -888,6 +888,7 @@ function Olympus.setUnitsData(arg, time) local lat, lng, alt = coord.LOtoLL(unit:getPoint()) local position = unit:getPosition() local heading = math.atan2( position.x.z, position.x.x ) + local velocity = unit:getVelocity(); -- Fill the data table table["name"] = unit:getTypeName() @@ -896,7 +897,9 @@ function Olympus.setUnitsData(arg, time) table["position"]["lat"] = lat table["position"]["lng"] = lng table["position"]["alt"] = alt - table["speed"] = mist.vec.mag(unit:getVelocity()) + table["speed"] = mist.vec.mag(velocity) + table["horizontalVelocity"] = math.sqrt(velocity.x * velocity.x + velocity.z * velocity.z) + table["verticalVelocity"] = velocity.y table["heading"] = heading table["isAlive"] = unit:isExist() and unit:isActive() and unit:getLife() >= 1 diff --git a/src/core/include/datatypes.h b/src/core/include/datatypes.h index 26c5a084..d24f28c2 100644 --- a/src/core/include/datatypes.h +++ b/src/core/include/datatypes.h @@ -19,6 +19,8 @@ namespace DataIndex { hasTask, position, speed, + horizontalVelocity, + verticalVelocity, heading, isActiveTanker, isActiveAWACS, diff --git a/src/core/include/unit.h b/src/core/include/unit.h index 058990db..a428dcbe 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -55,6 +55,8 @@ public: bool checkTaskFailed(); void resetTaskFailedCounter(); void setHasTaskAssigned(bool newHasTaskAssigned); + void setEnableTaskCheckFailed(bool newEnableTaskCheckFailed) { enableTaskFailedCheck = newEnableTaskCheckFailed; } + bool getEnableTaskCheckFailed() { return enableTaskFailedCheck; } void triggerUpdate(unsigned char datumIndex); @@ -73,9 +75,11 @@ public: virtual void setGroupName(string newValue) { updateValue(groupName, newValue, DataIndex::groupName); } virtual void setState(unsigned char newValue) { updateValue(state, newValue, DataIndex::state); }; virtual void setTask(string newValue) { updateValue(task, newValue, DataIndex::task); } - virtual void setHasTask(bool newValue) { updateValue(hasTask, newValue, DataIndex::hasTask); } + virtual void setHasTask(bool newValue); virtual void setPosition(Coords newValue) { updateValue(position, newValue, DataIndex::position); } virtual void setSpeed(double newValue) { updateValue(speed, newValue, DataIndex::speed); } + virtual void setHorizontalVelocity(double newValue) { updateValue(horizontalVelocity, newValue, DataIndex::horizontalVelocity); } + virtual void setVerticalVelocity(double newValue) { updateValue(verticalVelocity, newValue, DataIndex::verticalVelocity); } virtual void setHeading(double newValue) { updateValue(heading, newValue, DataIndex::heading); } virtual void setIsActiveTanker(bool newValue); virtual void setIsActiveAWACS(bool newValue); @@ -119,6 +123,8 @@ public: virtual bool getHasTask() { return hasTask; } virtual Coords getPosition() { return position; } virtual double getSpeed() { return speed; } + virtual double getHorizontalVelocity() { return horizontalVelocity; } + virtual double getVerticalVelocity() { return verticalVelocity; } virtual double getHeading() { return heading; } virtual bool getIsActiveTanker() { return isActiveTanker; } virtual bool getIsActiveAWACS() { return isActiveAWACS; } @@ -164,6 +170,8 @@ protected: bool hasTask = false; Coords position = Coords(NULL); double speed = NULL; + double horizontalVelocity = NULL; + double verticalVelocity = NULL; double heading = NULL; bool isActiveTanker = false; bool isActiveAWACS = false; @@ -200,6 +208,7 @@ protected: double initialFuel = 0; map updateTimeMap; unsigned long long lastLoopTime = 0; + bool enableTaskFailedCheck = false; /********** Private methods **********/ virtual void AIloop() = 0; diff --git a/src/core/src/airunit.cpp b/src/core/src/airunit.cpp index 5db499c6..f5f95f20 100644 --- a/src/core/src/airunit.cpp +++ b/src/core/src/airunit.cpp @@ -80,15 +80,18 @@ void AirUnit::setState(unsigned char newState) /************ Perform any action required when ENTERING a state ************/ switch (newState) { case State::IDLE: { + setEnableTaskCheckFailed(false); clearActivePath(); resetActiveDestination(); break; } case State::REACH_DESTINATION: { + setEnableTaskCheckFailed(true); resetActiveDestination(); break; } case State::ATTACK: { + setEnableTaskCheckFailed(true); if (isTargetAlive()) { Unit* target = unitsManager->getUnit(targetID); Coords targetPosition = Coords(target->getPosition().lat, target->getPosition().lng, 0); @@ -99,36 +102,43 @@ void AirUnit::setState(unsigned char newState) break; } case State::FOLLOW: { + setEnableTaskCheckFailed(true); clearActivePath(); resetActiveDestination(); break; } case State::LAND: { + setEnableTaskCheckFailed(false); resetActiveDestination(); break; } case State::REFUEL: { + setEnableTaskCheckFailed(true); initialFuel = fuel; clearActivePath(); resetActiveDestination(); break; } case State::BOMB_POINT: { + setEnableTaskCheckFailed(true); clearActivePath(); resetActiveDestination(); break; } case State::CARPET_BOMB: { + setEnableTaskCheckFailed(true); clearActivePath(); resetActiveDestination(); break; } case State::BOMB_BUILDING: { + setEnableTaskCheckFailed(true); clearActivePath(); resetActiveDestination(); break; } case State::LAND_AT_POINT: { + setEnableTaskCheckFailed(true); resetActiveDestination(); break; } diff --git a/src/core/src/groundunit.cpp b/src/core/src/groundunit.cpp index 801000b0..0ee297a3 100644 --- a/src/core/src/groundunit.cpp +++ b/src/core/src/groundunit.cpp @@ -13,6 +13,9 @@ extern Scheduler* scheduler; extern UnitsManager* unitsManager; json::value GroundUnit::database = json::value(); +#define RANDOM_ZERO_TO_ONE (double)(rand()) / (double)(RAND_MAX) +#define RANDOM_MINUS_ONE_TO_ONE (((double)(rand()) / (double)(RAND_MAX) - 0.5) * 2) + void GroundUnit::loadDatabase(string path) { char* buf = nullptr; size_t sz = 0; @@ -89,30 +92,36 @@ void GroundUnit::setState(unsigned char newState) /************ Perform any action required when ENTERING a state ************/ switch (newState) { case State::IDLE: { + setEnableTaskCheckFailed(false); clearActivePath(); resetActiveDestination(); break; } case State::REACH_DESTINATION: { + setEnableTaskCheckFailed(true); resetActiveDestination(); break; } case State::FIRE_AT_AREA: { + setEnableTaskCheckFailed(true); clearActivePath(); resetActiveDestination(); break; } case State::SIMULATE_FIRE_FIGHT: { + setEnableTaskCheckFailed(true); clearActivePath(); resetActiveDestination(); break; } case State::SCENIC_AAA: { + setEnableTaskCheckFailed(false); clearActivePath(); resetActiveDestination(); break; } case State::MISS_ON_PURPOSE: { + setEnableTaskCheckFailed(false); clearActivePath(); resetActiveDestination(); break; @@ -214,7 +223,7 @@ void GroundUnit::AIloop() } if (internalCounter == 0) - internalCounter = 20 / 0.05; // TODO make defined constant + internalCounter = 20 / FRAMERATE_TIME_INTERVAL; internalCounter--; break; @@ -247,7 +256,7 @@ void GroundUnit::AIloop() } if (internalCounter == 0) - internalCounter = 20 / 0.05; + internalCounter = 20 / FRAMERATE_TIME_INTERVAL; internalCounter--; break; @@ -261,48 +270,58 @@ void GroundUnit::AIloop() 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 = 5; /* degs */ + double engagementRange = 10000; /* 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(); + } + /* Only do if we have a valid target close enough for AAA */ if (target != nullptr && distance < 10000 /* m */) { - /* Default gun values */ - double barrelHeight = 1.0; /* m */ - double muzzleVelocity = 860; /* m/s */ - double aimTime = 10; /* s */ - unsigned int shotsToFire = 10; - - 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(); - } - /* Approximate the flight time */ if (muzzleVelocity != 0) aimTime += distance / muzzleVelocity; - internalCounter = (aimTime + 2) / 0.05; // TODO fix me you fucking monster + internalCounter = (aimTime + (3 - shotsIntensity) * shotsBaseInterval + 2) / FRAMERATE_TIME_INTERVAL; - /* Compute where the target will be in aimTime seconds. We don't consider vertical velocity atm, since after all we are not really tring to hit */ - double aimDistance = target->getSpeed() * aimTime; + /* 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; - /* Compute distance to the aim point */ // TODO: why am I here? - double dist; - double bearing1; - double bearing2; - Geodesic::WGS84().Inverse(position.lat, position.lng, aimLat, aimLng, dist, bearing1, bearing2); + /* 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; /* Send the command */ std::ostringstream taskSS; taskSS.precision(10); - taskSS << "{id = 'FireAtPoint', lat = " << aimLat << ", lng = " << aimLng << ", alt = " << target->getPosition().alt << ", radius = 0.001, expendQty = " << shotsToFire << " }"; + taskSS << "{id = 'FireAtPoint', lat = " << aimLat << ", lng = " << aimLng << ", alt = " << aimAlt << ", radius = 0.001, expendQty = " << shotsToFire << " }"; Command* command = dynamic_cast(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); })); scheduler->appendCommand(command); setHasTask(true); @@ -315,8 +334,16 @@ void GroundUnit::AIloop() } } - if (internalCounter == 0) - internalCounter = 5 / 0.05; + /* 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; + } internalCounter--; break; diff --git a/src/core/src/navyunit.cpp b/src/core/src/navyunit.cpp index 0c66380d..256fbb64 100644 --- a/src/core/src/navyunit.cpp +++ b/src/core/src/navyunit.cpp @@ -81,21 +81,25 @@ void NavyUnit::setState(unsigned char newState) /************ Perform any action required when ENTERING a state ************/ switch (newState) { case State::IDLE: { + setEnableTaskCheckFailed(false); clearActivePath(); resetActiveDestination(); break; } case State::REACH_DESTINATION: { + setEnableTaskCheckFailed(true); resetActiveDestination(); break; } case State::FIRE_AT_AREA: { + setEnableTaskCheckFailed(true); clearActivePath(); resetActiveDestination(); resetTask(); break; } case State::SIMULATE_FIRE_FIGHT: { + setEnableTaskCheckFailed(true); clearActivePath(); resetActiveDestination(); resetTask(); diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index e9eddaa1..58ef3a8e 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -69,6 +69,12 @@ void Unit::update(json::value json, double dt) if (json.has_number_field(L"speed")) setSpeed(json[L"speed"].as_number().to_double()); + if (json.has_number_field(L"horizontalVelocity")) + setHorizontalVelocity(json[L"horizontalVelocity"].as_number().to_double()); + + if (json.has_number_field(L"verticalVelocity")) + setVerticalVelocity(json[L"verticalVelocity"].as_number().to_double()); + if (json.has_boolean_field(L"isAlive")) setAlive(json[L"isAlive"].as_bool()); @@ -146,7 +152,7 @@ void Unit::runAILoop() { /* If the unit is alive, controlled, is the leader of the group and it is not a human, run the AI Loop that performs the requested commands and instructions (moving, attacking, etc) */ if (getAlive() && getControlled() && !getHuman() && getIsLeader()) { - if (checkTaskFailed() && state != State::IDLE && state != State::LAND) { + if (getEnableTaskCheckFailed() && checkTaskFailed()) { log(unitName + " has no task, switching to IDLE state"); setState(State::IDLE); } @@ -249,6 +255,8 @@ void Unit::getData(stringstream& ss, unsigned long long time) case DataIndex::hasTask: appendNumeric(ss, datumIndex, hasTask); break; case DataIndex::position: appendNumeric(ss, datumIndex, position); break; case DataIndex::speed: appendNumeric(ss, datumIndex, speed); break; + case DataIndex::horizontalVelocity: appendNumeric(ss, datumIndex, horizontalVelocity); break; + case DataIndex::verticalVelocity: appendNumeric(ss, datumIndex, verticalVelocity); break; case DataIndex::heading: appendNumeric(ss, datumIndex, heading); break; case DataIndex::isActiveTanker: appendNumeric(ss, datumIndex, isActiveTanker); break; case DataIndex::isActiveAWACS: appendNumeric(ss, datumIndex, isActiveAWACS); break; @@ -734,6 +742,10 @@ bool Unit::updateActivePath(bool looping) } } +void Unit::setHasTask(bool newValue) { + updateValue(hasTask, newValue, DataIndex::hasTask); +} + bool Unit::checkTaskFailed() { if (getHasTask()) diff --git a/src/core/src/unitsmanager.cpp b/src/core/src/unitsmanager.cpp index a157956e..68b7a41e 100644 --- a/src/core/src/unitsmanager.cpp +++ b/src/core/src/unitsmanager.cpp @@ -172,19 +172,20 @@ Unit* UnitsManager::getClosestUnit(Unit* unit, unsigned char coalition, vectorgetPosition().lat, unit->getPosition().lng, p.second->getPosition().lat, p.second->getPosition().lng, dist, bearing1, bearing2); + double altDelta = unit->getPosition().alt - p.second->getPosition().alt; /* If the closest unit has not been assigned yet, assign it to this unit */ if (closestUnit == nullptr) { closestUnit = p.second; - distance = dist; + distance = sqrt(dist * dist + altDelta * altDelta); } else { /* Check if the unit is closer than the one already selected */ if (dist < distance) { closestUnit = p.second; - distance = dist; + distance = sqrt(dist * dist + altDelta * altDelta); } } }