4
diff --git a/client/views/unitcontrolpanel.ejs b/client/views/unitcontrolpanel.ejs
index fd37c1ff..276b569c 100644
--- a/client/views/unitcontrolpanel.ejs
+++ b/client/views/unitcontrolpanel.ejs
@@ -54,9 +54,10 @@
-
+
+
+
+
diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua
index 72eb98d1..a9cbf868 100644
--- a/scripts/OlympusCommand.lua
+++ b/scripts/OlympusCommand.lua
@@ -1,6 +1,6 @@
local version = "v0.1.2-alpha"
-local debug = false
+local debug = true
Olympus.unitCounter = 1
Olympus.payloadRegistry = {}
@@ -79,6 +79,18 @@ function Olympus.buildEnrouteTask(options)
}
}
end
+ -- Start being an active tanker
+ elseif options['id'] == 'Tanker' then
+ task = {
+ id = 'Tanker',
+ params = {},
+ }
+ -- Start being an active AWACS
+ elseif options['id'] == 'AWACS' then
+ task = {
+ id = 'AWACS',
+ params = {},
+ }
end
return task
end
@@ -86,18 +98,44 @@ end
-- Builds a valid task depending on the provided options
function Olympus.buildTask(options)
local task = nil
- -- Engage specific target by ID. Checks if target exists.
- if options['id'] == 'FollowUnit' and options['leaderID'] and options['offset'] then
- local leader = Olympus.getUnitByID(options['leaderID'])
- if leader and leader:isExist() then
+
+ if (Olympus.isArray(options)) then
+ local tasks = {}
+ for idx, subOptions in pairs(options) do
+ tasks[idx] = Olympus.buildTask(subOptions) or Olympus.buildEnrouteTask(subOptions)
+ end
+ task = {
+ id = 'ComboTask',
+ params = {
+ tasks = tasks
+ }
+ }
+ Olympus.debug(Olympus.serializeTable(task), 30)
+ else
+ if options['id'] == 'FollowUnit' and options['leaderID'] and options['offset'] then
+ local leader = Olympus.getUnitByID(options['leaderID'])
+ if leader and leader:isExist() then
+ task = {
+ id = 'Follow',
+ params = {
+ groupId = leader:getGroup():getID(),
+ pos = options['offset'],
+ lastWptIndexFlag = false,
+ lastWptIndex = 1
+ }
+ }
+ end
+ elseif options['id'] == 'Refuel' then
task = {
- id = 'Follow',
- params = {
- groupId = leader:getGroup():getID(),
- pos = options['offset'],
- lastWptIndexFlag = false,
- lastWptIndex = 1
- }
+ id = 'Refueling',
+ params = {}
+ }
+ elseif options['id'] == 'Orbit' then
+ task = {
+ id = 'Orbit',
+ params = {
+ pattern = options['pattern'] or "Circle"
+ }
}
end
end
@@ -253,6 +291,7 @@ function Olympus.spawnAircraft(coalition, unitType, lat, lng, spawnOptions)
["payload"] =
{
["pylons"] = payload,
+ ["fuel"] = 999999,
["flare"] = 60,
["ammo_type"] = 1,
["chaff"] = 60,
@@ -356,6 +395,7 @@ function Olympus.setTask(ID, taskOptions)
local unit = Olympus.getUnitByID(ID)
if unit then
local task = Olympus.buildTask(taskOptions);
+ Olympus.debug("Olympus.setTask " .. Olympus.serializeTable(task), 20)
if task then
unit:getGroup():getController():setTask(task)
Olympus.debug("Olympus.setTask completed successfully", 2)
@@ -382,7 +422,7 @@ function Olympus.setCommand(ID, command)
end
function Olympus.setOption(ID, optionID, optionValue)
- Olympus.debug("Olympus.setCommand " .. ID .. " " .. optionID .. " " .. optionValue, 2)
+ Olympus.debug("Olympus.setOption " .. ID .. " " .. optionID .. " " .. optionValue, 2)
local unit = Olympus.getUnitByID(ID)
if unit then
unit:getGroup():getController():setOption(optionID, optionValue)
@@ -422,6 +462,16 @@ function Olympus.serializeTable(val, name, skipnewlines, depth)
return tmp
end
+function Olympus.isArray(t)
+ local i = 0
+ for _ in pairs(t) do
+ i = i + 1
+ if t[i] == nil then return false end
+ end
+ return true
+end
+
+
function Olympus.setMissionData(arg, time)
local missionData = {}
diff --git a/src/core/include/airunit.h b/src/core/include/airunit.h
index 550bbccf..a2aeb7a6 100644
--- a/src/core/include/airunit.h
+++ b/src/core/include/airunit.h
@@ -26,5 +26,4 @@ protected:
void createHoldingPattern();
bool updateActivePath(bool looping);
void goToDestination(wstring enrouteTask = L"nil");
- void taskWingmen();
};
\ No newline at end of file
diff --git a/src/core/include/unit.h b/src/core/include/unit.h
index e5e27ef0..441c3e75 100644
--- a/src/core/include/unit.h
+++ b/src/core/include/unit.h
@@ -103,13 +103,35 @@ public:
void pushActivePathBack(Coords newActivePathBack);
void popActivePathFront();
void setTargetID(int newTargetID) { targetID = newTargetID; addMeasure(L"targetID", json::value(newTargetID));}
+ void setIsTanker(bool newIsTanker) { isTanker = newIsTanker; addMeasure(L"isTanker", json::value(newIsTanker));}
+ void setIsAWACS(bool newIsAWACS) { isAWACS = newIsAWACS; addMeasure(L"isAWACS", json::value(newIsAWACS));}
+ void setTACANOn(bool newTACANOn);
+ void setTACANChannel(int newTACANChannel);
+ void setTACANXY(wstring newTACANXY);
+ void setTACANCallsign(wstring newTACANCallsign);
+ void setTACAN();
+ void setRadioOn(bool newRadioOn);
+ void setRadioFrequency(int newRadioFrequency);
+ void setRadioCallsign(int newRadioCallsign);
+ void setRadioCallsignNumber(int newRadioCallsignNumber);
+ void setRadio();
wstring getCurrentTask() { return currentTask; }
virtual double getTargetSpeed() { return targetSpeed; };
virtual double getTargetAltitude() { return targetAltitude; };
Coords getActiveDestination() { return activeDestination; }
list
getActivePath() { return activePath; }
int getTargetID() { return targetID; }
-
+ bool getIsTanker() { return isTanker; }
+ bool getIsAWACS() { return isAWACS; }
+ bool getTACANOn() { return TACANOn; }
+ int getTACANChannel() { return TACANChannel; }
+ wstring getTACANXY() { return TACANXY; }
+ wstring getTACANCallsign() { return TACANCallsign; }
+ bool getRadioOn() { return radioOn; }
+ int getRadioFrequency() { return radioFrequency; }
+ int getRadioCallsign() { return radioCallsign; }
+ int getRadioCallsignNumber() { return radioCallsignNumber; }
+
/********** Options data **********/
void setROE(wstring newROE);
void setReactionToThreat(wstring newReactionToThreat);
@@ -147,6 +169,7 @@ protected:
/********** Mission data **********/
double fuel = 0;
+ double initialFuel = 0; // Used internally to detect refueling completed
json::value ammo = json::value::null();
json::value targets = json::value::null();
bool hasTask = false;
@@ -168,6 +191,16 @@ protected:
list activePath;
Coords activeDestination = Coords(0);
int targetID = NULL;
+ bool isTanker = false;
+ bool isAWACS = false;
+ bool TACANOn = false;
+ int TACANChannel = 40;
+ wstring TACANXY = L"X";
+ wstring TACANCallsign = L"TKR";
+ bool radioOn = false;
+ int radioFrequency = 260000000; // MHz
+ int radioCallsign = 1;
+ int radioCallsignNumber = 1;
/********** Options data **********/
wstring ROE = L"";
diff --git a/src/core/src/airunit.cpp b/src/core/src/airunit.cpp
index 08922240..cf3d2369 100644
--- a/src/core/src/airunit.cpp
+++ b/src/core/src/airunit.cpp
@@ -22,6 +22,7 @@ void AirUnit::setState(int newState)
{
if (state != newState)
{
+ /* Perform any action required when LEAVING a certain state */
switch (state) {
case State::IDLE: {
break;
@@ -36,25 +37,27 @@ void AirUnit::setState(int newState)
case State::FOLLOW: {
break;
}
- case State::WINGMAN: {
- if (isWingman)
- return;
+ case State::LAND: {
break;
}
- case State::LAND: {
+ case State::REFUEL: {
break;
}
default:
break;
}
+ /* Perform any action required when ENTERING a certain state */
switch (newState) {
case State::IDLE: {
+ clearActivePath();
resetActiveDestination();
+ addMeasure(L"currentState", json::value(L"Idle"));
break;
}
case State::REACH_DESTINATION: {
resetActiveDestination();
+ addMeasure(L"currentState", json::value(L"Reach destination"));
break;
}
case State::ATTACK: {
@@ -64,19 +67,26 @@ void AirUnit::setState(int newState)
clearActivePath();
pushActivePathFront(targetPosition);
resetActiveDestination();
+ addMeasure(L"currentState", json::value(L"Attack"));
}
break;
}
case State::FOLLOW: {
+ clearActivePath();
resetActiveDestination();
- break;
- }
- case State::WINGMAN: {
- resetActiveDestination();
+ addMeasure(L"currentState", json::value(L"Follow"));
break;
}
case State::LAND: {
resetActiveDestination();
+ addMeasure(L"currentState", json::value(L"Land"));
+ break;
+ }
+ case State::REFUEL: {
+ initialFuel = fuel;
+ clearActivePath();
+ resetActiveDestination();
+ addMeasure(L"currentState", json::value(L"Refuel"));
break;
}
default:
@@ -170,63 +180,50 @@ void AirUnit::goToDestination(wstring enrouteTask)
log(unitName + L" error, no active destination!");
}
-void AirUnit::taskWingmen()
-{
- switch (state) {
- case State::IDLE:
- case State::REACH_DESTINATION:
- case State::ATTACK:{
- int idx = 1;
- for (auto const& wingman : wingmen)
- {
- if (!wingman->getIsWingman())
- {
- wingman->setIsWingman(true);
- wingman->setLeader(this);
- }
-
- if (wingman->getFormation().compare(formation) != 0)
- {
- wingman->resetTask();
- wingman->setFormation(formation);
- if (formation.compare(L"Line abreast") == 0)
- wingman->setFormationOffset(Offset(0 * idx, 0 * idx, 1852 * idx));
- idx++;
- }
- }
- break;
- }
- default:
- break;
- }
-}
-
void AirUnit::AIloop()
{
/* State machine */
switch (state) {
case State::IDLE: {
- wstring enrouteTask = L"nil";
currentTask = L"Idle";
- if (activeDestination == NULL || !hasTask)
+ if (!hasTask)
{
- createHoldingPattern();
- setActiveDestination();
- goToDestination(enrouteTask);
+ std::wostringstream taskSS;
+ if (isTanker) {
+ taskSS << "{ [1] = { id = 'Tanker' }, [2] = { id = 'Orbit', pattern = 'Race-Track' } }";
+ }
+ else if (isAWACS) {
+ taskSS << "{ [1] = { id = 'AWACS' }, [2] = { id = 'Orbit', pattern = 'Circle' } }";
+ }
+ else {
+ taskSS << "{ id = 'Orbit', pattern = 'Circle' }";
+ }
+ Command* command = dynamic_cast(new SetTask(ID, taskSS.str()));
+ scheduler->appendCommand(command);
+ hasTask = true;
}
- else {
- if (isDestinationReached() && updateActivePath(true) && setActiveDestination())
- goToDestination(enrouteTask);
- }
-
- if (isLeader)
- taskWingmen();
break;
}
case State::REACH_DESTINATION: {
- wstring enrouteTask = L"nil";
- currentTask = L"Reaching destination";
+ wstring enrouteTask = L"";
+ bool looping = false;
+
+ if (isTanker)
+ {
+ enrouteTask = L"{ id = 'Tanker' }";
+ currentTask = L"Tanker";
+ }
+ else if (isAWACS)
+ {
+ enrouteTask = L"{ id = 'AWACS' }";
+ currentTask = L"AWACS";
+ }
+ else
+ {
+ enrouteTask = L"nil";
+ currentTask = L"Reaching destination";
+ }
if (activeDestination == NULL || !hasTask)
{
@@ -235,23 +232,18 @@ void AirUnit::AIloop()
}
else {
if (isDestinationReached()) {
- if (updateActivePath(false) && setActiveDestination())
+ if (updateActivePath(looping) && setActiveDestination())
goToDestination(enrouteTask);
else {
setState(State::IDLE);
break;
}
}
- }
-
- if (isLeader)
- taskWingmen();
-
+ }
break;
}
-
case State::LAND: {
- wstring enrouteTask = L"{" "id = 'land' }";
+ wstring enrouteTask = L"{ id = 'Land' }";
currentTask = L"Landing";
if (activeDestination == NULL)
@@ -259,10 +251,6 @@ void AirUnit::AIloop()
setActiveDestination();
goToDestination(enrouteTask);
}
-
- if (isLeader)
- taskWingmen();
-
break;
}
case State::ATTACK: {
@@ -282,49 +270,34 @@ void AirUnit::AIloop()
wstring enrouteTask = enrouteTaskSS.str();
currentTask = L"Attacking " + getTargetName();
- if (activeDestination == NULL || !hasTask)
+ if (!hasTask)
{
setActiveDestination();
goToDestination(enrouteTask);
}
- else {
- if (isDestinationReached()) {
- if (updateActivePath(false) && setActiveDestination())
- goToDestination(enrouteTask);
- else {
- setState(State::IDLE);
- break;
- }
- }
- }
-
- if (isLeader)
- taskWingmen();
break;
}
case State::FOLLOW: {
- /* TODO */
- setState(State::IDLE);
- break;
- }
- case State::WINGMAN: {
- /* In the WINGMAN state, the unit relinquishes control to the leader */
clearActivePath();
activeDestination = Coords(NULL);
- if (leader == nullptr || !leader->getAlive())
- {
- this->setFormation(L"");
- this->setIsWingman(false);
+
+ /* If the target is not alive (either not set or was destroyed) go back to IDLE */
+ if (!isTargetAlive()) {
+ setState(State::IDLE);
break;
}
+
+ currentTask = L"Following " + getTargetName();
+
+ Unit* target = unitsManager->getUnit(targetID);
if (!hasTask) {
- if (leader != nullptr && leader->getAlive() && formationOffset != NULL)
+ if (target != nullptr && target->getAlive() && formationOffset != NULL)
{
std::wostringstream taskSS;
taskSS << "{"
<< "id = 'FollowUnit'" << ", "
- << "leaderID = " << leader->getID() << ","
+ << "leaderID = " << target->getID() << ","
<< "offset = {"
<< "x = " << formationOffset.x << ","
<< "y = " << formationOffset.y << ","
@@ -338,7 +311,26 @@ void AirUnit::AIloop()
}
break;
}
+ case State::REFUEL: {
+ currentTask = L"Refueling";
+
+ if (!hasTask) {
+ if (fuel <= initialFuel) {
+ std::wostringstream taskSS;
+ taskSS << "{"
+ << "id = 'Refuel'"
+ << "}";
+ Command* command = dynamic_cast(new SetTask(ID, taskSS.str()));
+ scheduler->appendCommand(command);
+ hasTask = true;
+ }
+ else {
+ setState(State::IDLE);
+ }
+ }
+ }
default:
break;
}
+ addMeasure(L"currentTask", json::value(currentTask));
}
diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp
index 8e80f9bf..318945f4 100644
--- a/src/core/src/scheduler.cpp
+++ b/src/core/src/scheduler.cpp
@@ -142,14 +142,34 @@ void Scheduler::handleRequest(wstring key, json::value value)
unit->setTargetID(targetID);
unit->setState(State::ATTACK);
}
- else if (key.compare(L"stopAttack") == 0)
+ else if (key.compare(L"followUnit") == 0)
{
int ID = value[L"ID"].as_integer();
+ int targetID = value[L"targetID"].as_integer();
+ int offsetX = value[L"offsetX"].as_integer();
+ int offsetY = value[L"offsetY"].as_integer();
+ int offsetZ = value[L"offsetZ"].as_integer();
+
Unit* unit = unitsManager->getUnit(ID);
+ Unit* target = unitsManager->getUnit(targetID);
+
+ wstring unitName;
+ wstring targetName;
+
if (unit != nullptr)
- unit->setState(State::REACH_DESTINATION);
+ unitName = unit->getUnitName();
else
return;
+
+ if (target != nullptr)
+ targetName = target->getUnitName();
+ else
+ return;
+
+ log(L"Unit " + unitName + L" following unit " + targetName);
+ unit->setFormationOffset(Offset(offsetX, offsetY, offsetZ));
+ unit->setTargetID(targetID);
+ unit->setState(State::FOLLOW);
}
else if (key.compare(L"changeSpeed") == 0)
{
@@ -250,6 +270,36 @@ void Scheduler::handleRequest(wstring key, json::value value)
int ID = value[L"ID"].as_integer();
unitsManager->deleteUnit(ID);
}
+ else if (key.compare(L"refuel") == 0)
+ {
+ int ID = value[L"ID"].as_integer();
+ Unit* unit = unitsManager->getUnit(ID);
+ unit->setState(State::REFUEL);
+ }
+ else if (key.compare(L"setAdvancedOptions") == 0)
+ {
+ int ID = value[L"ID"].as_integer();
+ Unit* unit = unitsManager->getUnit(ID);
+ if (unit != nullptr)
+ {
+ unit->setIsTanker(value[L"isTanker"].as_bool());
+ unit->setIsAWACS(value[L"isAWACS"].as_bool());
+
+ unit->setTACANOn(true); // TODO Remove
+ unit->setTACANChannel(value[L"TACANChannel"].as_number().to_int32());
+ unit->setTACANXY(value[L"TACANXY"].as_string());
+ unit->setTACANCallsign(value[L"TACANCallsign"].as_string());
+ unit->setTACAN();
+
+ unit->setRadioOn(true); // TODO Remove
+ unit->setRadioFrequency(value[L"radioFrequency"].as_number().to_int32());
+ unit->setRadioCallsign(value[L"radioCallsign"].as_number().to_int32());
+ unit->setRadioCallsignNumber(value[L"radioCallsignNumber"].as_number().to_int32());
+ unit->setRadio();
+
+ unit->resetActiveDestination();
+ }
+ }
else
{
log(L"Unknown command: " + key);
diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp
index 8d4c80af..bf848269 100644
--- a/src/core/src/unit.cpp
+++ b/src/core/src/unit.cpp
@@ -19,6 +19,7 @@ Unit::Unit(json::value json, int ID) :
ID(ID)
{
log("Creating unit with ID: " + to_string(ID));
+ addMeasure(L"currentState", json::value(L"Idle"));
}
Unit::~Unit()
@@ -134,7 +135,7 @@ json::value Unit::getData(long long time)
/********** Task data **********/
json[L"taskData"] = json::value::object();
- for (auto key : { L"currentTask", L"targetSpeed", L"targetAltitude", L"activePath"})
+ for (auto key : { L"currentState", L"currentTask", L"targetSpeed", L"targetAltitude", L"activePath", L"isTanker", L"isAWACS", L"TACANChannel", L"TACANXY", L"TACANCallsign", L"radioFrequency", L"radioCallsign", L"radioCallsignNumber"})
{
if (measures.find(key) != measures.end() && measures[key]->getTime() > time)
json[L"taskData"][key] = measures[key]->getValue();
@@ -346,4 +347,90 @@ void Unit::landAt(Coords loc) {
clearActivePath();
pushActivePathBack(loc);
setState(State::LAND);
-}
\ No newline at end of file
+}
+
+void Unit::setTACANOn(bool newTACANOn) {
+ TACANOn = newTACANOn;
+ addMeasure(L"TACANOn", json::value(newTACANOn));
+}
+
+void Unit::setTACANChannel(int newTACANChannel) {
+ TACANChannel = newTACANChannel;
+ addMeasure(L"TACANChannel", json::value(newTACANChannel));
+}
+
+void Unit::setTACANXY(wstring newTACANXY) {
+ TACANXY = newTACANXY;
+ addMeasure(L"TACANXY", json::value(newTACANXY));
+}
+void Unit::setTACANCallsign(wstring newTACANCallsign) {
+ TACANCallsign = newTACANCallsign;
+ addMeasure(L"TACANCallsign", json::value(newTACANCallsign));
+}
+
+void Unit::setRadioOn(bool newRadioOn) {
+ radioOn = newRadioOn;
+ addMeasure(L"radioOn", json::value(newRadioOn));
+}
+
+void Unit::setRadioFrequency(int newRadioFrequency) {
+ radioFrequency = newRadioFrequency;
+ addMeasure(L"radioFrequency", json::value(newRadioFrequency));
+}
+
+void Unit::setRadioCallsign(int newRadioCallsign) {
+ radioCallsign = newRadioCallsign;
+ addMeasure(L"radioCallsign", json::value(newRadioCallsign));
+}
+
+void Unit::setRadioCallsignNumber(int newRadioCallsignNumber) {
+ radioCallsignNumber = newRadioCallsignNumber;
+ addMeasure(L"radioCallsignNumber", json::value(newRadioCallsignNumber));
+}
+
+void Unit::setTACAN()
+{
+ std::wostringstream commandSS;
+ commandSS << "{"
+ << "id = 'ActivateBeacon',"
+ << "params = {"
+ << "type = " << ((TACANXY.compare(L"X") == 0)? 4: 5) << ","
+ << "system = 4,"
+ << "name = Olympus_TACAN,"
+ << "callsign = " << TACANCallsign << ", "
+ << "frequency = " << TACANChannelToFrequency(TACANChannel, TACANXY) << ","
+ << "}"
+ << "}";
+ Command* command = dynamic_cast(new SetCommand(ID, commandSS.str()));
+ scheduler->appendCommand(command);
+}
+
+void Unit::setRadio()
+{
+ {
+ std::wostringstream commandSS;
+ commandSS << "{"
+ << "id = 'SetFrequency',"
+ << "params = {"
+ << "modulation = 0," // TODO Allow selection
+ << "frequency = " << radioFrequency << ","
+ << "}"
+ << "}";
+ Command* command = dynamic_cast(new SetCommand(ID, commandSS.str()));
+ scheduler->appendCommand(command);
+ }
+
+ {
+ std::wostringstream commandSS;
+ commandSS << "{"
+ << "id = 'SetCallsign',"
+ << "params = {"
+ << "callname = " << radioCallsign << ","
+ << "number = " << radioCallsignNumber << ","
+ << "}"
+ << "}";
+ Command* command = dynamic_cast(new SetCommand(ID, commandSS.str()));
+ scheduler->appendCommand(command);
+ }
+}
+
diff --git a/src/dcstools/include/dcstools.h b/src/dcstools/include/dcstools.h
index b4866db4..db8ff519 100644
--- a/src/dcstools/include/dcstools.h
+++ b/src/dcstools/include/dcstools.h
@@ -8,4 +8,5 @@ void DllExport LogError(lua_State* L, string message);
void DllExport Log(lua_State* L, string message, int level);
int DllExport dostring_in(lua_State* L, string target, string command);
map DllExport getAllUnits(lua_State* L);
+int DllExport TACANChannelToFrequency(int channel, wstring XY);
diff --git a/src/dcstools/src/dcstools.cpp b/src/dcstools/src/dcstools.cpp
index 3995f9b5..64184a98 100644
--- a/src/dcstools/src/dcstools.cpp
+++ b/src/dcstools/src/dcstools.cpp
@@ -94,7 +94,6 @@ exit:
return units;
}
-
int dostring_in(lua_State* L, string target, string command)
{
lua_getglobal(L, "net");
@@ -102,4 +101,10 @@ int dostring_in(lua_State* L, string target, string command)
lua_pushstring(L, target.c_str());
lua_pushstring(L, command.c_str());
return lua_pcall(L, 2, 0, 0);
+}
+
+int TACANChannelToFrequency(int channel, wstring XY)
+{
+ int basef = (XY == L"X" && channel > 63) || (XY == L"Y" && channel < 64) ? 1087: 961;
+ return (basef + channel) * 1000000;
}
\ No newline at end of file