More work on follow and tanking (added combo tasks)

This commit is contained in:
Pax1601
2023-04-16 20:24:33 +02:00
parent 39698c66a3
commit 77d39c17b8
11 changed files with 158 additions and 114 deletions

View File

@@ -28,6 +28,7 @@ var mouseInfoPanel: MouseInfoPanel;
var logPanel: LogPanel; var logPanel: LogPanel;
var connected: boolean = false; var connected: boolean = false;
var paused: boolean = false;
var activeCoalition: string = "blue"; var activeCoalition: string = "blue";
var sessionHash: string | null = null; var sessionHash: string | null = null;
@@ -106,8 +107,10 @@ function startPeriodicUpdate() {
function requestUpdate() { function requestUpdate() {
/* Main update rate = 250ms is minimum time, equal to server update time. */ /* Main update rate = 250ms is minimum time, equal to server update time. */
getUnits((data: UnitsData) => { getUnits((data: UnitsData) => {
getUnitsManager()?.update(data); if (!getPaused()){
checkSessionHash(data.sessionHash); getUnitsManager()?.update(data);
checkSessionHash(data.sessionHash);
}
}, false); }, false);
setTimeout(() => requestUpdate(), getConnected() ? 250 : 1000); setTimeout(() => requestUpdate(), getConnected() ? 250 : 1000);
@@ -117,15 +120,17 @@ function requestUpdate() {
function requestRefresh() { function requestRefresh() {
/* Main refresh rate = 5000ms. */ /* Main refresh rate = 5000ms. */
getUnits((data: UnitsData) => { getUnits((data: UnitsData) => {
getUnitsManager()?.update(data); if (!getPaused()){
getAirbases((data: AirbasesData) => getMissionData()?.update(data)); getUnitsManager()?.update(data);
getBullseyes((data: BullseyesData) => getMissionData()?.update(data)); getAirbases((data: AirbasesData) => getMissionData()?.update(data));
getMission((data: any) => {getMissionData()?.update(data)}); getBullseyes((data: BullseyesData) => getMissionData()?.update(data));
getMission((data: any) => {getMissionData()?.update(data)});
// Update the list of existing units // Update the list of existing units
getUnitDataTable()?.update(); getUnitDataTable()?.update();
checkSessionHash(data.sessionHash); checkSessionHash(data.sessionHash);
}
}, true); }, true);
setTimeout(() => requestRefresh(), 5000); setTimeout(() => requestRefresh(), 5000);
} }
@@ -183,6 +188,9 @@ function setupEvents() {
case "Quote": case "Quote":
unitDataTable.toggle(); unitDataTable.toggle();
break break
case "Space":
setPaused(!getPaused());
break;
} }
}); });
@@ -268,4 +276,12 @@ export function getConnected() {
return connected; return connected;
} }
export function setPaused(newPaused: boolean) {
paused = newPaused;
}
export function getPaused() {
return paused;
}
window.onload = setup; window.onload = setup;

View File

@@ -210,7 +210,6 @@ export class UnitControlPanel extends Panel {
if (getUnitsManager().getSelectedUnits().length == 1) if (getUnitsManager().getSelectedUnits().length == 1)
{ {
var asd = this.#advancedSettingsDialog;
this.#radioCallsignDropdown.setOptions(["Enfield", "Springfield", "Uzi", "Colt", "Dodge", "Ford", "Chevy", "Pontiac"]); this.#radioCallsignDropdown.setOptions(["Enfield", "Springfield", "Uzi", "Colt", "Dodge", "Ford", "Chevy", "Pontiac"]);
this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign); this.#radioCallsignDropdown.selectValue(unit.getTaskData().radioCallsign);
var tankerCheckbox = this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.querySelector("input") var tankerCheckbox = this.#advancedSettingsDialog.querySelector("#tanker-checkbox")?.querySelector("input")

View File

@@ -118,7 +118,11 @@ export function attackUnit(ID: number, targetID: number) {
} }
export function followUnit(ID: number, targetID: number) { export function followUnit(ID: number, targetID: number) {
var command = { "ID": ID, "targetID": targetID }; // X: front-rear, positive front
// Y: top-bottom, positive bottom
// Z: left-right, positive right
var command = { "ID": ID, "targetID": targetID, "offsetX": -50, "offsetY": -10, "offsetZ": 50};
var data = { "followUnit": command } var data = { "followUnit": command }
POST(data, () => { }); POST(data, () => { });
} }

View File

@@ -708,6 +708,25 @@ export class AircraftDatabase extends UnitDatabase {
], ],
"filename": "kc-135.png" "filename": "kc-135.png"
}, },
"KC135MPRS": {
"name": "KC135MPRS",
"label": "KC-135 MPRS",
"shortLabel": "135M",
"loadouts": [
{
"fuel": 1,
"items": [
],
"roles": [
"Tanker"
],
"code": "",
"name": "Default Tanker"
}
],
"filename": "kc-135.png"
},
"MiG-23MLD": { "MiG-23MLD": {
"name": "MiG-23MLD", "name": "MiG-23MLD",
"label": "MiG-23MLD", "label": "MiG-23MLD",

View File

@@ -515,6 +515,9 @@ export class Unit extends Marker {
points.push(new LatLng(destination.lat, destination.lng)); points.push(new LatLng(destination.lat, destination.lng));
this.#pathPolyline.setLatLngs(points); this.#pathPolyline.setLatLngs(points);
} }
if (points.length == 1)
this.#clearPath();
} }
} }

View File

@@ -98,24 +98,46 @@ end
-- Builds a valid task depending on the provided options -- Builds a valid task depending on the provided options
function Olympus.buildTask(options) function Olympus.buildTask(options)
local task = nil local task = nil
if options['id'] == 'FollowUnit' and options['leaderID'] and options['offset'] then
local leader = Olympus.getUnitByID(options['leaderID']) if (Olympus.isArray(options)) then
if leader and leader:isExist() 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 = { task = {
id = 'Follow', id = 'Refueling',
params = { params = {}
groupId = leader:getGroup():getID(), }
pos = options['offset'], elseif options['id'] == 'Orbit' then
lastWptIndexFlag = false, task = {
lastWptIndex = 1 id = 'Orbit',
} params = {
pattern = options['pattern'] or "Circle"
}
} }
end end
elseif options['id'] == 'Refuel' then
task = {
id = 'Refueling',
params = {}
}
end end
return task return task
end end
@@ -269,6 +291,7 @@ function Olympus.spawnAircraft(coalition, unitType, lat, lng, spawnOptions)
["payload"] = ["payload"] =
{ {
["pylons"] = payload, ["pylons"] = payload,
["fuel"] = 999999,
["flare"] = 60, ["flare"] = 60,
["ammo_type"] = 1, ["ammo_type"] = 1,
["chaff"] = 60, ["chaff"] = 60,
@@ -439,6 +462,16 @@ function Olympus.serializeTable(val, name, skipnewlines, depth)
return tmp return tmp
end 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) function Olympus.setMissionData(arg, time)
local missionData = {} local missionData = {}

View File

@@ -26,5 +26,4 @@ protected:
void createHoldingPattern(); void createHoldingPattern();
bool updateActivePath(bool looping); bool updateActivePath(bool looping);
void goToDestination(wstring enrouteTask = L"nil"); void goToDestination(wstring enrouteTask = L"nil");
void taskWingmen();
}; };

View File

@@ -37,11 +37,6 @@ void AirUnit::setState(int newState)
case State::FOLLOW: { case State::FOLLOW: {
break; break;
} }
case State::WINGMAN: {
if (isWingman)
return;
break;
}
case State::LAND: { case State::LAND: {
break; break;
} }
@@ -55,6 +50,7 @@ void AirUnit::setState(int newState)
/* Perform any action required when ENTERING a certain state */ /* Perform any action required when ENTERING a certain state */
switch (newState) { switch (newState) {
case State::IDLE: { case State::IDLE: {
clearActivePath();
resetActiveDestination(); resetActiveDestination();
break; break;
} }
@@ -73,10 +69,7 @@ void AirUnit::setState(int newState)
break; break;
} }
case State::FOLLOW: { case State::FOLLOW: {
resetActiveDestination(); clearActivePath();
break;
}
case State::WINGMAN: {
resetActiveDestination(); resetActiveDestination();
break; break;
} }
@@ -180,71 +173,40 @@ void AirUnit::goToDestination(wstring enrouteTask)
log(unitName + L" error, no active destination!"); 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() void AirUnit::AIloop()
{ {
/* State machine */ /* State machine */
switch (state) { switch (state) {
case State::IDLE: { case State::IDLE: {
wstring enrouteTask = L"nil";
currentTask = L"Idle"; currentTask = L"Idle";
if (activeDestination == NULL || !hasTask) if (!hasTask)
{ {
createHoldingPattern(); std::wostringstream taskSS;
setActiveDestination(); if (isTanker) {
goToDestination(enrouteTask); taskSS << "{ [1] = { id = 'Orbit', pattern = 'Race-Track' }, [2] = { id = 'Tanker' } }";
}
else {
taskSS << "{ id = 'Orbit', pattern = 'Circle' }";
}
Command* command = dynamic_cast<Command*>(new SetTask(ID, taskSS.str()));
scheduler->appendCommand(command);
hasTask = true;
} }
else {
if (isDestinationReached() && updateActivePath(true) && setActiveDestination())
goToDestination(enrouteTask);
}
if (isLeader)
taskWingmen();
break; break;
} }
case State::REACH_DESTINATION: { case State::REACH_DESTINATION: {
wstring enrouteTask = L""; wstring enrouteTask = L"";
bool looping = false;
if (isTanker) if (isTanker)
{ {
enrouteTask = L"{" "id = 'Tanker' }"; enrouteTask = L"{ id = 'Tanker' }";
currentTask = L"Tanker"; currentTask = L"Tanker";
} }
else if (isAWACS) else if (isAWACS)
{ {
enrouteTask = L"{" "id = 'AWACS' }"; enrouteTask = L"{ id = 'AWACS' }";
currentTask = L"AWACS"; currentTask = L"AWACS";
} }
else else
@@ -260,21 +222,16 @@ void AirUnit::AIloop()
} }
else { else {
if (isDestinationReached()) { if (isDestinationReached()) {
if (updateActivePath(false) && setActiveDestination()) if (updateActivePath(looping) && setActiveDestination())
goToDestination(enrouteTask); goToDestination(enrouteTask);
else { else {
setState(State::IDLE); setState(State::IDLE);
break; break;
} }
} }
} }
if (isLeader)
taskWingmen();
break; break;
} }
case State::LAND: { case State::LAND: {
wstring enrouteTask = L"{" "id = 'land' }"; wstring enrouteTask = L"{" "id = 'land' }";
currentTask = L"Landing"; currentTask = L"Landing";
@@ -284,10 +241,6 @@ void AirUnit::AIloop()
setActiveDestination(); setActiveDestination();
goToDestination(enrouteTask); goToDestination(enrouteTask);
} }
if (isLeader)
taskWingmen();
break; break;
} }
case State::ATTACK: { case State::ATTACK: {
@@ -322,34 +275,28 @@ void AirUnit::AIloop()
} }
} }
} }
if (isLeader)
taskWingmen();
break; break;
} }
case State::FOLLOW: { case State::FOLLOW: {
/* TODO */
setState(State::IDLE);
break;
}
case State::WINGMAN: {
/* In the WINGMAN state, the unit relinquishes control to the leader */
clearActivePath(); clearActivePath();
activeDestination = Coords(NULL); activeDestination = Coords(NULL);
if (leader == nullptr || !leader->getAlive())
{ /* If the target is not alive (either not set or was destroyed) go back to IDLE */
this->setFormation(L""); if (!isTargetAlive()) {
this->setIsWingman(false); setState(State::IDLE);
break; break;
} }
currentTask = L"Following " + getTargetName();
Unit* target = unitsManager->getUnit(targetID);
if (!hasTask) { if (!hasTask) {
if (leader != nullptr && leader->getAlive() && formationOffset != NULL) if (target != nullptr && target->getAlive() && formationOffset != NULL)
{ {
std::wostringstream taskSS; std::wostringstream taskSS;
taskSS << "{" taskSS << "{"
<< "id = 'FollowUnit'" << ", " << "id = 'FollowUnit'" << ", "
<< "leaderID = " << leader->getID() << "," << "leaderID = " << target->getID() << ","
<< "offset = {" << "offset = {"
<< "x = " << formationOffset.x << "," << "x = " << formationOffset.x << ","
<< "y = " << formationOffset.y << "," << "y = " << formationOffset.y << ","
@@ -364,6 +311,8 @@ void AirUnit::AIloop()
break; break;
} }
case State::REFUEL: { case State::REFUEL: {
currentTask = L"Refueling";
if (!hasTask) { if (!hasTask) {
std::wostringstream taskSS; std::wostringstream taskSS;
taskSS << "{" taskSS << "{"
@@ -377,4 +326,6 @@ void AirUnit::AIloop()
default: default:
break; break;
} }
addMeasure(L"currentTask", json::value(currentTask));
addMeasure(L"currentState", json::value(state));
} }

View File

@@ -142,14 +142,34 @@ void Scheduler::handleRequest(wstring key, json::value value)
unit->setTargetID(targetID); unit->setTargetID(targetID);
unit->setState(State::ATTACK); 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 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* unit = unitsManager->getUnit(ID);
Unit* target = unitsManager->getUnit(targetID);
wstring unitName;
wstring targetName;
if (unit != nullptr) if (unit != nullptr)
unit->setState(State::REACH_DESTINATION); unitName = unit->getUnitName();
else else
return; 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) else if (key.compare(L"changeSpeed") == 0)
{ {

View File

@@ -134,7 +134,7 @@ json::value Unit::getData(long long time)
/********** Task data **********/ /********** Task data **********/
json[L"taskData"] = json::value::object(); json[L"taskData"] = json::value::object();
for (auto key : { L"currentTask", L"targetSpeed", L"targetAltitude", L"activePath" }) for (auto key : { 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) if (measures.find(key) != measures.end() && measures[key]->getTime() > time)
json[L"taskData"][key] = measures[key]->getValue(); json[L"taskData"][key] = measures[key]->getValue();

View File

@@ -105,6 +105,6 @@ int dostring_in(lua_State* L, string target, string command)
int TACANChannelToFrequency(int channel, wstring XY) int TACANChannelToFrequency(int channel, wstring XY)
{ {
int basef = (XY == L"X" && channel > 63) || (XY == L"Y" && channel < 64) ? 1087000000 : 961000000; int basef = (XY == L"X" && channel > 63) || (XY == L"Y" && channel < 64) ? 1087: 961;
return basef + 1000000 * channel; return (basef + channel) * 1000000;
} }