Renamed src to backend

This commit is contained in:
Pax1601
2024-01-03 16:19:16 +01:00
parent a0634a7f99
commit 8024db9579
76 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
#include "aircraft.h"
#include "utils.h"
#include "logger.h"
#include "commands.h"
#include "scheduler.h"
#include "defines.h"
#include "unitsManager.h"
#include <GeographicLib/Geodesic.hpp>
using namespace GeographicLib;
extern Scheduler* scheduler;
extern UnitsManager* unitsManager;
json::value Aircraft::database = json::value();
extern string instancePath;
void Aircraft::loadDatabase(string path) {
std::ifstream ifstream(instancePath + path);
std::stringstream ss;
ss << ifstream.rdbuf();
std::error_code errorCode;
database = json::value::parse(ss.str(), errorCode);
if (database.is_object())
log("Aircrafts database loaded correctly from " + instancePath + path);
else
log("Error reading Aircrafts database file");
}
/* Aircraft */
Aircraft::Aircraft(json::value json, unsigned int ID) : AirUnit(json, ID)
{
log("New Aircraft created with ID: " + to_string(ID));
setCategory("Aircraft");
setDesiredSpeed(knotsToMs(300));
setDesiredAltitude(ftToM(20000));
};
void Aircraft::changeSpeed(string change)
{
if (change.compare("stop") == 0)
setState(State::IDLE);
else if (change.compare("slow") == 0)
setDesiredSpeed(getDesiredSpeed() - knotsToMs(25));
else if (change.compare("fast") == 0)
setDesiredSpeed(getDesiredSpeed() + knotsToMs(25));
if (getDesiredSpeed() < knotsToMs(50))
setDesiredSpeed(knotsToMs(50));
}
void Aircraft::changeAltitude(string change)
{
if (change.compare("descend") == 0)
{
if (getDesiredAltitude() > 5000)
setDesiredAltitude(getDesiredAltitude() - ftToM(2500));
else if (getDesiredAltitude() > 0)
setDesiredAltitude(getDesiredAltitude() - ftToM(500));
}
else if (change.compare("climb") == 0)
{
if (getDesiredAltitude() > 5000)
setDesiredAltitude(getDesiredAltitude() + ftToM(2500));
else if (getDesiredAltitude() >= 0)
setDesiredAltitude(getDesiredAltitude() + ftToM(500));
}
if (getDesiredAltitude() < 0)
setDesiredAltitude(0);
}

View File

@@ -0,0 +1,379 @@
#include "airunit.h"
#include "utils.h"
#include "logger.h"
#include "commands.h"
#include "scheduler.h"
#include "defines.h"
#include "unitsManager.h"
#include <GeographicLib/Geodesic.hpp>
using namespace GeographicLib;
extern Scheduler* scheduler;
extern UnitsManager* unitsManager;
/* Air unit */
AirUnit::AirUnit(json::value json, unsigned int ID) : Unit(json, ID)
{
};
void AirUnit::setDefaults(bool force)
{
if (!getAlive() || !getControlled() || getHuman() || !getIsLeader()) return;
/* Set the default IDLE state */
setState(State::IDLE);
/* Set desired altitude to be equal to current altitude so the unit does not climb/descend after spawn */
setDesiredAltitude(position.alt);
/* Set the default options */
setROE(ROE::OPEN_FIRE_WEAPON_FREE, force);
setReactionToThreat(ReactionToThreat::EVADE_FIRE, force);
setEmissionsCountermeasures(EmissionCountermeasure::DEFEND, force);
strcpy_s(TACAN.callsign, 4, "TKR");
setTACAN(TACAN, force);
setRadio(radio, force);
setGeneralSettings(generalSettings, force);
}
void AirUnit::setState(unsigned char newState)
{
Coords currentTargetPosition = getTargetPosition();
/************ Perform any action required when LEAVING a state ************/
if (newState != state) {
switch (state) {
case State::IDLE: {
break;
}
case State::REACH_DESTINATION: {
break;
}
case State::ATTACK: {
setTargetID(NULL);
break;
}
case State::FOLLOW: {
setLeaderID(NULL);
break;
}
case State::LAND: {
break;
}
case State::REFUEL: {
break;
}
case State::BOMB_POINT:
case State::CARPET_BOMB:
case State::BOMB_BUILDING: {
setTargetPosition(Coords(NULL));
break;
}
case State::LAND_AT_POINT: {
break;
}
default:
break;
}
}
/************ 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);
clearActivePath();
pushActivePathFront(targetPosition);
resetActiveDestination();
}
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:
case State::CARPET_BOMB:
case State::BOMB_BUILDING: {
setTargetPosition(currentTargetPosition);
setEnableTaskCheckFailed(true);
clearActivePath();
resetActiveDestination();
break;
}
case State::LAND_AT_POINT: {
setEnableTaskCheckFailed(true);
resetActiveDestination();
break;
}
default:
break;
}
setHasTask(false);
resetTaskFailedCounter();
log(unitName + " setting state from " + to_string(state) + " to " + to_string(newState));
state = newState;
triggerUpdate(DataIndex::state);
AIloop();
}
void AirUnit::AIloop()
{
srand(static_cast<unsigned int>(time(NULL)) + ID);
/* State machine */
switch (state) {
case State::IDLE: {
if (isActiveTanker)
setTask("Tanker racetrack");
else if (isActiveAWACS)
setTask("AWACS racetrack");
else
setTask("Idle");
if (!getHasTask())
{
std::ostringstream taskSS;
if (isActiveTanker) {
taskSS << "{ [1] = { id = 'Tanker' }, [2] = { id = 'Orbit', pattern = 'Race-Track', altitude = " <<
desiredAltitude << ", speed = " << desiredSpeed << ", altitudeType = '" <<
(desiredAltitudeType ? "AGL" : "ASL") << "', speedType = '" << (desiredSpeedType ? "GS" : "CAS") << "' }}";
}
else if (isActiveAWACS) {
taskSS << "{ [1] = { id = 'AWACS' }, [2] = { id = 'Orbit', pattern = 'Circle', altitude = " <<
desiredAltitude << ", speed = " << desiredSpeed << ", altitudeType = '" <<
(desiredAltitudeType ? "AGL" : "ASL") << "', speedType = '" << (desiredSpeedType ? "GS" : "CAS") << "' }}";
}
else {
taskSS << "{ id = 'Orbit', pattern = 'Circle', altitude = " <<
desiredAltitude << ", speed = " << desiredSpeed << ", altitudeType = '" <<
(desiredAltitudeType ? "AGL" : "ASL") << "', speedType = '" << (desiredSpeedType ? "GS" : "CAS") << "'}";
}
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
scheduler->appendCommand(command);
setHasTask(true);
}
break;
}
case State::REACH_DESTINATION: {
string enrouteTask = "";
bool looping = false;
if (isActiveTanker)
{
enrouteTask = "{ id = 'Tanker' }";
setTask("Tanker");
}
else if (isActiveAWACS)
{
enrouteTask = "{ id = 'AWACS' }";
setTask("AWACS");
}
else
{
enrouteTask = "nil";
setTask("Reaching destination");
}
if (activeDestination == NULL || !getHasTask())
{
if (!setActiveDestination())
setState(State::IDLE);
else
goToDestination(enrouteTask);
}
else {
if (isDestinationReached(getDestinationReachedThreshold())) {
if (updateActivePath(looping) && setActiveDestination())
goToDestination(enrouteTask);
else
setState(State::IDLE);
}
}
break;
}
case State::LAND: {
string enrouteTask = "{ id = 'Land' }";
setTask("Landing");
if (activeDestination == NULL)
{
setActiveDestination();
goToDestination(enrouteTask);
}
break;
}
case State::ATTACK: {
/* If the target is not alive (either not set or was succesfully destroyed) go back to REACH_DESTINATION */
if (!isTargetAlive()) {
setState(State::REACH_DESTINATION);
break;
}
/* Attack state is an "enroute" task, meaning the unit will keep trying to attack even if a new destination is set. This is useful to
manoeuvre the unit so that it can detect and engage the target. */
std::ostringstream enrouteTaskSS;
enrouteTaskSS << "{"
<< "id = 'EngageUnit'" << ","
<< "targetID = " << targetID << ","
<< "}";
string enrouteTask = enrouteTaskSS.str();
setTask("Attacking " + getTargetName());
if (!getHasTask())
{
setActiveDestination();
goToDestination(enrouteTask);
}
break;
}
case State::FOLLOW: {
clearActivePath();
activeDestination = Coords(NULL);
/* If the leader is not alive (either not set or was destroyed) go back to IDLE */
if (!isLeaderAlive()) {
setState(State::IDLE);
break;
}
setTask("Following " + getTargetName());
Unit* leader = unitsManager->getUnit(leaderID);
if (!getHasTask()) {
if (leader != nullptr && leader->getAlive() && formationOffset != NULL)
{
std::ostringstream taskSS;
taskSS << "{"
<< "id = 'FollowUnit'" << ", "
<< "leaderID = " << leader->getID() << ","
<< "offset = {"
<< "x = " << formationOffset.x << ","
<< "y = " << formationOffset.y << ","
<< "z = " << formationOffset.z
<< "},"
<< "}";
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
scheduler->appendCommand(command);
setHasTask(true);
}
}
break;
}
case State::REFUEL: {
setTask("Refueling");
if (!getHasTask()) {
if (fuel <= initialFuel) {
std::ostringstream taskSS;
taskSS << "{"
<< "id = 'Refuel'"
<< "}";
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
scheduler->appendCommand(command);
setHasTask(true);
}
else {
setState(State::IDLE);
}
}
break;
}
case State::BOMB_POINT: {
setTask("Bombing point");
if (!getHasTask()) {
std::ostringstream taskSS;
taskSS.precision(10);
taskSS << "{id = 'Bombing', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << "}";
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
scheduler->appendCommand(command);
setHasTask(true);
}
break;
}
case State::CARPET_BOMB: {
setTask("Carpet bombing");
if (!getHasTask()) {
std::ostringstream taskSS;
taskSS.precision(10);
taskSS << "{id = 'CarpetBombing', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << "}";
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
scheduler->appendCommand(command);
setHasTask(true);
}
break;
}
case State::BOMB_BUILDING: {
setTask("Bombing building");
if (!getHasTask()) {
std::ostringstream taskSS;
taskSS.precision(10);
taskSS << "{id = 'AttackMapObject', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << "}";
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
scheduler->appendCommand(command);
setHasTask(true);
}
break;
}
case State::LAND_AT_POINT: {
setTask("Landing at point");
if (!getHasTask()) {
setActiveDestination();
std::ostringstream taskSS;
taskSS.precision(10),
taskSS << "{"
<< "id = 'LandAtPoint', "
<< "lat = " << activeDestination.lat << ", "
<< "lng = " << activeDestination.lng
<< "}";
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
scheduler->appendCommand(command);
setHasTask(true);
}
break;
}
default:
break;
}
}

View File

@@ -0,0 +1,251 @@
#include "commands.h"
#include "logger.h"
#include "dcstools.h"
#include "unit.h"
#include "unitsmanager.h"
extern UnitsManager* unitsManager;
/* Move command */
string Move::getString()
{
std::ostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.move, "
<< "\"" << groupName << "\"" << ", "
<< destination.lat << ", "
<< destination.lng << ", "
<< altitude << ", "
<< "\"" << altitudeType << "\"" << ", "
<< speed << ", "
<< "\"" << speedType << "\"" << ", "
<< "\"" << category << "\"" << ", "
<< taskOptions;
return commandSS.str();
}
/* Smoke command */
string Smoke::getString()
{
std::ostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.smoke, "
<< "\"" << color << "\"" << ", "
<< location.lat << ", "
<< location.lng;
return commandSS.str();
}
/* Spawn ground units command */
string SpawnGroundUnits::getString()
{
std::ostringstream unitsSS;
unitsSS.precision(10);
for (int i = 0; i < spawnOptions.size(); i++) {
unitsSS << "[" << i + 1 << "] = {"
<< "unitType = " << "\"" << spawnOptions[i].unitType << "\"" << ", "
<< "lat = " << spawnOptions[i].location.lat << ", "
<< "lng = " << spawnOptions[i].location.lng << ", "
<< "liveryID = " << "\"" << spawnOptions[i].liveryID << "\"" << " }, ";
}
std::ostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.spawnUnits, {"
<< "category = " << "\"" << "GroundUnit" << "\"" << ", "
<< "coalition = " << "\"" << coalition << "\"" << ", "
<< "country = \"" << country << "\", "
<< "units = " << "{" << unitsSS.str() << "}" << "}";
return commandSS.str();
}
/* Spawn ground units command */
string SpawnNavyUnits::getString()
{
std::ostringstream unitsSS;
unitsSS.precision(10);
for (int i = 0; i < spawnOptions.size(); i++) {
unitsSS << "[" << i + 1 << "] = {"
<< "unitType = " << "\"" << spawnOptions[i].unitType << "\"" << ", "
<< "lat = " << spawnOptions[i].location.lat << ", "
<< "lng = " << spawnOptions[i].location.lng << ", "
<< "liveryID = " << "\"" << spawnOptions[i].liveryID << "\"" << " }, ";
}
std::ostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.spawnUnits, {"
<< "category = " << "\"" << "NavyUnit" << "\"" << ", "
<< "coalition = " << "\"" << coalition << "\"" << ", "
<< "country = \"" << country << "\", "
<< "units = " << "{" << unitsSS.str() << "}" << "}";
return commandSS.str();
}
/* Spawn aircrafts command */
string SpawnAircrafts::getString()
{
std::ostringstream unitsSS;
unitsSS.precision(10);
for (int i = 0; i < spawnOptions.size(); i++) {
unitsSS << "[" << i + 1 << "] = {"
<< "unitType = " << "\"" << spawnOptions[i].unitType << "\"" << ", "
<< "lat = " << spawnOptions[i].location.lat << ", "
<< "lng = " << spawnOptions[i].location.lng << ", "
<< "alt = " << spawnOptions[i].location.alt << ", "
<< "loadout = \"" << spawnOptions[i].loadout << "\"" << ", "
<< "liveryID = " << "\"" << spawnOptions[i].liveryID << "\"" << " }, ";
}
std::ostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.spawnUnits, {"
<< "category = " << "\"" << "Aircraft" << "\"" << ", "
<< "coalition = " << "\"" << coalition << "\"" << ", "
<< "airbaseName = \"" << airbaseName << "\", "
<< "country = \"" << country << "\", "
<< "units = " << "{" << unitsSS.str() << "}" << "}";
return commandSS.str();
}
/* Spawn helicopters command */
string SpawnHelicopters::getString()
{
std::ostringstream unitsSS;
unitsSS.precision(10);
for (int i = 0; i < spawnOptions.size(); i++) {
unitsSS << "[" << i + 1 << "] = {"
<< "unitType = " << "\"" << spawnOptions[i].unitType << "\"" << ", "
<< "lat = " << spawnOptions[i].location.lat << ", "
<< "lng = " << spawnOptions[i].location.lng << ", "
<< "alt = " << spawnOptions[i].location.alt << ", "
<< "loadout = \"" << spawnOptions[i].loadout << "\"" << ", "
<< "liveryID = " << "\"" << spawnOptions[i].liveryID << "\"" << " }, ";
}
std::ostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.spawnUnits, {"
<< "category = " << "\"" << "Helicopter" << "\"" << ", "
<< "coalition = " << "\"" << coalition << "\"" << ", "
<< "airbaseName = \"" << airbaseName << "\", "
<< "country = \"" << country << "\", "
<< "units = " << "{" << unitsSS.str() << "}" << "}";
return commandSS.str();
}
/* Clone unit command */
string Clone::getString()
{
std::ostringstream unitsSS;
unitsSS.precision(10);
for (int i = 0; i < cloneOptions.size(); i++) {
unitsSS << "[" << i + 1 << "] = {"
<< "ID = " << cloneOptions[i].ID << ", "
<< "lat = " << cloneOptions[i].location.lat << ", "
<< "lng = " << cloneOptions[i].location.lng << " }, ";
}
std::ostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.clone, "
<< "{" << unitsSS.str() << "}" << ", "
<< (deleteOriginal ? "true" : "false");
return commandSS.str();
}
/* Delete unit command */
string Delete::getString()
{
std::ostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.delete, "
<< ID << ", "
<< (explosion ? "true" : "false") << ", "
<< "\"" << explosionType << "\"";
return commandSS.str();
}
/* Set task command */
string SetTask::getString()
{
std::ostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.setTask, "
<< "\"" << groupName << "\"" << ", "
<< task;
return commandSS.str();
}
/* Reset task command */
string ResetTask::getString()
{
std::ostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.resetTask, "
<< "\"" << groupName << "\"";
return commandSS.str();
}
/* Set command command */
string SetCommand::getString()
{
std::ostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.setCommand, "
<< "\"" << groupName << "\"" << ", "
<< command;
return commandSS.str();
}
/* Set option command */
string SetOption::getString()
{
std::ostringstream commandSS;
commandSS.precision(10);
if (!isBoolean) {
commandSS << "Olympus.setOption, "
<< "\"" << groupName << "\"" << ", "
<< optionID << ", "
<< optionValue;
} else {
commandSS << "Olympus.setOption, "
<< "\"" << groupName << "\"" << ", "
<< optionID << ", "
<< (optionBool? "true": "false");
}
return commandSS.str();
}
/* Set onOff command */
string SetOnOff::getString()
{
std::ostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.setOnOff, "
<< "\"" << groupName << "\"" << ", "
<< (onOff ? "true" : "false");
return commandSS.str();
}
/* Explosion command */
string Explosion::getString()
{
std::ostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.explosion, "
<< intensity << ", "
<< "\"" << explosionType << "\"" << ", "
<< location.lat << ", "
<< location.lng;
return commandSS.str();
}

163
backend/core/src/core.cpp Normal file
View File

@@ -0,0 +1,163 @@
#include "dcstools.h"
#include "logger.h"
#include "defines.h"
#include "unitsManager.h"
#include "weaponsManager.h"
#include "server.h"
#include "scheduler.h"
#include "scriptLoader.h"
#include "luatools.h"
#include <chrono>
using namespace std::chrono;
auto lastUnitsUpdate = std::chrono::system_clock::now();
auto lastWeaponsUpdate = std::chrono::system_clock::now();
auto lastExecution = std::chrono::system_clock::now();
/* Singleton objects */
UnitsManager* unitsManager = nullptr;
WeaponsManager* weaponsManager = nullptr;
Server* server = nullptr;
Scheduler* scheduler = nullptr;
/* Data jsons */
json::value missionData = json::value::object();
mutex mutexLock;
string sessionHash;
string instancePath;
bool initialized = false;
unsigned int frameCounter = 0;
/* Called when DCS simulation stops. All singleton instances are deleted. */
extern "C" DllExport int coreDeinit(lua_State* L)
{
if (!initialized)
return (0);
log("Olympus coreDeinit called successfully");
server->stop(L);
delete unitsManager;
delete weaponsManager;
delete server;
delete scheduler;
log("All singletons objects destroyed successfully");
return(0);
}
/* Called when DCS simulation starts. All singletons are instantiated, and the custom Lua functions are registered in the Lua state. */
extern "C" DllExport int coreInit(lua_State* L, const char* path)
{
instancePath = path;
log("Initializing core.dll with instance path " + instancePath);
sessionHash = random_string(16);
log("Random session hash " + sessionHash);
unitsManager = new UnitsManager(L);
weaponsManager = new WeaponsManager(L);
server = new Server(L);
scheduler = new Scheduler(L);
registerLuaFunctions(L);
server->start(L);
unitsManager->loadDatabases();
initialized = true;
return(0);
}
extern "C" DllExport int coreFrame(lua_State* L)
{
if (!initialized)
return (0);
/* Lock for thread safety */
lock_guard<mutex> guard(mutexLock);
frameCounter++;
const std::chrono::duration<double> executionDuration = std::chrono::system_clock::now() - lastExecution;
if (executionDuration.count() > (20 * FRAMERATE_TIME_INTERVAL)) {
if (executionDuration.count() > 0) {
scheduler->setFrameRate(frameCounter / executionDuration.count());
frameCounter = 0;
}
lastExecution = std::chrono::system_clock::now();
}
if (scheduler != nullptr)
scheduler->execute(L);
return(0);
}
extern "C" DllExport int coreUnitsData(lua_State * L)
{
if (!initialized)
return (0);
/* Lock for thread safety */
lock_guard<mutex> guard(mutexLock);
json::value unitsData = json::value::object();
lua_getglobal(L, "Olympus");
lua_getfield(L, -1, "unitsData");
luaTableToJSON(L, -1, unitsData);
const std::chrono::duration<double> updateDuration = std::chrono::system_clock::now() - lastUnitsUpdate;
if (unitsData.has_object_field(L"units")) {
unitsManager->update(unitsData[L"units"], updateDuration.count());
}
lastUnitsUpdate = std::chrono::system_clock::now();
return(0);
}
extern "C" DllExport int coreWeaponsData(lua_State * L)
{
if (!initialized)
return (0);
/* Lock for thread safety */
lock_guard<mutex> guard(mutexLock);
json::value weaponsData = json::value::object();
lua_getglobal(L, "Olympus");
lua_getfield(L, -1, "weaponsData");
luaTableToJSON(L, -1, weaponsData);
const std::chrono::duration<double> updateDuration = std::chrono::system_clock::now() - lastWeaponsUpdate;
if (weaponsData.has_object_field(L"weapons")) {
weaponsManager->update(weaponsData[L"weapons"], updateDuration.count());
}
lastWeaponsUpdate = std::chrono::system_clock::now();
return(0);
}
extern "C" DllExport int coreMissionData(lua_State * L)
{
if (!initialized)
return (0);
/* Lock for thread safety */
lock_guard<mutex> guard(mutexLock);
lua_getglobal(L, "Olympus");
lua_getfield(L, -1, "missionData");
luaTableToJSON(L, -1, missionData);
return(0);
}

View File

@@ -0,0 +1,30 @@
#include "datatypes.h"
bool operator==(const DataTypes::TACAN& lhs, const DataTypes::TACAN& rhs)
{
return lhs.isOn == rhs.isOn && lhs.channel == rhs.channel && lhs.XY == rhs.XY && strcmp(lhs.callsign, rhs.callsign) == 0;
}
bool operator==(const DataTypes::Radio& lhs, const DataTypes::Radio& rhs)
{
return lhs.frequency == rhs.frequency && lhs.callsign == rhs.callsign && lhs.callsignNumber == rhs.callsignNumber;
}
bool operator==(const DataTypes::GeneralSettings& lhs, const DataTypes::GeneralSettings& rhs)
{
return lhs.prohibitAA == rhs.prohibitAA && lhs.prohibitAfterburner == rhs.prohibitAfterburner && lhs.prohibitAG == rhs.prohibitAG &&
lhs.prohibitAirWpn == rhs.prohibitAirWpn && lhs.prohibitJettison == rhs.prohibitJettison;
}
bool operator==(const DataTypes::Ammo& lhs, const DataTypes::Ammo& rhs)
{
return lhs.category == rhs.category && lhs.guidance == rhs.guidance && lhs.missileCategory == rhs.missileCategory &&
lhs.quantity == rhs.quantity && strcmp(lhs.name, rhs.name) == 0;
}
bool operator==(const DataTypes::Contact& lhs, const DataTypes::Contact& rhs)
{
return lhs.detectionMethod == rhs.detectionMethod && lhs.ID == rhs.ID;
}

View File

@@ -0,0 +1,566 @@
#include "groundunit.h"
#include "utils.h"
#include "logger.h"
#include "commands.h"
#include "scheduler.h"
#include "defines.h"
#include "unitsmanager.h"
#include <GeographicLib/Geodesic.hpp>
using namespace GeographicLib;
extern Scheduler* scheduler;
extern UnitsManager* unitsManager;
json::value GroundUnit::database = json::value();
extern string instancePath;
#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) {
std::ifstream ifstream(instancePath + path);
std::stringstream ss;
ss << ifstream.rdbuf();
std::error_code errorCode;
database = json::value::parse(ss.str(), errorCode);
if (database.is_object())
log("GroundUnits database loaded correctly from " + instancePath + path);
else
log("Error reading GroundUnits database file");
}
/* Ground unit */
GroundUnit::GroundUnit(json::value json, unsigned int ID) : Unit(json, ID)
{
log("New Ground Unit created with ID: " + to_string(ID));
setCategory("GroundUnit");
setDesiredSpeed(10);
};
void GroundUnit::setDefaults(bool force)
{
if (!getAlive() || !getControlled() || getHuman() || !getIsLeader()) return;
/* Set the default IDLE state */
setState(State::IDLE);
/* Set the default options */
setROE(ROE::WEAPON_FREE, force);
setOnOff(onOff, force);
setFollowRoads(followRoads, force);
}
void GroundUnit::setState(unsigned char newState)
{
Coords currentTargetPosition = getTargetPosition();
/************ Perform any action required when LEAVING a state ************/
if (newState != state) {
switch (state) {
case State::IDLE: {
break;
}
case State::REACH_DESTINATION: {
break;
}
case State::ATTACK: {
setTargetID(NULL);
break;
}
case State::FIRE_AT_AREA:
case State::SIMULATE_FIRE_FIGHT:
case State::SCENIC_AAA:
case State::MISS_ON_PURPOSE: {
setTargetPosition(Coords(NULL));
break;
}
default:
break;
}
}
/************ 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);
clearActivePath();
resetActiveDestination();
break;
}
case State::FIRE_AT_AREA: {
setTargetPosition(currentTargetPosition);
setEnableTaskCheckFailed(true);
clearActivePath();
resetActiveDestination();
break;
}
case State::SIMULATE_FIRE_FIGHT: {
setTargetPosition(currentTargetPosition);
setEnableTaskCheckFailed(false);
clearActivePath();
resetActiveDestination();
break;
}
case State::SCENIC_AAA: {
setEnableTaskCheckFailed(false);
clearActivePath();
resetActiveDestination();
break;
}
case State::MISS_ON_PURPOSE: {
setEnableTaskCheckFailed(false);
clearActivePath();
resetActiveDestination();
break;
}
default:
break;
}
setHasTask(false);
resetTaskFailedCounter();
log(unitName + " setting state from " + to_string(state) + " to " + to_string(newState));
state = newState;
triggerUpdate(DataIndex::state);
AIloop();
}
void GroundUnit::AIloop()
{
srand(static_cast<unsigned int>(time(NULL)) + ID);
switch (state) {
case State::IDLE: {
setTask("Idle");
if (getHasTask())
resetTask();
break;
}
case State::REACH_DESTINATION: {
setTask("Reaching destination");
string enrouteTask = "";
bool looping = false;
std::ostringstream taskSS;
taskSS << "{ id = 'FollowRoads', value = " << (getFollowRoads() ? "true" : "false") << " }";
enrouteTask = taskSS.str();
if (activeDestination == NULL || !getHasTask())
{
if (!setActiveDestination())
setState(State::IDLE);
else
goToDestination(enrouteTask);
}
else {
if (isDestinationReached(GROUND_DEST_DIST_THR)) {
if (updateActivePath(looping) && setActiveDestination())
goToDestination(enrouteTask);
else
setState(State::IDLE);
}
}
break;
}
case State::ATTACK: {
Unit* target = unitsManager->getUnit(getTargetID());
if (target != nullptr) {
setTask("Attacking " + target->getUnitName());
if (!getHasTask()) {
/* 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);
}
}
else {
setState(State::IDLE);
}
break;
}
case State::FIRE_AT_AREA: {
setTask("Firing at area");
if (!getHasTask()) {
std::ostringstream taskSS;
taskSS.precision(10);
taskSS << "{id = 'FireAtPoint', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << ", radius = 100}";
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
scheduler->appendCommand(command);
setHasTask(true);
}
break;
}
case State::SIMULATE_FIRE_FIGHT: {
setTask("Simulating fire fight");
if (internalCounter == 0 && targetPosition != Coords(NULL) && scheduler->getLoad() < 30) {
/* Get the distance and bearing to the target */
Coords scatteredTargetPosition = targetPosition;
double distance;
double bearing1;
double bearing2;
Geodesic::WGS84().Inverse(getPosition().lat, getPosition().lng, scatteredTargetPosition.lat, scatteredTargetPosition.lng, distance, bearing1, bearing2);
/* 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);
/* Recover the data from the database */
double aimTime = 2; /* s */
bool indirectFire = false;
double shotsBaseInterval = 15; /* s */
if (database.has_object_field(to_wstring(name))) {
json::value databaseEntry = database[to_wstring(name)];
if (databaseEntry.has_number_field(L"aimTime"))
aimTime = databaseEntry[L"aimTime"].as_number().to_double();
if (databaseEntry.has_boolean_field(L"indirectFire"))
indirectFire = databaseEntry[L"indirectFire"].as_bool();
if (databaseEntry.has_number_field(L"shotsBaseInterval"))
shotsBaseInterval = databaseEntry[L"shotsBaseInterval"].as_number().to_double();
}
/* If the unit is of the indirect fire type, like a mortar, simply shoot at the target */
if (indirectFire) {
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}";
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
scheduler->appendCommand(command);
setHasTask(true);
}
/* Otherwise use the aim method */
else {
log(unitName + "(" + name + ")" + " simulating fire fight with aim at point method");
aimAtPoint(scatteredTargetPosition);
}
/* Wait an amout of time depending on the shots intensity */
internalCounter = static_cast<unsigned int>(((ShotsIntensity::HIGH - shotsIntensity) * shotsBaseInterval + aimTime) / FRAMERATE_TIME_INTERVAL);
}
if (targetPosition == Coords(NULL))
setState(State::IDLE);
/* Fallback if something went wrong */
if (internalCounter == 0)
internalCounter = static_cast<unsigned int>(3 / FRAMERATE_TIME_INTERVAL);
internalCounter--;
break;
}
case State::SCENIC_AAA: {
setTask("Scenic AAA");
/* Only perform scenic functions when the scheduler is "free" */
if (((!getHasTask() && scheduler->getLoad() < 30) || internalCounter == 0)) {
double distance = 0;
unsigned char unitCoalition = coalition == 0 ? getOperateAs() : coalition;
unsigned char targetCoalition = unitCoalition == 2 ? 1 : 2;
Unit* target = unitsManager->getClosestUnit(this, targetCoalition, { "Aircraft", "Helicopter" }, distance);
/* Recover the data from the database */
double aimTime = 2; /* s */
double shotsBaseInterval = 15; /* s */
if (database.has_object_field(to_wstring(name))) {
json::value databaseEntry = database[to_wstring(name)];
if (databaseEntry.has_number_field(L"aimTime"))
aimTime = databaseEntry[L"aimTime"].as_number().to_double();
if (databaseEntry.has_number_field(L"shotsBaseInterval"))
shotsBaseInterval = databaseEntry[L"shotsBaseInterval"].as_number().to_double();
}
/* Only run if an enemy air unit is closer than 20km to avoid useless load */
if (target != nullptr && distance < 20000 /* m */) {
double r = 15; /* m */
double barrelElevation = r * tan(acos(((double)(rand()) / (double)(RAND_MAX))));
double lat = 0;
double lng = 0;
double randomBearing = ((double)(rand()) / (double)(RAND_MAX)) * 360;
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 << ", radius = 0.001}";
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
scheduler->appendCommand(command);
setHasTask(true);
/* Wait an amout of time depending on the shots intensity */
internalCounter = static_cast<unsigned int>(((ShotsIntensity::HIGH - shotsIntensity) * shotsBaseInterval + aimTime) / FRAMERATE_TIME_INTERVAL);
}
}
if (internalCounter == 0)
internalCounter = static_cast<unsigned int>(3 / FRAMERATE_TIME_INTERVAL);
internalCounter--;
break;
}
case State::MISS_ON_PURPOSE: {
setTask("Missing on purpose");
/* Check that the unit can perform AAA duties */
bool canAAA = false;
if (database.has_object_field(to_wstring(name))) {
json::value databaseEntry = database[to_wstring(name)];
if (databaseEntry.has_boolean_field(L"canAAA"))
canAAA = databaseEntry[L"canAAA"].as_bool();
}
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) {
double distance = 0;
unsigned char unitCoalition = coalition == 0 ? getOperateAs() : coalition;
unsigned char targetCoalition = unitCoalition == 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 */
double acquisitionRange = 0; /* m */
/* 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();
if (databaseEntry.has_number_field(L"acquisitionRange"))
acquisitionRange = databaseEntry[L"acquisitionRange"].as_number().to_double();
}
/* Get all the units in range and select one at random */
double range = max(max(engagementRange, aimMethodRange), acquisitionRange);
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;
}
}
/* 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;
/* 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 = 'AttackUnit', unitID = " << target->getID() << " }";
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
scheduler->appendCommand(command);
setHasTask(true);
internalCounter = static_cast<unsigned int>((aimTime + (ShotsIntensity::HIGH - shotsIntensity) * shotsBaseInterval + 2) / FRAMERATE_TIME_INTERVAL);
}
/* Else, do miss on purpose */
else {
/* Compute where the target will be in aimTime seconds, plus the effect of scatter. */
double scatterDistance = distance * tan(shotsBaseScatter * (ShotsScatter::LOW - shotsScatter) / 57.29577) * (RANDOM_ZERO_TO_ONE - 0.1);
double aimDistance = target->getHorizontalVelocity() * aimTime + scatterDistance;
double aimLat = 0;
double aimLng = 0;
Geodesic::WGS84().Direct(target->getPosition().lat, target->getPosition().lng, target->getTrack() * 57.29577, aimDistance, aimLat, aimLng); /* TODO make util to convert degrees and radians function */
double aimAlt = target->getPosition().alt + target->getVerticalVelocity() * aimTime + distance * tan(shotsBaseScatter * (ShotsScatter::LOW - shotsScatter) / 57.29577) * RANDOM_ZERO_TO_ONE; // Force to always miss high never low
/* Send the command */
if (distance < engagementRange) {
/* 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 << " }";
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));
internalCounter = static_cast<unsigned int>((aimTime + (ShotsIntensity::HIGH - shotsIntensity) * shotsBaseInterval + 2) / FRAMERATE_TIME_INTERVAL);
}
else if (distance < aimMethodRange) {
/* If the unit is closer than the aim method range, use the aim method range */
aimAtPoint(Coords(aimLat, aimLng, aimAlt));
setTargetPosition(Coords(aimLat, aimLng, target->getPosition().alt));
internalCounter = static_cast<unsigned int>((aimTime + (ShotsIntensity::HIGH - shotsIntensity) * shotsBaseInterval + 2) / FRAMERATE_TIME_INTERVAL);
}
else {
/* Else just wake the unit up with an impossible command */
std::ostringstream taskSS;
taskSS.precision(10);
taskSS << "{id = 'FireAtPoint', lat = " << 0 << ", lng = " << 0 << ", alt = " << 0 << ", radius = 0.001, expendQty = " << 0 << " }";
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
scheduler->appendCommand(command);
setHasTask(true);
setTargetPosition(Coords(NULL));
/* Don't wait too long before checking again */
internalCounter = static_cast<unsigned int>(5 / FRAMERATE_TIME_INTERVAL);
}
}
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();
}
internalCounter = static_cast<unsigned int>((5 + RANDOM_ZERO_TO_ONE * alertnessTimeConstant * 0 /* TODO: remove to enable alertness again */) / FRAMERATE_TIME_INTERVAL);
missOnPurposeTarget = nullptr;
setTargetPosition(Coords(NULL));
}
internalCounter--;
}
else {
setState(State::IDLE);
}
break;
}
default:
break;
}
}
void GroundUnit::aimAtPoint(Coords aimTarget) {
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 * (dist - sqrt(inner)) / (2 * alpha);
double lat = 0;
double lng = 0;
Geodesic::WGS84().Direct(position.lat, position.lng, bearing1, r, lat, lng);
log(unitName + "(" + name + ")" + " shooting with aim at point method. Barrel elevation: " + to_string(barrelElevation * 57.29577) + "°, bearing: " + to_string(bearing1) + "°");
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)
setState(State::IDLE);
else if (change.compare("slow") == 0)
setDesiredSpeed(getDesiredSpeed() - knotsToMs(5));
else if (change.compare("fast") == 0)
setDesiredSpeed(getDesiredSpeed() + knotsToMs(5));
if (getDesiredSpeed() < 0)
setDesiredSpeed(0);
}
void GroundUnit::setOnOff(bool newOnOff, bool force)
{
if (newOnOff != onOff || force) {
Unit::setOnOff(newOnOff, force);
Command* command = dynamic_cast<Command*>(new SetOnOff(groupName, onOff));
scheduler->appendCommand(command);
}
}
void GroundUnit::setFollowRoads(bool newFollowRoads, bool force)
{
if (newFollowRoads != followRoads || force) {
Unit::setFollowRoads(newFollowRoads, force);
resetActiveDestination(); /* Reset active destination to apply option*/
}
}

View File

@@ -0,0 +1,71 @@
#include "helicopter.h"
#include "utils.h"
#include "logger.h"
#include "commands.h"
#include "scheduler.h"
#include "defines.h"
#include "unitsManager.h"
#include <GeographicLib/Geodesic.hpp>
using namespace GeographicLib;
extern Scheduler* scheduler;
extern UnitsManager* unitsManager;
json::value Helicopter::database = json::value();
extern string instancePath;
void Helicopter::loadDatabase(string path) {
std::ifstream ifstream(instancePath + path);
std::stringstream ss;
ss << ifstream.rdbuf();
std::error_code errorCode;
database = json::value::parse(ss.str(), errorCode);
if (database.is_object())
log("Helicopters database loaded correctly from " + instancePath + path);
else
log("Error reading Helicopters database file");
}
/* Helicopter */
Helicopter::Helicopter(json::value json, unsigned int ID) : AirUnit(json, ID)
{
log("New Helicopter created with ID: " + to_string(ID));
setCategory("Helicopter");
setDesiredSpeed(knotsToMs(100));
setDesiredAltitude(ftToM(5000));
};
void Helicopter::changeSpeed(string change)
{
if (change.compare("stop") == 0)
setState(State::IDLE);
else if (change.compare("slow") == 0)
setDesiredSpeed(getDesiredSpeed() - knotsToMs(10));
else if (change.compare("fast") == 0)
setDesiredSpeed(getDesiredSpeed() + knotsToMs(10));
if (getDesiredSpeed() < knotsToMs(0))
setDesiredSpeed(knotsToMs(0));
}
void Helicopter::changeAltitude(string change)
{
if (change.compare("descend") == 0)
{
if (getDesiredAltitude() > 100)
setDesiredAltitude(getDesiredAltitude() - ftToM(100));
else if (getDesiredAltitude() > 0)
setDesiredAltitude(getDesiredAltitude() - ftToM(10));
}
else if (change.compare("climb") == 0)
{
if (getDesiredAltitude() > 100)
setDesiredAltitude(getDesiredAltitude() + ftToM(100));
else if (getDesiredAltitude() >= 0)
setDesiredAltitude(getDesiredAltitude() + ftToM(10));
}
if (getDesiredAltitude() < 0)
setDesiredAltitude(0);
}

View File

@@ -0,0 +1,236 @@
#include "navyunit.h"
#include "utils.h"
#include "logger.h"
#include "commands.h"
#include "scheduler.h"
#include "defines.h"
#include "unitsManager.h"
#include <GeographicLib/Geodesic.hpp>
using namespace GeographicLib;
extern Scheduler* scheduler;
extern UnitsManager* unitsManager;
json::value NavyUnit::database = json::value();
extern string instancePath;
void NavyUnit::loadDatabase(string path) {
std::ifstream ifstream(instancePath + path);
std::stringstream ss;
ss << ifstream.rdbuf();
std::error_code errorCode;
database = json::value::parse(ss.str(), errorCode);
if (database.is_object())
log("NavyUnits database loaded correctly from " + instancePath + path);
else
log("Error reading NavyUnits database file");
}
/* Navy Unit */
NavyUnit::NavyUnit(json::value json, unsigned int ID) : Unit(json, ID)
{
log("New Navy Unit created with ID: " + to_string(ID));
setCategory("NavyUnit");
setDesiredSpeed(10);
};
void NavyUnit::setDefaults(bool force)
{
if (!getAlive() || !getControlled() || getHuman() || !getIsLeader()) return;
/* Set the default IDLE state */
setState(State::IDLE);
/* Set the default options */
setROE(ROE::WEAPON_FREE, force);
setOnOff(onOff, force);
setFollowRoads(followRoads, force);
}
void NavyUnit::setState(unsigned char newState)
{
Coords currentTargetPosition = getTargetPosition();
/************ Perform any action required when LEAVING a state ************/
if (newState != state) {
switch (state) {
case State::IDLE: {
break;
}
case State::REACH_DESTINATION: {
break;
}
case State::ATTACK: {
setTargetID(NULL);
break;
}
case State::FIRE_AT_AREA:
case State::SIMULATE_FIRE_FIGHT:
case State::SCENIC_AAA:
case State::MISS_ON_PURPOSE: {
setTargetPosition(Coords(NULL));
break;
}
default:
break;
}
}
/************ 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);
clearActivePath();
resetActiveDestination();
break;
}
case State::FIRE_AT_AREA: {
setTargetPosition(currentTargetPosition);
setEnableTaskCheckFailed(true);
clearActivePath();
resetActiveDestination();
break;
}
case State::SIMULATE_FIRE_FIGHT: {
setTargetPosition(currentTargetPosition);
setEnableTaskCheckFailed(false);
clearActivePath();
resetActiveDestination();
break;
}
case State::SCENIC_AAA: {
setEnableTaskCheckFailed(false);
clearActivePath();
resetActiveDestination();
break;
}
case State::MISS_ON_PURPOSE: {
setEnableTaskCheckFailed(false);
clearActivePath();
resetActiveDestination();
break;
}
default:
break;
}
setHasTask(false);
resetTaskFailedCounter();
log(unitName + " setting state from " + to_string(state) + " to " + to_string(newState));
state = newState;
triggerUpdate(DataIndex::state);
AIloop();
}
void NavyUnit::AIloop()
{
srand(static_cast<unsigned int>(time(NULL)) + ID);
switch (state) {
case State::IDLE: {
setTask("Idle");
if (getHasTask())
resetTask();
break;
}
case State::REACH_DESTINATION: {
string enrouteTask = "{}";
bool looping = false;
if (activeDestination == NULL || !getHasTask())
{
if (!setActiveDestination())
setState(State::IDLE);
else
goToDestination(enrouteTask);
}
else {
if (isDestinationReached(NAVY_DEST_DIST_THR)) {
if (updateActivePath(looping) && setActiveDestination())
goToDestination(enrouteTask);
else
setState(State::IDLE);
}
}
break;
}
case State::ATTACK: {
Unit* target = unitsManager->getUnit(getTargetID());
if (target != nullptr) {
setTask("Attacking " + target->getUnitName());
if (!getHasTask()) {
/* 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);
}
}
else {
setState(State::IDLE);
}
}
case State::FIRE_AT_AREA: {
setTask("Firing at area");
if (!getHasTask()) {
std::ostringstream taskSS;
taskSS.precision(10);
taskSS << "{id = 'FireAtPoint', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << ", radius = 1000}";
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str(), [this]() { this->setHasTaskAssigned(true); }));
scheduler->appendCommand(command);
setHasTask(true);
}
}
case State::SIMULATE_FIRE_FIGHT: {
setTask("Simulating fire fight");
// TODO
setState(State::IDLE);
}
default:
break;
}
}
void NavyUnit::changeSpeed(string change)
{
if (change.compare("stop") == 0)
setState(State::IDLE);
else if (change.compare("slow") == 0)
setDesiredSpeed(getDesiredSpeed() - knotsToMs(5));
else if (change.compare("fast") == 0)
setDesiredSpeed(getDesiredSpeed() + knotsToMs(5));
if (getDesiredSpeed() < 0)
setDesiredSpeed(0);
}
void NavyUnit::setOnOff(bool newOnOff, bool force)
{
if (newOnOff != onOff || force) {
Unit::setOnOff(newOnOff, force);
Command* command = dynamic_cast<Command*>(new SetOnOff(groupName, onOff));
scheduler->appendCommand(command);
}
}

View File

@@ -0,0 +1,691 @@
#include "scheduler.h"
#include "logger.h"
#include "dcstools.h"
#include "unitsManager.h"
#include "utils.h"
#include "unit.h"
extern UnitsManager* unitsManager;
Scheduler::Scheduler(lua_State* L)
{
LogInfo(L, "Scheduler constructor called successfully");
}
Scheduler::~Scheduler()
{
}
/* Appends a */
void Scheduler::appendCommand(Command* newCommand)
{
for (auto command : commands) {
if (command->getString().compare(newCommand->getString()) == 0 && command->getPriority() == newCommand->getPriority())
return;
}
commands.push_back(newCommand);
}
int Scheduler::getLoad()
{
int currentLoad = 0;
for (auto command : commands) {
currentLoad += command->getLoad();
}
return currentLoad;
}
void Scheduler::execute(lua_State* L)
{
/* Decrease the active computation load. New commands can be sent only if the load has reached 0.
This is needed to avoid server lag. */
if (load > 0) {
load--;
return;
}
int priority = CommandPriority::IMMEDIATE;
while (priority >= CommandPriority::LOW) {
for (auto command : commands)
{
if (command->getPriority() == priority)
{
string commandString = "Olympus.protectedCall(" + command->getString() + ")";
if (dostring_in(L, "server", (commandString)))
log("Error executing command " + commandString);
else
log("Command '" + commandString + "' executed correctly, current load " + to_string(getLoad()));
/* Adjust the load depending on the fps */
double fpsMultiplier = 20;
if (getFrameRate() + 3 > 0)
fpsMultiplier = static_cast<unsigned int>(max(1, 60 / (getFrameRate() + 3))); /* Multiplier between 1 and 20 */
load = static_cast<unsigned int>(command->getLoad() * fpsMultiplier);
commands.remove(command);
executedCommandsHashes.push_back(command->getHash());
command->executeCallback(); /* Execute the command callback (this is a lambda function that can be used to execute a function when the command is run) */
delete command;
return;
}
}
priority--;
};
}
void Scheduler::setCommandModeOptions(json::value value) {
if (value.has_boolean_field(L"restrictSpawns"))
setRestrictSpawns(value[L"restrictSpawns"].as_bool());
if (value.has_boolean_field(L"restrictToCoalition"))
setRestrictToCoalition(value[L"restrictToCoalition"].as_bool());
if (value.has_number_field(L"setupTime"))
setSetupTime(value[L"setupTime"].as_number().to_int32());
if (value.has_object_field(L"spawnPoints")) {
if (value[L"spawnPoints"].has_number_field(L"blue"))
setBlueSpawnPoints(value[L"spawnPoints"][L"blue"].as_number().to_int32());
if (value[L"spawnPoints"].has_number_field(L"red"))
setRedSpawnPoints(value[L"spawnPoints"][L"red"].as_number().to_int32());
}
if (value.has_array_field(L"eras")) {
int length = static_cast<int>(value[L"eras"].as_array().size());
vector<string> newEras;
for (int idx = 0; idx < length; idx++)
newEras.push_back(to_string(value[L"eras"].as_array().at(idx)));
setEras(newEras);
}
}
json::value Scheduler::getCommandModeOptions() {
json::value json = json::value::object();
json[L"restrictSpawns"] = json::value(getRestrictSpawns());
json[L"restrictToCoalition"] = json::value(getRestrictToCoalition());
json[L"setupTime"] = json::value(getSetupTime());
json[L"spawnPoints"] = json::value::object();
json[L"spawnPoints"][L"blue"] = json::value(getBlueSpawnPoints());
json[L"spawnPoints"][L"red"] = json::value(getRedSpawnPoints());
int idx = 0;
json[L"eras"] = json::value::array();
for (string era : getEras())
json[L"eras"][idx++] = json::value(to_wstring(era));
return json;
}
bool Scheduler::checkSpawnPoints(int spawnPoints, string coalition)
{
if (!getRestrictSpawns()) return true;
if (coalition.compare("blue") == 0) {
if (getBlueSpawnPoints() - spawnPoints >= 0) {
setBlueSpawnPoints(getBlueSpawnPoints() - spawnPoints);
return true;
}
else {
log("Not enough blue coalition spawn points available. Available: " + to_string(getBlueSpawnPoints()) + ", required: " + to_string(spawnPoints));
return false;
}
}
else if (coalition.compare("red") == 0) {
if (getRedSpawnPoints() - spawnPoints >= 0) {
setRedSpawnPoints(getRedSpawnPoints() - spawnPoints);
return true;
}
else {
log("Not enough red coalition spawn points available. Available: " + to_string(getRedSpawnPoints()) + ", required: " + to_string(spawnPoints));
return false;
}
}
return false;
}
void Scheduler::handleRequest(string key, json::value value, string username, json::value& answer)
{
Command* command = nullptr;
log("Received request with ID: " + key);
log(L"Incoming command raw value: " + value.serialize());
/************************/
if (key.compare("setPath") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr)
{
json::value path = value[L"path"];
list<Coords> newPath;
for (unsigned int i = 0; i < path.as_array().size(); i++)
{
string WP = to_string(i);
double lat = path[i][L"lat"].as_double();
double lng = path[i][L"lng"].as_double();
Coords dest; dest.lat = lat; dest.lng = lng;
newPath.push_back(dest);
}
unit->setActivePath(newPath);
unit->setState(State::REACH_DESTINATION);
log(username + " updated destination path for unit " + unit->getUnitName() + "(" + unit->getName() + ")", true);
}
}
/************************/
else if (key.compare("smoke") == 0)
{
string color = to_string(value[L"color"]);
double lat = value[L"location"][L"lat"].as_double();
double lng = value[L"location"][L"lng"].as_double();
Coords loc; loc.lat = lat; loc.lng = lng;
command = dynamic_cast<Command*>(new Smoke(color, loc));
log(username + " added a " + color + " smoke at (" + to_string(lat) + ", " + to_string(lng) + ")", true);
}
/************************/
else if (key.compare("spawnAircrafts") == 0 || key.compare("spawnHelicopters") == 0)
{
bool immediate = value[L"immediate"].as_bool();
string coalition = to_string(value[L"coalition"]);
string airbaseName = to_string(value[L"airbaseName"]);
string country = to_string(value[L"country"]);
int spawnPoints = value[L"spawnPoints"].as_number().to_int32();
if (!checkSpawnPoints(spawnPoints, coalition)) return;
vector<SpawnOptions> spawnOptions;
for (auto unit : value[L"units"].as_array()) {
string unitType = to_string(unit[L"unitType"]);
double lat = unit[L"location"][L"lat"].as_double();
double lng = unit[L"location"][L"lng"].as_double();
double alt = unit[L"altitude"].as_double();
Coords location; location.lat = lat; location.lng = lng; location.alt = alt;
string loadout = to_string(unit[L"loadout"]);
string liveryID = to_string(unit[L"liveryID"]);
spawnOptions.push_back({unitType, location, loadout, liveryID});
log(username + " spawned a " + coalition + " " + unitType, true);
}
if (key.compare("spawnAircrafts") == 0)
command = dynamic_cast<Command*>(new SpawnAircrafts(coalition, spawnOptions, airbaseName, country, immediate));
else
command = dynamic_cast<Command*>(new SpawnHelicopters(coalition, spawnOptions, airbaseName, country, immediate));
}
/************************/
else if (key.compare("spawnGroundUnits") == 0 || key.compare("spawnNavyUnits") == 0)
{
bool immediate = value[L"immediate"].as_bool();
string coalition = to_string(value[L"coalition"]);
string country = to_string(value[L"country"]);
int spawnPoints = value[L"spawnPoints"].as_number().to_int32();
if (!checkSpawnPoints(spawnPoints, coalition)) return;
vector<SpawnOptions> spawnOptions;
for (auto unit : value[L"units"].as_array()) {
string unitType = to_string(unit[L"unitType"]);
double lat = unit[L"location"][L"lat"].as_double();
double lng = unit[L"location"][L"lng"].as_double();
Coords location; location.lat = lat; location.lng = lng;
string liveryID = to_string(unit[L"liveryID"]);
spawnOptions.push_back({ unitType, location, "", liveryID });
log(username + " spawned a " + coalition + " " + unitType, true);
}
if (key.compare("spawnGroundUnits") == 0)
command = dynamic_cast<Command*>(new SpawnGroundUnits(coalition, spawnOptions, country, immediate));
else
command = dynamic_cast<Command*>(new SpawnNavyUnits(coalition, spawnOptions, country, immediate));
}
/************************/
else if (key.compare("attackUnit") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
unsigned int targetID = value[L"targetID"].as_integer();
Unit* unit = unitsManager->getGroupLeader(ID);
Unit* target = unitsManager->getUnit(targetID);
if (unit != nullptr && target != nullptr) {
log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to attack unit " + target->getUnitName() + "(" + target->getName() + ")", true);
unit->setTargetID(targetID);
unit->setState(State::ATTACK);
}
}
/************************/
else if (key.compare("followUnit") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
unsigned int leaderID = value[L"targetID"].as_integer();
double offsetX = value[L"offsetX"].as_double();
double offsetY = value[L"offsetY"].as_double();
double offsetZ = value[L"offsetZ"].as_double();
Unit* unit = unitsManager->getGroupLeader(ID);
Unit* leader = unitsManager->getUnit(leaderID);
if (unit != nullptr && leader != nullptr) {
log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to follow unit " + leader->getUnitName() + "(" + leader->getName() + ")", true);
unit->setFormationOffset(Offset(offsetX, offsetY, offsetZ));
unit->setLeaderID(leaderID);
unit->setState(State::FOLLOW);
}
}
/************************/
else if (key.compare("changeSpeed") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unit->changeSpeed(to_string(value[L"change"]));
log(username + " changed " + unit->getUnitName() + "(" + unit->getName() + ") speed: " + to_string(value[L"change"]), true);
}
}
/************************/
else if (key.compare("changeAltitude") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unit->changeAltitude(to_string(value[L"change"]));
log(username + " changed " + unit->getUnitName() + "(" + unit->getName() + ") altitude: " + to_string(value[L"change"]), true);
}
}
/************************/
else if (key.compare("setSpeed") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unit->setDesiredSpeed(value[L"speed"].as_double());
log(username + " set " + unit->getUnitName() + "(" + unit->getName() + ") speed: " + to_string(value[L"speed"].as_double()), true);
}
}
/************************/
else if (key.compare("setSpeedType") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unit->setDesiredSpeedType(to_string(value[L"speedType"]));
log(username + " set " + unit->getUnitName() + "(" + unit->getName() + ") speed type: " + to_string(value[L"speedType"]), true);
}
}
/************************/
else if (key.compare("setAltitude") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unit->setDesiredAltitude(value[L"altitude"].as_double());
log(username + " set " + unit->getUnitName() + "(" + unit->getName() + ") altitude: " + to_string(value[L"altitude"].as_double()), true);
}
}
/************************/
else if (key.compare("setAltitudeType") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unit->setDesiredAltitudeType(to_string(value[L"altitudeType"]));
log(username + " set " + unit->getUnitName() + "(" + unit->getName() + ") altitude type: " + to_string(value[L"altitudeType"]), true);
}
}
/************************/
else if (key.compare("cloneUnits") == 0)
{
vector<CloneOptions> cloneOptions;
bool deleteOriginal = value[L"deleteOriginal"].as_bool();
for (auto unit : value[L"units"].as_array()) {
unsigned int ID = unit[L"ID"].as_integer();
double lat = unit[L"location"][L"lat"].as_double();
double lng = unit[L"location"][L"lng"].as_double();
Coords location; location.lat = lat; location.lng = lng;
cloneOptions.push_back({ ID, location });
log(username + " cloning unit with ID " + to_string(ID), true);
}
command = dynamic_cast<Command*>(new Clone(cloneOptions, deleteOriginal));
}
/************************/
else if (key.compare("setROE") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unsigned char ROE = value[L"ROE"].as_number().to_uint32();
unit->setROE(ROE);
log(username + " set unit " + unit->getUnitName() + "(" + unit->getName() + ") ROE to " + to_string(ROE), true);
}
}
/************************/
else if (key.compare("setReactionToThreat") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unsigned char reactionToThreat = value[L"reactionToThreat"].as_number().to_uint32();
unit->setReactionToThreat(reactionToThreat);
log(username + " set unit " + unit->getUnitName() + "(" + unit->getName() + ") reaction to threat to " + to_string(reactionToThreat), true);
}
}
/************************/
else if (key.compare("setEmissionsCountermeasures") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unsigned char emissionsCountermeasures = value[L"emissionsCountermeasures"].as_number().to_uint32();
unit->setEmissionsCountermeasures(emissionsCountermeasures);
log(username + " set unit " + unit->getUnitName() + "(" + unit->getName() + ") emissions and countermeasures to " + to_string(emissionsCountermeasures), true);
}
}
/************************/
else if (key.compare("landAt") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
double lat = value[L"location"][L"lat"].as_double();
double lng = value[L"location"][L"lng"].as_double();
Coords loc; loc.lat = lat; loc.lng = lng;
unit->landAt(loc);
log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to land", true);
}
}
/************************/
else if (key.compare("deleteUnit") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
bool explosion = value[L"explosion"].as_bool();
string explosionType = to_string(value[L"explosionType"]);
bool immediate = value[L"immediate"].as_bool();
Unit* unit = unitsManager->getUnit(ID);
if (unit != nullptr) {
unitsManager->deleteUnit(ID, explosion, explosionType, immediate);
log(username + " deleted unit " + unit->getUnitName() + "(" + unit->getName() + ")", true);
}
}
/************************/
else if (key.compare("refuel") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unit->setState(State::REFUEL);
log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to refuel", true);
}
}
/************************/
else if (key.compare("setAdvancedOptions") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr)
{
/* Advanced tasking */
unit->setIsActiveTanker(value[L"isActiveTanker"].as_bool());
unit->setIsActiveAWACS(value[L"isActiveAWACS"].as_bool());
/* TACAN Options */
DataTypes::TACAN TACAN;
TACAN.isOn = value[L"TACAN"][L"isOn"].as_bool();
TACAN.channel = static_cast<unsigned char>(value[L"TACAN"][L"channel"].as_number().to_uint32());
TACAN.XY = to_string(value[L"TACAN"][L"XY"]).at(0);
string callsign = to_string(value[L"TACAN"][L"callsign"]);
if (callsign.length() > 3)
callsign = callsign.substr(0, 3);
strcpy_s(TACAN.callsign, 4, callsign.c_str());
unit->setTACAN(TACAN);
/* Radio Options */
auto radio = value[L"radio"];
unit->setRadio({ radio[L"frequency"].as_number().to_uint32(),
static_cast<unsigned char>(radio[L"callsign"].as_number().to_uint32()),
static_cast<unsigned char>(radio[L"callsignNumber"].as_number().to_uint32())
});
/* General Settings */
auto generalSettings = value[L"generalSettings"];
unit->setGeneralSettings({ generalSettings[L"prohibitJettison"].as_bool(),
generalSettings[L"prohibitAA"].as_bool(),
generalSettings[L"prohibitAG"].as_bool(),
generalSettings[L"prohibitAfterburner"].as_bool(),
generalSettings[L"prohibitAirWpn"].as_bool(),
});
unit->resetActiveDestination();
log(username + " updated unit " + unit->getUnitName() + "(" + unit->getName() + ") advancedOptions", true);
}
}
/************************/
else if (key.compare("setFollowRoads") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
bool followRoads = value[L"followRoads"].as_bool();
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unit->setFollowRoads(followRoads);
log(username + " set unit " + unit->getUnitName() + "(" + unit->getName() + ") followRoads to: " + (followRoads ? "true" : "false"), true);
}
}
/************************/
else if (key.compare("setOnOff") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
bool onOff = value[L"onOff"].as_bool();
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unit->setOnOff(onOff);
log(username + " set unit " + unit->getUnitName() + "(" + unit->getName() + ") onOff to: " + (onOff? "true": "false"), true);
}
}
/************************/
else if (key.compare("explosion") == 0)
{
unsigned int intensity = value[L"intensity"].as_integer();
string explosionType = to_string(value[L"explosionType"]);
double lat = value[L"location"][L"lat"].as_double();
double lng = value[L"location"][L"lng"].as_double();
log("Adding explosion of type " + explosionType + " at (" + to_string(lat) + ", " + to_string(lng) + ")");
Coords loc; loc.lat = lat; loc.lng = lng;
command = dynamic_cast<Command*>(new Explosion(intensity, explosionType, loc));
}
/************************/
else if (key.compare("bombPoint") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
double lat = value[L"location"][L"lat"].as_double();
double lng = value[L"location"][L"lng"].as_double();
Coords loc; loc.lat = lat; loc.lng = lng;
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unit->setTargetPosition(loc);
unit->setState(State::BOMB_POINT);
log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to bomb a point", true);
}
}
/************************/
else if (key.compare("carpetBomb") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
double lat = value[L"location"][L"lat"].as_double();
double lng = value[L"location"][L"lng"].as_double();
Coords loc; loc.lat = lat; loc.lng = lng;
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unit->setTargetPosition(loc);
unit->setState(State::CARPET_BOMB);
log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to perform carpet bombing", true);
}
}
/************************/
/* TODO: this command does not appear to be working in DCS and has been disabled */
else if (key.compare("bombBuilding") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
double lat = value[L"location"][L"lat"].as_double();
double lng = value[L"location"][L"lng"].as_double();
Coords loc; loc.lat = lat; loc.lng = lng;
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unit->setTargetPosition(loc);
unit->setState(State::BOMB_BUILDING);
}
}
/************************/
else if (key.compare("fireAtArea") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
double lat = value[L"location"][L"lat"].as_double();
double lng = value[L"location"][L"lng"].as_double();
Coords loc; loc.lat = lat; loc.lng = lng;
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unit->setTargetPosition(loc);
unit->setState(State::FIRE_AT_AREA);
log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to fire at area", true);
}
}
/************************/
else if (key.compare("simulateFireFight") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
double lat = value[L"location"][L"lat"].as_double();
double lng = value[L"location"][L"lng"].as_double();
double alt = value[L"altitude"].as_double();
Coords loc; loc.lat = lat; loc.lng = lng; loc.alt = alt;
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unit->setTargetPosition(loc);
unit->setState(State::SIMULATE_FIRE_FIGHT);
log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to simulate a fire fight", true);
}
}
/************************/
else if (key.compare("scenicAAA") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unit->setState(State::SCENIC_AAA);
log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to enter scenic AAA state", true);
}
}
/************************/
else if (key.compare("missOnPurpose") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unit->setState(State::MISS_ON_PURPOSE);
log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to enter Miss On Purpose state", true);
}
}
/************************/
else if (key.compare("setOperateAs") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
unsigned char operateAs = value[L"operateAs"].as_number().to_uint32();
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr)
unit->setOperateAs(operateAs);
}
/************************/
else if (key.compare("landAtPoint") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
double lat = value[L"location"][L"lat"].as_double();
double lng = value[L"location"][L"lng"].as_double();
Coords loc; loc.lat = lat; loc.lng = lng;
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
list<Coords> newPath;
newPath.push_back(loc);
unit->setActivePath(newPath);
unit->setState(State::LAND_AT_POINT);
log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to land at point", true);
}
}
/************************/
else if (key.compare("setShotsScatter") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unsigned char shotsScatter = value[L"shotsScatter"].as_number().to_uint32();
unit->setShotsScatter(shotsScatter);
log(username + " set unit " + unit->getUnitName() + "(" + unit->getName() + ") shots scatter to " + to_string(shotsScatter), true);
}
}
/************************/
else if (key.compare("setShotsIntensity") == 0)
{
unsigned int ID = value[L"ID"].as_integer();
unitsManager->acquireControl(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr) {
unsigned char shotsIntensity = value[L"shotsIntensity"].as_number().to_uint32();
unit->setShotsIntensity(shotsIntensity);
log(username + " set unit " + unit->getUnitName() + "(" + unit->getName() + ") shots intensity to " + to_string(shotsIntensity), true);
}
}
/************************/
else if (key.compare("setCommandModeOptions") == 0)
{
setCommandModeOptions(value);
log(username + " updated the Command Mode Options", true);
}
/************************/
else if (key.compare("reloadDatabases") == 0) {
unitsManager->loadDatabases();
}
/************************/
else
{
log("Unknown command: " + key);
}
if (command != nullptr)
{
appendCommand(command);
log("New command appended correctly to stack. Current server load: " + to_string(getLoad()));
answer[L"commandHash"] = json::value(to_wstring(command->getHash()));
}
}

View File

@@ -0,0 +1,47 @@
#include "scriptLoader.h"
#include "logger.h"
#include "luatools.h"
#include "dcstools.h"
#include "defines.h"
#include <algorithm>
extern string instancePath;
bool executeLuaScript(lua_State* L, string path)
{
replace(path.begin(), path.end(), '\\', '/');
// Encase the loading call in a procted call to catch any syntax errors
string str = "Olympus.protectedCall(dofile, \"" + path + "\")";
if (dostring_in(L, "server", str.c_str()) != 0)
{
log("Error registering " + path);
return false;
}
else
{
log(path + " registered successfully");
return true;
}
}
/* Executes the "OlympusCommand.lua" file to load in the "Server" Lua space all the Lua functions necessary to perform DCS commands (like moving units) */
void registerLuaFunctions(lua_State* L)
{
string modLocation;
if (dostring_in(L, "server", PROTECTED_CALL))
{
log("Error registering protectedCall");
}
else
{
log("protectedCall registered successfully");
}
executeLuaScript(L, instancePath + MIST_SCRIPT);
executeLuaScript(L, instancePath + OLYMPUS_COMMAND_SCRIPT);
executeLuaScript(L, instancePath + UNIT_PAYLOADS_SCRIPT);
executeLuaScript(L, instancePath + TEMPLATES_SCRIPT);
executeLuaScript(L, instancePath + MODS_SCRIPT);
}

345
backend/core/src/server.cpp Normal file
View File

@@ -0,0 +1,345 @@
#include "server.h"
#include "logger.h"
#include "defines.h"
#include "unitsManager.h"
#include "weaponsManager.h"
#include "scheduler.h"
#include "luatools.h"
#include <exception>
#include <stdexcept>
#include "base64.hpp"
#include <chrono>
using namespace std::chrono;
using namespace base64;
extern UnitsManager* unitsManager;
extern WeaponsManager* weaponsManager;
extern Scheduler* scheduler;
extern json::value missionData;
extern mutex mutexLock;
extern string sessionHash;
extern string instancePath;
void handle_eptr(std::exception_ptr eptr)
{
try {
if (eptr) {
std::rethrow_exception(eptr);
}
}
catch (const std::exception& e) {
log(e.what());
}
}
Server::Server(lua_State* L):
serverThread(nullptr),
runListener(true)
{
}
void Server::start(lua_State* L)
{
log("Starting RESTServer");
serverThread = new thread(&Server::task, this);
}
void Server::stop(lua_State* L)
{
log("Stopping RESTServer");
runListener = false;
if (serverThread != nullptr)
serverThread->join();
}
void Server::handle_options(http_request request)
{
http_response response(status_codes::OK);
response.headers().add(U("Allow"), U("GET, PUT, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
response.headers().add(U("Access-Control-Allow-Methods"), U("GET, PUT, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type, Authorization"));
request.reply(response);
}
void Server::handle_get(http_request request)
{
/* Lock for thread safety */
lock_guard<mutex> guard(mutexLock);
milliseconds ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
http_response response(status_codes::OK);
string password = extractPassword(request);
if (password.compare(gameMasterPassword) == 0 || password.compare(blueCommanderPassword) == 0 || password.compare(redCommanderPassword) == 0)
{
std::exception_ptr eptr;
try {
auto answer = json::value::object();
auto path = uri::split_path(uri::decode(request.relative_uri().path()));
/* If present, extract the request reference time. This is used for updates, and it specifies the last time that request has been performed */
map<utility::string_t, utility::string_t> query = request.relative_uri().split_query(request.relative_uri().query());
unsigned long long time = 0;
if (query.find(L"time") != query.end())
{
try {
time = stoull((*(query.find(L"time"))).second);
}
catch (...) {
time = 0;
}
}
if (path.size() > 0)
{
string URI = to_string(path[0]);
/* Units data */
if (URI.compare(UNITS_URI) == 0)
{
unsigned long long updateTime = ms.count();
stringstream ss;
ss.write((char*)&updateTime, sizeof(updateTime));
unitsManager->getUnitData(ss, time);
response.set_body(concurrency::streams::bytestream::open_istream(ss.str()));
}
else if (URI.compare(WEAPONS_URI) == 0)
{
unsigned long long updateTime = ms.count();
stringstream ss;
ss.write((char*)&updateTime, sizeof(updateTime));
weaponsManager->getWeaponData(ss, time);
response.set_body(concurrency::streams::bytestream::open_istream(ss.str()));
}
else {
/* Logs data */
if (URI.compare(LOGS_URI) == 0)
{
auto logs = json::value::object();
getLogsJSON(logs, time);
answer[L"logs"] = logs;
}
/* Airbases data */
else if (URI.compare(AIRBASES_URI) == 0 && missionData.has_object_field(L"airbases"))
answer[L"airbases"] = missionData[L"airbases"];
/* Bullseyes data */
else if (URI.compare(BULLSEYE_URI) == 0 && missionData.has_object_field(L"bullseyes"))
answer[L"bullseyes"] = missionData[L"bullseyes"];
/* Mission data */
else if (URI.compare(MISSION_URI) == 0 && missionData.has_object_field(L"mission")) {
answer[L"mission"] = missionData[L"mission"];
answer[L"mission"][L"commandModeOptions"] = scheduler->getCommandModeOptions();
/* The active mode is determined by the inserted password*/
if (password.compare(gameMasterPassword) == 0)
answer[L"mission"][L"commandModeOptions"][L"commandMode"] = json::value(L"Game master");
else if (password.compare(blueCommanderPassword) == 0)
answer[L"mission"][L"commandModeOptions"][L"commandMode"] = json::value(L"Blue commander");
else if (password.compare(redCommanderPassword) == 0)
answer[L"mission"][L"commandModeOptions"][L"commandMode"] = json::value(L"Red commander");
else
answer[L"mission"][L"commandModeOptions"][L"commandMode"] = json::value(L"Observer");
}
else if (URI.compare(COMMANDS_URI) == 0 && query.find(L"commandHash") != query.end()) {
answer[L"commandExecuted"] = json::value(scheduler->isCommandExecuted(to_string(query[L"commandHash"])));
}
/* Common data */
answer[L"time"] = json::value::string(to_wstring(ms.count()));
answer[L"sessionHash"] = json::value::string(to_wstring(sessionHash));
answer[L"load"] = scheduler->getLoad();
answer[L"frameRate"] = scheduler->getFrameRate();
response.set_body(answer);
}
}
}
catch (...) {
eptr = std::current_exception(); // capture
}
handle_eptr(eptr);
}
else {
response = status_codes::Unauthorized;
}
response.headers().add(U("Allow"), U("GET, PUT, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
response.headers().add(U("Access-Control-Allow-Methods"), U("GET, PUT, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type, Authorization"));
request.reply(response);
}
void Server::handle_request(http_request request, function<void(json::value const&, json::value&)> action)
{
http_response response(status_codes::OK);
//TODO: limit what a user can do depending on the password
string password = extractPassword(request);
if (password.compare(gameMasterPassword) == 0 || password.compare(blueCommanderPassword) == 0 || password.compare(redCommanderPassword) == 0)
{
auto answer = json::value::object();
request.extract_json().then([&answer, &action](pplx::task<json::value> task)
{
try
{
auto const& jvalue = task.get();
if (!jvalue.is_null())
action(jvalue, answer);
}
catch (http_exception const& e)
{
log(e.what());
}
}).wait();
response.set_body(answer);
}
else {
response = status_codes::Unauthorized;
}
response.headers().add(U("Allow"), U("GET, PUT, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Origin"), U("*"));
response.headers().add(U("Access-Control-Allow-Methods"), U("GET, PUT, OPTIONS"));
response.headers().add(U("Access-Control-Allow-Headers"), U("Content-Type, Authorization"));
request.reply(response);
}
void Server::handle_put(http_request request)
{
string username = extractUsername(request);
handle_request(
request,
[username](json::value const& jvalue, json::value& answer)
{
/* Lock for thread safety */
lock_guard<mutex> guard(mutexLock);
for (auto const& e : jvalue.as_object())
{
auto key = e.first;
auto value = e.second;
std::exception_ptr eptr;
try {
scheduler->handleRequest(to_string(key), value, username, answer);
}
catch (...) {
eptr = std::current_exception(); // capture
}
handle_eptr(eptr);
}
});
}
string Server::extractUsername(http_request& request) {
if (request.headers().has(L"Authorization")) {
string authorization = to_string(request.headers().find(L"Authorization")->second);
string s = "Basic ";
string::size_type i = authorization.find(s);
if (i != std::string::npos)
authorization.erase(i, s.length());
else
return "";
string decoded = from_base64(authorization);
i = decoded.find(":");
if (i != string::npos && i <= decoded.length())
decoded.erase(i, decoded.length() - i);
else
return "";
return decoded;
}
else
return "";
}
string Server::extractPassword(http_request& request) {
if (request.headers().has(L"Authorization")) {
string authorization = to_string(request.headers().find(L"Authorization")->second);
string s = "Basic ";
string::size_type i = authorization.find(s);
if (i != std::string::npos)
authorization.erase(i, s.length());
else
return "";
string decoded = from_base64(authorization);
i = decoded.find(":");
if (i != string::npos && i+1 < decoded.length())
decoded.erase(0, i+1);
else
return "";
return decoded;
}
else
return "";
}
void Server::task()
{
string address = REST_ADDRESS;
string jsonLocation = instancePath + OLYMPUS_JSON_PATH;
log("Reading configuration from " + jsonLocation);
std::ifstream ifstream(jsonLocation);
std::stringstream ss;
ss << ifstream.rdbuf();
std::error_code errorCode;
json::value config = json::value::parse(ss.str(), errorCode);
if (config.is_object() && config.has_object_field(L"server") &&
config[L"server"].has_string_field(L"address") && config[L"server"].has_number_field(L"port"))
{
address = "http://" + to_string(config[L"server"][L"address"]) + ":" + to_string(config[L"server"][L"port"].as_number().to_int32());
log("Starting server on " + address);
}
else
log("Error reading configuration file. Starting server on " + address);
if (config.is_object() && config.has_object_field(L"authentication"))
{
if (config[L"authentication"].has_string_field(L"gameMasterPassword")) gameMasterPassword = to_string(config[L"authentication"][L"gameMasterPassword"]);
if (config[L"authentication"].has_string_field(L"blueCommanderPassword")) blueCommanderPassword = to_string(config[L"authentication"][L"blueCommanderPassword"]);
if (config[L"authentication"].has_string_field(L"redCommanderPassword")) redCommanderPassword = to_string(config[L"authentication"][L"redCommanderPassword"]);
}
else
log("Error reading configuration file. No password set.");
http_listener listener(to_wstring(address + "/" + REST_URI));
std::function<void(http_request)> handle_options = std::bind(&Server::handle_options, this, std::placeholders::_1);
std::function<void(http_request)> handle_get = std::bind(&Server::handle_get, this, std::placeholders::_1);
std::function<void(http_request)> handle_put = std::bind(&Server::handle_put, this, std::placeholders::_1);
listener.support(methods::OPTIONS, handle_options);
listener.support(methods::GET, handle_get);
listener.support(methods::PUT, handle_put);
try
{
listener.open()
.then([&listener]() {log("RESTServer starting to listen"); })
.wait();
while (runListener) { Sleep(1000); };
listener.close()
.then([&listener]() {log("RESTServer stopping connections"); })
.wait();
log("RESTServer stopped listening");
}
catch (exception const& e)
{
log(e.what());
}
}

810
backend/core/src/unit.cpp Normal file
View File

@@ -0,0 +1,810 @@
#include "unit.h"
#include "utils.h"
#include "logger.h"
#include "commands.h"
#include "scheduler.h"
#include "defines.h"
#include "unitsmanager.h"
#include <chrono>
using namespace std::chrono;
#include <GeographicLib/Geodesic.hpp>
using namespace GeographicLib;
extern Scheduler* scheduler;
extern UnitsManager* unitsManager;
Unit::Unit(json::value json, unsigned int ID) :
ID(ID)
{
log("Creating unit with ID: " + to_string(ID));
}
Unit::~Unit()
{
}
void Unit::initialize(json::value json)
{
if (json.has_string_field(L"name"))
setName(to_string(json[L"name"]));
if (json.has_string_field(L"unitName"))
setUnitName(to_string(json[L"unitName"]));
if (json.has_string_field(L"groupName"))
setGroupName(to_string(json[L"groupName"]));
if (json.has_number_field(L"coalitionID"))
setCoalition(json[L"coalitionID"].as_number().to_int32());
//if (json.has_number_field(L"Country"))
// setCountry(json[L"Country"].as_number().to_int32());
/* All units which contain the name "Olympus" are automatically under AI control */
if (getUnitName().find("Olympus") != string::npos)
setControlled(true);
update(json, 0);
setDefaults();
}
void Unit::update(json::value json, double dt)
{
if (json.has_object_field(L"position"))
{
setPosition({
json[L"position"][L"lat"].as_number().to_double(),
json[L"position"][L"lng"].as_number().to_double(),
json[L"position"][L"alt"].as_number().to_double()
});
}
if (json.has_number_field(L"heading"))
setHeading(json[L"heading"].as_number().to_double());
if (json.has_number_field(L"track"))
setTrack(json[L"track"].as_number().to_double());
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());
if (json.has_boolean_field(L"isHuman"))
setHuman(json[L"isHuman"].as_bool());
if (json.has_number_field(L"fuel")) {
setFuel(short(json[L"fuel"].as_number().to_double() * 100));
}
if (json.has_object_field(L"ammo")) {
vector<DataTypes::Ammo> ammo;
for (auto const& el : json[L"ammo"].as_object()) {
DataTypes::Ammo ammoItem;
auto ammoJson = el.second;
if (ammoJson.has_number_field(L"count"))
ammoItem.quantity = ammoJson[L"count"].as_number().to_uint32();
if (ammoJson.has_object_field(L"desc")) {
if (ammoJson[L"desc"].has_string_field(L"displayName")) {
string name = to_string(ammoJson[L"desc"][L"displayName"].as_string());
name = name.substr(0, min(name.size(), sizeof(ammoItem.name) - 1));
strcpy_s(ammoItem.name, sizeof(ammoItem.name), name.c_str());
}
if (ammoJson[L"desc"].has_number_field(L"guidance"))
ammoItem.guidance = ammoJson[L"desc"][L"guidance"].as_number().to_uint32();
if (ammoJson[L"desc"].has_number_field(L"category"))
ammoItem.category = ammoJson[L"desc"][L"category"].as_number().to_uint32();
if (ammoJson[L"desc"].has_number_field(L"missileCategory"))
ammoItem.missileCategory = ammoJson[L"desc"][L"missileCategory"].as_number().to_uint32();
}
ammo.push_back(ammoItem);
}
setAmmo(ammo);
}
if (json.has_object_field(L"contacts")) {
vector<DataTypes::Contact> contacts;
for (auto const& el : json[L"contacts"].as_object()) {
DataTypes::Contact contactItem;
auto contactJson = el.second;
contactItem.ID = contactJson[L"object"][L"id_"].as_number().to_uint32();
string detectionMethod = to_string(contactJson[L"detectionMethod"]);
if (detectionMethod.compare("VISUAL") == 0) contactItem.detectionMethod = 1;
else if (detectionMethod.compare("OPTIC") == 0) contactItem.detectionMethod = 2;
else if (detectionMethod.compare("RADAR") == 0) contactItem.detectionMethod = 4;
else if (detectionMethod.compare("IRST") == 0) contactItem.detectionMethod = 8;
else if (detectionMethod.compare("RWR") == 0) contactItem.detectionMethod = 16;
else if (detectionMethod.compare("DLINK") == 0) contactItem.detectionMethod = 32;
contacts.push_back(contactItem);
}
setContacts(contacts);
}
if (json.has_boolean_field(L"hasTask"))
setHasTask(json[L"hasTask"].as_bool());
if (json.has_number_field(L"health"))
setHealth(static_cast<unsigned char>(json[L"health"].as_number().to_uint32()));
runAILoop();
}
void Unit::setDefaults(bool force)
{
}
void Unit::runAILoop() {
/* Set isLeader */
Unit* leader = nullptr;
setIsLeader(unitsManager->isUnitGroupLeader(this, leader));
/* 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 (getEnableTaskCheckFailed() && checkTaskFailed()) {
log(unitName + " has no task, switching to IDLE state");
setState(State::IDLE);
}
AIloop();
}
refreshLeaderData(lastLoopTime);
milliseconds ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
lastLoopTime = ms.count();
}
void Unit::refreshLeaderData(unsigned long long time) {
/* When units are in a group, most data comes from the group leader. If new data is available, align it from the leader */
if (!getIsLeader()) {
Unit* leader = unitsManager->getGroupLeader(this);
if (leader != nullptr) {
for (unsigned char datumIndex = DataIndex::startOfData + 1; datumIndex < DataIndex::lastIndex; datumIndex++)
{
if (leader->checkFreshness(datumIndex, time)) {
switch (datumIndex) {
case DataIndex::controlled: updateValue(controlled, leader->controlled, datumIndex); break;
case DataIndex::state: updateValue(state, leader->state, datumIndex); break;
case DataIndex::task: updateValue(task, leader->task, datumIndex); break;
case DataIndex::hasTask: updateValue(hasTask, leader->hasTask, datumIndex); break;
case DataIndex::isActiveTanker: updateValue(isActiveTanker, leader->isActiveTanker, datumIndex); break;
case DataIndex::isActiveAWACS: updateValue(isActiveAWACS, leader->isActiveAWACS, datumIndex); break;
case DataIndex::onOff: updateValue(onOff, leader->onOff, datumIndex); break;
case DataIndex::followRoads: updateValue(followRoads, leader->followRoads, datumIndex); break;
case DataIndex::desiredSpeed: updateValue(desiredSpeed, leader->desiredSpeed, datumIndex); break;
case DataIndex::desiredSpeedType: updateValue(desiredSpeedType, leader->desiredSpeedType, datumIndex); break;
case DataIndex::desiredAltitude: updateValue(desiredAltitude, leader->desiredAltitude, datumIndex); break;
case DataIndex::desiredAltitudeType: updateValue(desiredAltitudeType, leader->desiredAltitudeType, datumIndex); break;
case DataIndex::leaderID: updateValue(leaderID, leader->leaderID, datumIndex); break;
case DataIndex::formationOffset: updateValue(formationOffset, leader->formationOffset, datumIndex); break;
case DataIndex::targetID: updateValue(targetID, leader->targetID, datumIndex); break;
case DataIndex::targetPosition: updateValue(targetPosition, leader->targetPosition, datumIndex); break;
case DataIndex::ROE: updateValue(ROE, leader->ROE, datumIndex); break;
case DataIndex::reactionToThreat: updateValue(reactionToThreat, leader->reactionToThreat, datumIndex); break;
case DataIndex::emissionsCountermeasures: updateValue(emissionsCountermeasures, leader->emissionsCountermeasures, datumIndex); break;
case DataIndex::TACAN: updateValue(TACAN, leader->TACAN, datumIndex); break;
case DataIndex::radio: updateValue(radio, leader->radio, datumIndex); break;
case DataIndex::generalSettings: updateValue(generalSettings, leader->generalSettings, datumIndex); break;
case DataIndex::activePath: updateValue(activePath, leader->activePath, datumIndex); break;
case DataIndex::operateAs: updateValue(operateAs, leader->operateAs, datumIndex); break;
case DataIndex::shotsScatter: updateValue(shotsScatter, leader->shotsScatter, datumIndex); break;
case DataIndex::shotsIntensity: updateValue(shotsIntensity, leader->shotsIntensity, datumIndex); break;
}
}
}
}
}
}
bool Unit::checkFreshness(unsigned char datumIndex, unsigned long long time) {
auto it = updateTimeMap.find(datumIndex);
if (it == updateTimeMap.end())
return false;
else
return it->second > time;
}
bool Unit::hasFreshData(unsigned long long time) {
for (auto it : updateTimeMap)
if (it.second > time)
return true;
return false;
}
void Unit::getData(stringstream& ss, unsigned long long time)
{
/* When an update is requested, make sure data is refreshed */
if (time == 0)
refreshLeaderData(0);
const unsigned char endOfData = DataIndex::endOfData;
ss.write((const char*)&ID, sizeof(ID));
if (!alive && time == 0) {
unsigned char datumIndex = DataIndex::category;
appendString(ss, datumIndex, category);
datumIndex = DataIndex::alive;
appendNumeric(ss, datumIndex, alive);
}
else {
for (unsigned char datumIndex = DataIndex::startOfData + 1; datumIndex < DataIndex::lastIndex; datumIndex++)
{
if (checkFreshness(datumIndex, time)) {
switch (datumIndex) {
case DataIndex::category: appendString(ss, datumIndex, category); break;
case DataIndex::alive: appendNumeric(ss, datumIndex, alive); break;
case DataIndex::human: appendNumeric(ss, datumIndex, human); break;
case DataIndex::controlled: appendNumeric(ss, datumIndex, controlled); break;
case DataIndex::coalition: appendNumeric(ss, datumIndex, coalition); break;
case DataIndex::country: appendNumeric(ss, datumIndex, country); break;
case DataIndex::name: appendString(ss, datumIndex, name); break;
case DataIndex::unitName: appendString(ss, datumIndex, unitName); break;
case DataIndex::groupName: appendString(ss, datumIndex, groupName); break;
case DataIndex::state: appendNumeric(ss, datumIndex, state); break;
case DataIndex::task: appendString(ss, datumIndex, task); break;
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::track: appendNumeric(ss, datumIndex, track); break;
case DataIndex::isActiveTanker: appendNumeric(ss, datumIndex, isActiveTanker); break;
case DataIndex::isActiveAWACS: appendNumeric(ss, datumIndex, isActiveAWACS); break;
case DataIndex::onOff: appendNumeric(ss, datumIndex, onOff); break;
case DataIndex::followRoads: appendNumeric(ss, datumIndex, followRoads); break;
case DataIndex::fuel: appendNumeric(ss, datumIndex, fuel); break;
case DataIndex::desiredSpeed: appendNumeric(ss, datumIndex, desiredSpeed); break;
case DataIndex::desiredSpeedType: appendNumeric(ss, datumIndex, desiredSpeedType); break;
case DataIndex::desiredAltitude: appendNumeric(ss, datumIndex, desiredAltitude); break;
case DataIndex::desiredAltitudeType: appendNumeric(ss, datumIndex, desiredAltitudeType); break;
case DataIndex::leaderID: appendNumeric(ss, datumIndex, leaderID); break;
case DataIndex::formationOffset: appendNumeric(ss, datumIndex, formationOffset); break;
case DataIndex::targetID: appendNumeric(ss, datumIndex, targetID); break;
case DataIndex::targetPosition: appendNumeric(ss, datumIndex, targetPosition); break;
case DataIndex::ROE: appendNumeric(ss, datumIndex, ROE); break;
case DataIndex::reactionToThreat: appendNumeric(ss, datumIndex, reactionToThreat); break;
case DataIndex::emissionsCountermeasures: appendNumeric(ss, datumIndex, emissionsCountermeasures); break;
case DataIndex::TACAN: appendNumeric(ss, datumIndex, TACAN); break;
case DataIndex::radio: appendNumeric(ss, datumIndex, radio); break;
case DataIndex::generalSettings: appendNumeric(ss, datumIndex, generalSettings); break;
case DataIndex::ammo: appendVector(ss, datumIndex, ammo); break;
case DataIndex::contacts: appendVector(ss, datumIndex, contacts); break;
case DataIndex::activePath: appendList(ss, datumIndex, activePath); break;
case DataIndex::isLeader: appendNumeric(ss, datumIndex, isLeader); break;
case DataIndex::operateAs: appendNumeric(ss, datumIndex, operateAs); break;
case DataIndex::shotsScatter: appendNumeric(ss, datumIndex, shotsScatter); break;
case DataIndex::shotsIntensity: appendNumeric(ss, datumIndex, shotsIntensity); break;
case DataIndex::health: appendNumeric(ss, datumIndex, health); break;
}
}
}
}
ss.write((const char*)&endOfData, sizeof(endOfData));
}
void Unit::setAmmo(vector<DataTypes::Ammo> newValue)
{
if (ammo.size() == newValue.size()) {
bool equal = true;
for (int i = 0; i < ammo.size(); i++) {
if (ammo.at(i) != newValue.at(i))
{
equal = false;
break;
}
}
if (equal)
return;
}
ammo = newValue;
triggerUpdate(DataIndex::ammo);
}
void Unit::setContacts(vector<DataTypes::Contact> newValue)
{
if (contacts.size() == newValue.size()) {
bool equal = true;
for (int i = 0; i < contacts.size(); i++) {
if (contacts.at(i) != newValue.at(i))
{
equal = false;
break;
}
}
if (equal)
return;
}
contacts = newValue;
triggerUpdate(DataIndex::contacts);
}
void Unit::setActivePath(list<Coords> newPath)
{
activePath = newPath;
resetActiveDestination();
}
void Unit::clearActivePath()
{
list<Coords> newPath;
setActivePath(newPath);
}
void Unit::pushActivePathFront(Coords newActivePathFront)
{
list<Coords> path = activePath;
path.push_front(newActivePathFront);
setActivePath(path);
}
void Unit::pushActivePathBack(Coords newActivePathBack)
{
list<Coords> path = activePath;
path.push_back(newActivePathBack);
setActivePath(path);
}
void Unit::popActivePathFront()
{
list<Coords> path = activePath;
path.pop_front();
setActivePath(path);
}
string Unit::getTargetName()
{
if (isTargetAlive())
{
Unit* target = unitsManager->getUnit(targetID);
if (target != nullptr)
return target->getUnitName();
}
return "";
}
bool Unit::isTargetAlive()
{
if (targetID == NULL)
return false;
Unit* target = unitsManager->getUnit(targetID);
if (target != nullptr)
return target->alive;
else
return false;
}
string Unit::getLeaderName()
{
if (isLeaderAlive())
{
Unit* leader = unitsManager->getUnit(leaderID);
if (leader != nullptr)
return leader->getUnitName();
}
return "";
}
bool Unit::isLeaderAlive()
{
if (leaderID == NULL)
return false;
Unit* leader = unitsManager->getUnit(leaderID);
if (leader != nullptr)
return leader->alive;
else
return false;
}
void Unit::resetActiveDestination()
{
activeDestination = Coords(NULL);
}
void Unit::resetTask()
{
Command* command = dynamic_cast<Command*>(new ResetTask(groupName, [this]() { this->setHasTaskAssigned(false); }));
scheduler->appendCommand(command);
setHasTask(false);
resetTaskFailedCounter();
}
void Unit::setFormationOffset(Offset newFormationOffset)
{
formationOffset = newFormationOffset;
/* Apply the change */
setHasTask(false);
resetTaskFailedCounter();
AIloop();
triggerUpdate(DataIndex::formationOffset);
}
void Unit::setROE(unsigned char newROE, bool force)
{
if (ROE != newROE || force) {
ROE = newROE;
Command* command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::ROE, static_cast<unsigned int>(ROE)));
scheduler->appendCommand(command);
triggerUpdate(DataIndex::ROE);
}
}
void Unit::setReactionToThreat(unsigned char newReactionToThreat, bool force)
{
if (reactionToThreat != newReactionToThreat || force) {
reactionToThreat = newReactionToThreat;
Command* command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::REACTION_ON_THREAT, static_cast<unsigned int>(reactionToThreat)));
scheduler->appendCommand(command);
triggerUpdate(DataIndex::reactionToThreat);
}
}
void Unit::setEmissionsCountermeasures(unsigned char newEmissionsCountermeasures, bool force)
{
if (emissionsCountermeasures != newEmissionsCountermeasures || force) {
emissionsCountermeasures = newEmissionsCountermeasures;
unsigned int radarEnum;
unsigned int flareEnum;
unsigned int ECMEnum;
if (emissionsCountermeasures == EmissionCountermeasure::SILENT)
{
radarEnum = RadarUse::NEVER;
flareEnum = FlareUse::NEVER;
ECMEnum = ECMUse::NEVER_USE;
}
else if (emissionsCountermeasures == EmissionCountermeasure::ATTACK)
{
radarEnum = RadarUse::FOR_ATTACK_ONLY;
flareEnum = FlareUse::AGAINST_FIRED_MISSILE;
ECMEnum = ECMUse::USE_IF_ONLY_LOCK_BY_RADAR;
}
else if (emissionsCountermeasures == EmissionCountermeasure::DEFEND)
{
radarEnum = RadarUse::FOR_SEARCH_IF_REQUIRED;
flareEnum = FlareUse::WHEN_FLYING_IN_SAM_WEZ;
ECMEnum = ECMUse::USE_IF_DETECTED_LOCK_BY_RADAR;
}
else if (emissionsCountermeasures == EmissionCountermeasure::FREE)
{
radarEnum = RadarUse::FOR_CONTINUOUS_SEARCH;
flareEnum = FlareUse::WHEN_FLYING_NEAR_ENEMIES;
ECMEnum = ECMUse::ALWAYS_USE;
}
else
return;
Command* command;
command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::RADAR_USING, radarEnum));
scheduler->appendCommand(command);
command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::FLARE_USING, flareEnum));
scheduler->appendCommand(command);
command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::ECM_USING, ECMEnum));
scheduler->appendCommand(command);
triggerUpdate(DataIndex::emissionsCountermeasures);
}
}
void Unit::landAt(Coords loc)
{
clearActivePath();
pushActivePathBack(loc);
setState(State::LAND);
}
void Unit::setIsActiveTanker(bool newIsActiveTanker)
{
if (isActiveTanker != newIsActiveTanker) {
isActiveTanker = newIsActiveTanker;
/* Apply the change */
setHasTask(false);
resetTaskFailedCounter();
AIloop();
triggerUpdate(DataIndex::isActiveTanker);
}
}
void Unit::setIsActiveAWACS(bool newIsActiveAWACS)
{
if (isActiveAWACS != newIsActiveAWACS) {
isActiveAWACS = newIsActiveAWACS;
/* Apply the change */
setHasTask(false);
resetTaskFailedCounter();
AIloop();
triggerUpdate(DataIndex::isActiveAWACS);
}
}
void Unit::setTACAN(DataTypes::TACAN newTACAN, bool force)
{
if (TACAN != newTACAN || force)
{
TACAN = newTACAN;
if (TACAN.isOn) {
std::ostringstream commandSS;
if (TACAN.channel < 0)
TACAN.channel = 0;
if (TACAN.channel > 126)
TACAN.channel = 126;
commandSS << "{"
<< "id = 'ActivateBeacon',"
<< "params = {"
<< "type = " << ((TACAN.XY == 'X' == 0) ? 4 : 5) << ","
<< "system = 3,"
<< "name = \"Olympus_TACAN\","
<< "callsign = \"" << TACAN.callsign << "\", "
<< "frequency = " << TACANChannelToFrequency(TACAN.channel, TACAN.XY) << ","
<< "}"
<< "}";
Command* command = dynamic_cast<Command*>(new SetCommand(groupName, commandSS.str()));
scheduler->appendCommand(command);
}
else {
std::ostringstream commandSS;
commandSS << "{"
<< "id = 'DeactivateBeacon',"
<< "params = {"
<< "}"
<< "}";
Command* command = dynamic_cast<Command*>(new SetCommand(groupName, commandSS.str()));
scheduler->appendCommand(command);
}
triggerUpdate(DataIndex::TACAN);
}
}
void Unit::setRadio(DataTypes::Radio newRadio, bool force)
{
if (radio != newRadio || force)
{
radio = newRadio;
std::ostringstream commandSS;
Command* command;
if (radio.frequency < 0)
radio.frequency = 0;
if (radio.frequency > 999000000)
radio.frequency = 999000000;
commandSS << "{"
<< "id = 'SetFrequency',"
<< "params = {"
<< "modulation = 0," // TODO Allow selection
<< "frequency = " << radio.frequency << ","
<< "}"
<< "}";
command = dynamic_cast<Command*>(new SetCommand(groupName, commandSS.str()));
scheduler->appendCommand(command);
// Clear the stringstream
commandSS.str(string(""));
commandSS << "{"
<< "id = 'SetCallsign',"
<< "params = {"
<< "callname = " << to_string(radio.callsign) << ","
<< "number = " << to_string(radio.callsignNumber) << ","
<< "}"
<< "}";
command = dynamic_cast<Command*>(new SetCommand(groupName, commandSS.str()));
scheduler->appendCommand(command);
triggerUpdate(DataIndex::radio);
}
}
void Unit::setGeneralSettings(DataTypes::GeneralSettings newGeneralSettings, bool force)
{
if (generalSettings != newGeneralSettings)
{
generalSettings = newGeneralSettings;
Command* command;
command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::PROHIBIT_AA, generalSettings.prohibitAA));
scheduler->appendCommand(command);
command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::PROHIBIT_AG, generalSettings.prohibitAG));
scheduler->appendCommand(command);
command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::PROHIBIT_JETT, generalSettings.prohibitJettison));
scheduler->appendCommand(command);
command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::PROHIBIT_AB, generalSettings.prohibitAfterburner));
scheduler->appendCommand(command);
command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::ENGAGE_AIR_WEAPONS, !generalSettings.prohibitAirWpn));
scheduler->appendCommand(command);
triggerUpdate(DataIndex::generalSettings);
}
}
void Unit::setDesiredSpeed(double newDesiredSpeed)
{
if (desiredSpeed != newDesiredSpeed) {
desiredSpeed = newDesiredSpeed;
/* Apply the change */
setHasTask(false);
resetTaskFailedCounter();
AIloop();
triggerUpdate(DataIndex::desiredSpeed);
}
}
void Unit::setDesiredAltitude(double newDesiredAltitude)
{
if (desiredAltitude != newDesiredAltitude) {
desiredAltitude = newDesiredAltitude;
/* Apply the change */
setHasTask(false);
resetTaskFailedCounter();
AIloop();
triggerUpdate(DataIndex::desiredAltitude);
}
}
void Unit::setDesiredSpeedType(string newDesiredSpeedType)
{
if (desiredSpeedType != (newDesiredSpeedType.compare("GS") == 0)) {
desiredSpeedType = newDesiredSpeedType.compare("GS") == 0;
/* Apply the change */
setHasTask(false);
resetTaskFailedCounter();
AIloop();
triggerUpdate(DataIndex::desiredSpeedType);
}
}
void Unit::setDesiredAltitudeType(string newDesiredAltitudeType)
{
if (desiredAltitudeType != (newDesiredAltitudeType.compare("AGL") == 0)) {
desiredAltitudeType = newDesiredAltitudeType.compare("AGL") == 0;
/* Apply the change */
setHasTask(false);
resetTaskFailedCounter();
AIloop();
triggerUpdate(DataIndex::desiredAltitudeType);
}
}
void Unit::goToDestination(string enrouteTask)
{
if (activeDestination != NULL)
{
Command* command = dynamic_cast<Command*>(new Move(groupName, activeDestination, getDesiredSpeed(), getDesiredSpeedType() ? "GS" : "CAS", getDesiredAltitude(), getDesiredAltitudeType() ? "AGL" : "ASL", enrouteTask, getCategory(), getFollowRoads(), [this]() { this->setHasTaskAssigned(true); }));
scheduler->appendCommand(command);
setHasTask(true);
}
}
bool Unit::isDestinationReached(double threshold)
{
if (activeDestination != NULL)
{
/* Check if any unit in the group has reached the point */
for (auto const& p : unitsManager->getGroupMembers(groupName))
{
double dist = 0;
Geodesic::WGS84().Inverse(p->getPosition().lat, p->getPosition().lng, activeDestination.lat, activeDestination.lng, dist);
if (dist < threshold)
{
log(unitName + " destination reached");
return true;
}
else {
return false;
}
}
return false;
}
else
return true;
}
bool Unit::setActiveDestination()
{
if (activePath.size() > 0)
{
activeDestination = activePath.front();
log(unitName + " active destination set to queue front");
triggerUpdate(DataIndex::activePath);
return true;
}
else
{
activeDestination = Coords(0);
log(unitName + " active destination set to NULL");
triggerUpdate(DataIndex::activePath);
return false;
}
}
bool Unit::updateActivePath(bool looping)
{
if (activePath.size() > 0)
{
/* Push the next destination in the queue to the front */
if (looping)
pushActivePathBack(activePath.front());
popActivePathFront();
log(unitName + " active path front popped");
return true;
}
else {
return false;
}
}
void Unit::setHasTask(bool newValue) {
updateValue(hasTask, newValue, DataIndex::hasTask);
}
bool Unit::checkTaskFailed()
{
if (getHasTask())
return false;
else {
if (taskCheckCounter > 0)
taskCheckCounter -= hasTaskAssigned;
return taskCheckCounter == 0;
}
}
void Unit::resetTaskFailedCounter() {
taskCheckCounter = TASK_CHECK_INIT_VALUE;
}
void Unit::setHasTaskAssigned(bool newHasTaskAssigned) {
hasTaskAssigned = newHasTaskAssigned;
if (hasTaskAssigned)
log(unitName + " was assigned a new task");
else
log(unitName + " no task assigned");
}
void Unit::triggerUpdate(unsigned char datumIndex) {
updateTimeMap[datumIndex] = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
}

View File

@@ -0,0 +1,241 @@
#include "framework.h"
#include "unitsManager.h"
#include "logger.h"
#include "unit.h"
#include "aircraft.h"
#include "helicopter.h"
#include "groundunit.h"
#include "navyunit.h"
#include "weapon.h"
#include "commands.h"
#include "scheduler.h"
#include "defines.h"
#include <GeographicLib/Geodesic.hpp>
using namespace GeographicLib;
#include "base64.hpp"
using namespace base64;
extern Scheduler* scheduler;
UnitsManager::UnitsManager(lua_State* L)
{
LogInfo(L, "Units Manager constructor called successfully");
}
UnitsManager::~UnitsManager()
{
}
Unit* UnitsManager::getUnit(unsigned int ID)
{
if (units.find(ID) == units.end()) {
return nullptr;
}
else {
return units[ID];
}
}
bool UnitsManager::isUnitInGroup(Unit* unit)
{
if (unit != nullptr) {
string groupName = unit->getGroupName();
if (groupName.length() == 0) return false;
for (auto const& p : units)
{
if (p.second->getGroupName().compare(groupName) == 0 && p.second != unit && p.second->getAlive())
return true;
}
}
return false;
}
/* Returns true if unit is group leader. Else, returns false, and leader will be equal to the group leader */
bool UnitsManager::isUnitGroupLeader(Unit* unit, Unit*& leader)
{
if (unit != nullptr) {
leader = getGroupLeader(unit);
return leader == nullptr? false: unit == leader;
}
else
return false;
}
Unit* UnitsManager::getGroupLeader(Unit* unit)
{
if (unit != nullptr) {
string groupName = unit->getGroupName();
if (groupName.length() == 0) return nullptr;
/* Find the first alive unit that has the same groupName */
for (auto const& p : units)
{
if (p.second->getAlive() && p.second->getGroupName().compare(groupName) == 0)
return p.second;
}
}
return nullptr;
}
vector<Unit*> UnitsManager::getGroupMembers(string groupName)
{
vector<Unit*> members;
for (auto const& p : units)
{
if (p.second->getGroupName().compare(groupName) == 0)
members.push_back(p.second);
}
return members;
}
Unit* UnitsManager::getGroupLeader(unsigned int ID)
{
Unit* unit = getUnit(ID);
return getGroupLeader(unit);
}
void UnitsManager::update(json::value& json, double dt)
{
for (auto const& p : json.as_object())
{
unsigned int ID = std::stoi(p.first);
if (units.count(ID) == 0)
{
json::value value = p.second;
if (value.has_string_field(L"category")) {
string category = to_string(value[L"category"].as_string());
if (category.compare("Aircraft") == 0)
units[ID] = dynamic_cast<Unit*>(new Aircraft(p.second, ID));
else if (category.compare("Helicopter") == 0)
units[ID] = dynamic_cast<Unit*>(new Helicopter(p.second, ID));
else if (category.compare("GroundUnit") == 0)
units[ID] = dynamic_cast<Unit*>(new GroundUnit(p.second, ID));
else if (category.compare("NavyUnit") == 0)
units[ID] = dynamic_cast<Unit*>(new NavyUnit(p.second, ID));
/* Initialize the unit if creation was successfull */
if (units.count(ID) != 0) {
units[ID]->update(p.second, dt);
units[ID]->initialize(p.second);
}
}
}
else {
/* Update the unit if present*/
if (units.count(ID) != 0)
units[ID]->update(p.second, dt);
}
}
}
void UnitsManager::runAILoop() {
/* Run the AI Loop on all units */
for (auto const& unit : units)
unit.second->runAILoop();
}
void UnitsManager::getUnitData(stringstream &ss, unsigned long long time)
{
for (auto const& p : units)
p.second->getData(ss, time);
}
void UnitsManager::deleteUnit(unsigned int ID, bool explosion, string explosionType, bool immediate)
{
if (getUnit(ID) != nullptr)
{
Command* command = dynamic_cast<Command*>(new Delete(ID, explosion, explosionType, immediate));
scheduler->appendCommand(command);
}
}
Unit* UnitsManager::getClosestUnit(Unit* unit, unsigned char coalition, vector<string> categories, double &distance) {
Unit* closestUnit = nullptr;
distance = 0;
for (auto const& p : units) {
/* Check if the units category is of the correct type */
bool requestedCategory = false;
for (auto const& category : categories) {
if (p.second->getCategory().compare(category) == 0) {
requestedCategory = true;
break;
}
}
/* Check if the unit belongs to the desired coalition, is alive, and is of the category requested */
if (requestedCategory && p.second->getCoalition() == coalition && p.second->getAlive()) {
/* Compute the distance from the unit to the tested unit */
double dist;
double bearing1;
double bearing2;
Geodesic::WGS84().Inverse(unit->getPosition().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 = sqrt(dist * dist + altDelta * altDelta);
}
else {
/* Check if the unit is closer than the one already selected */
if (dist < distance) {
closestUnit = p.second;
distance = sqrt(dist * dist + altDelta * altDelta);
}
}
}
}
return closestUnit;
}
map<Unit*, double> UnitsManager::getUnitsInRange(Unit* unit, unsigned char coalition, vector<string> categories, double range) {
map<Unit*, double> unitsInRange;
for (auto const& p : units) {
/* Check if the units category is of the correct type */
bool requestedCategory = false;
for (auto const& category : categories) {
if (p.second->getCategory().compare(category) == 0) {
requestedCategory = true;
break;
}
}
/* Check if the unit belongs to the desired coalition, is alive, and is of the category requested */
if (requestedCategory && p.second->getCoalition() == coalition && p.second->getAlive()) {
/* Compute the distance from the unit to the tested unit */
double dist;
double bearing1;
double bearing2;
Geodesic::WGS84().Inverse(unit->getPosition().lat, unit->getPosition().lng, p.second->getPosition().lat, p.second->getPosition().lng, dist, bearing1, bearing2);
if (dist <= range)
unitsInRange[p.second] = dist;
}
}
return unitsInRange;
}
void UnitsManager::acquireControl(unsigned int ID) {
Unit* leader = getGroupLeader(ID);
if (leader != nullptr) {
if (!leader->getControlled()) {
leader->setControlled(true);
leader->setDefaults(true);
}
}
}
void UnitsManager::loadDatabases() {
Aircraft::loadDatabase(AIRCRAFT_DATABASE_PATH);
Helicopter::loadDatabase(HELICOPTER_DATABASE_PATH);
GroundUnit::loadDatabase(GROUNDUNIT_DATABASE_PATH);
NavyUnit::loadDatabase(NAVYUNIT_DATABASE_PATH);
}

116
backend/core/src/weapon.cpp Normal file
View File

@@ -0,0 +1,116 @@
#include "weapon.h"
#include "utils.h"
#include "logger.h"
#include "commands.h"
#include "scheduler.h"
#include "defines.h"
#include <chrono>
using namespace std::chrono;
Weapon::Weapon(json::value json, unsigned int ID) :
ID(ID)
{
log("Creating weapon with ID: " + to_string(ID));
}
Weapon::~Weapon()
{
}
void Weapon::initialize(json::value json)
{
if (json.has_string_field(L"name"))
setName(to_string(json[L"name"]));
if (json.has_number_field(L"coalitionID"))
setCoalition(json[L"coalitionID"].as_number().to_int32());
update(json, 0);
}
void Weapon::update(json::value json, double dt)
{
if (json.has_object_field(L"position"))
{
setPosition({
json[L"position"][L"lat"].as_number().to_double(),
json[L"position"][L"lng"].as_number().to_double(),
json[L"position"][L"alt"].as_number().to_double()
});
}
if (json.has_number_field(L"heading"))
setHeading(json[L"heading"].as_number().to_double());
if (json.has_number_field(L"speed"))
setSpeed(json[L"speed"].as_number().to_double());
if (json.has_boolean_field(L"isAlive"))
setAlive(json[L"isAlive"].as_bool());
}
bool Weapon::checkFreshness(unsigned char datumIndex, unsigned long long time) {
auto it = updateTimeMap.find(datumIndex);
if (it == updateTimeMap.end())
return false;
else
return it->second > time;
}
bool Weapon::hasFreshData(unsigned long long time) {
for (auto it : updateTimeMap)
if (it.second > time)
return true;
return false;
}
void Weapon::getData(stringstream& ss, unsigned long long time)
{
const unsigned char endOfData = DataIndex::endOfData;
ss.write((const char*)&ID, sizeof(ID));
if (!alive && time == 0) {
unsigned char datumIndex = DataIndex::category;
appendString(ss, datumIndex, category);
datumIndex = DataIndex::alive;
appendNumeric(ss, datumIndex, alive);
}
else {
for (unsigned char datumIndex = DataIndex::startOfData + 1; datumIndex < DataIndex::lastIndex; datumIndex++)
{
if (checkFreshness(datumIndex, time)) {
switch (datumIndex) {
case DataIndex::category: appendString(ss, datumIndex, category); break;
case DataIndex::alive: appendNumeric(ss, datumIndex, alive); break;
case DataIndex::coalition: appendNumeric(ss, datumIndex, coalition); break;
case DataIndex::name: appendString(ss, datumIndex, name); break;
case DataIndex::position: appendNumeric(ss, datumIndex, position); break;
case DataIndex::speed: appendNumeric(ss, datumIndex, speed); break;
case DataIndex::heading: appendNumeric(ss, datumIndex, heading); break;
}
}
}
}
ss.write((const char*)&endOfData, sizeof(endOfData));
}
void Weapon::triggerUpdate(unsigned char datumIndex) {
updateTimeMap[datumIndex] = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
}
/* Missile */
Missile::Missile(json::value json, unsigned int ID) : Weapon(json, ID)
{
log("New Missile created with ID: " + to_string(ID));
setCategory("Missile");
};
/* Bomb */
Bomb::Bomb(json::value json, unsigned int ID) : Weapon(json, ID)
{
log("New Bomb created with ID: " + to_string(ID));
setCategory("Bomb");
};

View File

@@ -0,0 +1,65 @@
#include "framework.h"
#include "weaponsManager.h"
#include "logger.h"
#include "weapon.h"
#include "scheduler.h"
#include "base64.hpp"
using namespace base64;
WeaponsManager::WeaponsManager(lua_State* L)
{
LogInfo(L, "Weapons Manager constructor called successfully");
}
WeaponsManager::~WeaponsManager()
{
}
Weapon* WeaponsManager::getWeapon(unsigned int ID)
{
if (weapons.find(ID) == weapons.end()) {
return nullptr;
}
else {
return weapons[ID];
}
}
void WeaponsManager::update(json::value& json, double dt)
{
for (auto const& p : json.as_object())
{
unsigned int ID = std::stoi(p.first);
if (weapons.count(ID) == 0)
{
json::value value = p.second;
if (value.has_string_field(L"category")) {
string category = to_string(value[L"category"].as_string());
if (category.compare("Missile") == 0)
weapons[ID] = dynamic_cast<Weapon*>(new Missile(p.second, ID));
else if (category.compare("Bomb") == 0)
weapons[ID] = dynamic_cast<Weapon*>(new Bomb(p.second, ID));
/* Initialize the weapon if creation was successfull */
if (weapons.count(ID) != 0) {
weapons[ID]->update(p.second, dt);
weapons[ID]->initialize(p.second);
}
}
}
else {
/* Update the weapon if present*/
if (weapons.count(ID) != 0)
weapons[ID]->update(p.second, dt);
}
}
}
void WeaponsManager::getWeaponData(stringstream& ss, unsigned long long time)
{
for (auto const& p : weapons)
p.second->getData(ss, time);
}