Started migration to TypeScript

This commit is contained in:
Pax1601 2023-01-08 10:05:10 +01:00
parent 036f70db34
commit ca6b66eb33
55 changed files with 1429 additions and 662 deletions

View File

@ -1,39 +1,81 @@
Olympus = {}
Olympus.unitCounter = 1
Olympus.payloadRegistry = {}
function Olympus.notify(message, displayFor)
trigger.action.outText(message, displayFor)
end
function Olympus.move(unitName, lat, lng, altitude, speed, category, targetName)
Olympus.notify("Olympus.move " .. unitName .. " (" .. lat .. ", " .. lng ..") " .. altitude .. "m " .. speed .. "m/s " .. category .. " target " .. targetName, 10)
local unit = Unit.getByName(unitName)
if unit ~= nil then
-- Gets a unit class reference from a given ObjectID (the ID used by Olympus for unit referencing)
function Olympus.getUnitByID(ID)
for name, table in pairs(mist.DBs.unitsByName) do
local unit = Unit.getByName(name)
if unit and unit:getObjectID() == ID then
return unit
end
end
return nil
end
function Olympus.getCountryIDByCoalition(coalition)
local countryID = 0
if coalition == 'red' then
countryID = country.id.RUSSIA
else
countryID = country.id.USA
end
return countryID
end
function Olympus.getCoalitionByCoalitionID(coalitionID)
local coalition = "neutral"
if coalitionID == 1 then
coalition = "red"
elseif coalitionID == 2 then
coalition = "blue"
end
return coalition
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'] == 'EngageUnit' and options['targetID'] ~= nil then
local target = Olympus.getUnitByID(options['targetID'])
if target and target:isExist() then
task = {
id = 'EngageUnit',
params = {
unitId = options['targetID'],
}
}
end
end
return task
end
-- Move a unit. Since most tasks in DCS are Enroute tasks, this function is the main way to control the unit AI
function Olympus.move(ID, lat, lng, altitude, speed, category, taskOptions)
Olympus.notify("Olympus.move " .. ID .. " (" .. lat .. ", " .. lng ..") " .. altitude .. "m " .. speed .. "m/s " .. category, 2)
local unit = Olympus.getUnitByID(ID)
if unit then
if category == "Aircraft" then
local startPoint = mist.getLeadPos(unit:getGroup())
local endPoint = coord.LLtoLO(lat, lng, 0)
local task = nil
if targetName ~= "" then
targetID = Unit.getByName(targetName):getID()
task = {
id = 'EngageUnit',
params = {
unitId = targetID,
}
}
end
local path = {
[1] = mist.fixedWing.buildWP(startPoint, flyOverPoint, speed, altitude, 'BARO'),
[2] = mist.fixedWing.buildWP(endPoint, turningPoint, speed, altitude, 'BARO')
}
local path = {}
path[#path + 1] = mist.fixedWing.buildWP(startPoint, flyOverPoint, speed, altitude, 'BARO')
if task ~= nil then
path[#path].task = task
end
path[#path + 1] = mist.fixedWing.buildWP(endPoint, turningPoint, speed, altitude, 'BARO')
if task ~= nil then
path[#path].task = task
-- If a task exists assign it to the controller
local task = Olympus.buildTask(taskOptions)
if task then
path[1].task = task
path[2].task = task
end
-- Assign the mission task to the controller
local missionTask = {
id = 'Mission',
params = {
@ -47,7 +89,7 @@ function Olympus.move(unitName, lat, lng, altitude, speed, category, targetName)
if groupCon then
groupCon:setTask(missionTask)
end
Olympus.notify("Olympus.move executed succesfully on a air unit", 10)
Olympus.notify("Olympus.move executed successfully on a Aircraft", 2)
elseif category == "GroundUnit" then
vars =
{
@ -59,17 +101,18 @@ function Olympus.move(unitName, lat, lng, altitude, speed, category, targetName)
disableRoads = true
}
mist.groupToRandomPoint(vars)
Olympus.notify("Olympus.move executed succesfully on a ground unit", 10)
Olympus.notify("Olympus.move executed succesfully on a ground unit", 2)
else
Olympus.notify("Olympus.move not implemented yet for " .. category, 10)
Olympus.notify("Olympus.move not implemented yet for " .. category, 2)
end
else
Olympus.notify("Error in Olympus.move " .. unitName, 10)
Olympus.notify("Error in Olympus.move " .. unitName, 2)
end
end
-- Creates a simple smoke on the ground
function Olympus.smoke(color, lat, lng)
Olympus.notify("Olympus.smoke " .. color .. " (" .. lat .. ", " .. lng ..")", 10)
Olympus.notify("Olympus.smoke " .. color .. " (" .. lat .. ", " .. lng ..")", 2)
local colorEnum = nil
if color == "green" then
colorEnum = trigger.smokeColor.Green
@ -85,14 +128,15 @@ function Olympus.smoke(color, lat, lng)
trigger.action.smoke(mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0)), colorEnum)
end
function Olympus.spawnGround(coalition, type, lat, lng, ID)
Olympus.notify("Olympus.spawnGround " .. coalition .. " " .. type .. " (" .. lat .. ", " .. lng ..")", 10)
-- Spawns a single ground unit
function Olympus.spawnGroundUnit(coalition, unitType, lat, lng)
Olympus.notify("Olympus.spawnGroundUnit " .. coalition .. " " .. unitType .. " (" .. lat .. ", " .. lng ..")", 2)
local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0))
local unitTable =
{
[1] =
{
["type"] = type,
["type"] = unitType,
["x"] = spawnLocation.x,
["y"] = spawnLocation.z,
["playerCanDrive"] = true,
@ -100,13 +144,8 @@ function Olympus.spawnGround(coalition, type, lat, lng, ID)
},
}
local countryID = nil
if coalition == 'red' then
countryID = country.id.RUSSIA
else
countryID = country.id.USA
end
local countryID = Olympus.getCountryIDByCoalition(coalition)
local vars =
{
units = unitTable,
@ -116,17 +155,31 @@ function Olympus.spawnGround(coalition, type, lat, lng, ID)
}
mist.dynAdd(vars)
Olympus.unitCounter = Olympus.unitCounter + 1
Olympus.notify("Olympus.spawnGround completed succesfully", 10)
Olympus.notify("Olympus.spawnGround completed succesfully", 2)
end
function Olympus.spawnAir(coalition, unitType, lat, lng, payloadName)
local alt = 5000
Olympus.notify("Olympus.spawnAir " .. coalition .. " " .. unitType .. " (" .. lat .. ", " .. lng ..") " .. payloadName, 10)
-- Spawns a single aircraft. Spawn options are:
-- payloadName: a string, one of the names defined in unitPayloads.lua. Must be compatible with the unitType
-- airbaseName: a string, if present the aircraft will spawn on the ground of the selected airbase
-- payload: a table, if present the unit will receive this specific payload. Overrides payloadName
function Olympus.spawnAircraft(coalition, unitType, lat, lng, spawnOptions)
local payloadName = spawnOptions["payloadName"]
local airbaseName = spawnOptions["airbaseName"]
local payload = spawnOptions["payload"]
Olympus.notify("Olympus.spawnAircraft " .. coalition .. " " .. unitType .. " (" .. lat .. ", " .. lng ..")", 2)
local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0))
local payload = {}
if Olympus.unitPayloads[unitType][payloadName] ~= nil then
payload = Olympus.unitPayloads[unitType][payloadName]
if payload == nil then
if payloadName and payloadName ~= "" and Olympus.unitPayloads[unitType][payloadName] ~= nil then
payload = Olympus.unitPayloads[unitType][payloadName]
else
payload = {}
end
end
local countryID = Olympus.getCountryIDByCoalition(coalition)
local unitTable =
{
[1] =
@ -134,12 +187,10 @@ function Olympus.spawnAir(coalition, unitType, lat, lng, payloadName)
["type"] = unitType,
["x"] = spawnLocation.x,
["y"] = spawnLocation.z,
["alt"] = alt,
["skill"] = "Excellent",
["payload"] =
{
["pylons"] = payload,
["fuel"] = 4900,
["flare"] = 60,
["ammo_type"] = 1,
["chaff"] = 60,
@ -151,31 +202,97 @@ function Olympus.spawnAir(coalition, unitType, lat, lng, payloadName)
[1] = 1,
[2] = 1,
[3] = 1,
["name"] = "Enfield11",
["name"] = "Olympus" .. Olympus.unitCounter,
},
["name"] = "Olympus-" .. Olympus.unitCounter
},
}
local countryID = nil
if coalition == 'red' then
countryID = country.id.RUSSIA
else
countryID = country.id.USA
-- If a airbase is provided the first waypoint is set as a From runway takeoff.
local route = {}
if airbaseName and airbaseName ~= "" then
local airbase = Airbase.getByName(airbaseName)
if airbase then
local airbaseID = airbase:getID()
route =
{
["points"] =
{
[1] =
{
["action"] = "From Runway",
["task"] =
{
["id"] = "ComboTask",
["params"] = {["tasks"] = {},},
},
["type"] = "TakeOff",
["ETA"] = 0,
["ETA_locked"] = true,
["x"] = spawnLocation.x,
["y"] = spawnLocation.z,
["formation_template"] = "",
["airdromeId"] = airbaseID,
["speed_locked"] = true,
},
},
}
end
end
local vars =
{
units = unitTable,
country = countryID,
category = 'airplane',
task = "CAP",
tasks = {},
name = "Olympus-" .. Olympus.unitCounter,
route = route,
task = 'CAP',
}
mist.dynAdd(vars)
-- Save the payload to be reused in case the unit is cloned. TODO: save by ID not by name (it works but I like consistency)
Olympus.payloadRegistry[vars.name] = payload
Olympus.unitCounter = Olympus.unitCounter + 1
Olympus.notify("Olympus.spawnAir completed succesfully", 10)
Olympus.notify("Olympus.spawnAir completed successfully", 2)
end
Olympus.notify("OlympusCommand script loaded correctly", 10)
-- Clones a unit by ID. Will clone the unit with the same original payload as the source unit. TODO: only works on Olympus unit not ME units.
function Olympus.clone(ID)
Olympus.notify("Olympus.clone " .. ID, 2)
local unit = Olympus.getUnitByID(ID)
if unit then
local coalition = Olympus.getCoalitionByCoalitionID(unit:getCoalition())
local lat, lng, alt = coord.LOtoLL(unit:getPoint())
-- TODO: only works on Aircraft
local spawnOptions = {
payload = Olympus.payloadRegistry[unitName]
}
Olympus.spawnAircraft(coalition, unit:getTypeName(), lat + 0.001, lng + 0.001, spawnOptions)
end
Olympus.notify("Olympus.clone completed successfully", 2)
end
function Olympus.follow(leaderID, ID)
Olympus.notify("Olympus.follow " .. ID .. " " .. leaderID, 2)
local leader = Olympus.getUnitByID(leaderID)
local unit = Olympus.getUnitByID(ID)
local followTask = {
id = 'Follow',
params = {
groupId = leader:getGroup():getID(),
pos = {x = 0 , y = 0, z = 20} ,
lastWptIndexFlag = false,
lastWptIndex = 1
}
}
Olympus.notify("Olympus.follow group ID" .. unit:getGroup():getID(), 2)
unit:getGroup():getController():pushTask(followTask)
Olympus.notify("Olympus.follow completed successfully", 2)
end
Olympus.notify("OlympusCommand script loaded successfully", 2)

View File

@ -11,8 +11,6 @@ function Olympus.setMissionData(arg, time)
local bullseyeVec3 = coalition.getMainRefPoint(0)
local bullseyeLatitude, bullseyeLongitude, bullseyeAltitude = coord.LOtoLL(bullseyeVec3)
local bullseye = {}
bullseye["x"] = bullseyeVec3.x
bullseye["y"] = bullseyeVec3.z
bullseye["lat"] = bullseyeLatitude
bullseye["lng"] = bullseyeLongitude
@ -41,9 +39,26 @@ function Olympus.setMissionData(arg, time)
end
end
-- Airbases data
local base = world.getAirbases()
local basesData = {}
for i = 1, #base do
local info = {}
local latitude, longitude, altitude = coord.LOtoLL(Airbase.getPoint(base[i]))
info["callsign"] = Airbase.getCallsign(base[i])
info["coalition"] = Airbase.getCoalition(base[i])
info["lat"] = latitude
info["lng"] = longitude
if Airbase.getUnit(base[i]) then
info["unitId"] = Airbase.getUnit(base[i]):getID()
end
basesData[i] = info
end
-- Assemble missionData table
missionData["bullseye"] = bullseye
missionData["unitsData"] = unitsData
missionData["airbases"] = basesData
local command = "Olympus.missionData = " .. Olympus.serializeTable(missionData) .. "\n" .. "Olympus.OlympusDLL.setMissionData()"
net.dostring_in("export", command)
@ -55,8 +70,6 @@ function Olympus.serializeTable(val, name, skipnewlines, depth)
depth = depth or 0
local tmp = string.rep(" ", depth)
if name then
if type(name) == "number" then
tmp = tmp .. "[" .. name .. "]" .. " = "
@ -67,11 +80,9 @@ function Olympus.serializeTable(val, name, skipnewlines, depth)
if type(val) == "table" then
tmp = tmp .. "{" .. (not skipnewlines and "\n" or "")
for k, v in pairs(val) do
tmp = tmp .. Olympus.serializeTable(v, k, skipnewlines, depth + 1) .. "," .. (not skipnewlines and "\n" or "")
end
tmp = tmp .. string.rep(" ", depth) .. "}"
elseif type(val) == "number" then
tmp = tmp .. tostring(val)

8
scripts/server.py Normal file
View File

@ -0,0 +1,8 @@
try:
# Python 2
from SimpleHTTPServer import test, SimpleHTTPRequestHandler
except ImportError:
# Python 3
from http.server import test, SimpleHTTPRequestHandler
test(SimpleHTTPRequestHandler)

44
scripts/server.spec Normal file
View File

@ -0,0 +1,44 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['server.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='server',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)

View File

@ -8,7 +8,7 @@ namespace CommandPriority {
};
namespace CommandType {
enum CommandTypes { NO_TYPE, MOVE, SMOKE, LASE, EXPLODE, SPAWN_AIR, SPAWN_GROUND };
enum CommandTypes { NO_TYPE, MOVE, SMOKE, LASE, EXPLODE, SPAWN_AIR, SPAWN_GROUND, CLONE, LAND, REFUEL, FOLLOW };
};
/* Base command class */
@ -17,7 +17,7 @@ class Command
public:
int getPriority() { return priority; }
int getType() { return type; }
virtual void execute(lua_State* L) = 0;
virtual wstring getString(lua_State* L) = 0;
protected:
int priority = CommandPriority::LOW;
@ -28,28 +28,26 @@ protected:
class MoveCommand : public Command
{
public:
MoveCommand(int ID, wstring unitName, Coords destination, double speed, double altitude, wstring unitCategory, wstring targetName):
ID(ID),
unitName(unitName),
MoveCommand(int ID, Coords destination, double speed, double altitude, wstring unitCategory, wstring taskOptions):
ID(ID),
destination(destination),
speed(speed),
altitude(altitude),
unitCategory(unitCategory),
targetName(targetName)
taskOptions(taskOptions)
{
priority = CommandPriority::LOW;
priority = CommandPriority::HIGH;
type = CommandType::MOVE;
};
virtual void execute(lua_State* L);
virtual wstring getString(lua_State* L);
private:
const int ID;
const wstring unitName;
const Coords destination;
const wstring unitCategory;
const double speed;
const double altitude;
const wstring targetName;
const wstring taskOptions;
};
/* Smoke command */
@ -63,7 +61,7 @@ public:
priority = CommandPriority::LOW;
type = CommandType::SMOKE;
};
virtual void execute(lua_State* L);
virtual wstring getString(lua_State* L);
private:
const wstring color;
@ -71,10 +69,10 @@ private:
};
/* Spawn ground unit command */
class SpawnGroundCommand : public Command
class SpawnGroundUnitCommand : public Command
{
public:
SpawnGroundCommand(wstring coalition, wstring unitType, Coords location) :
SpawnGroundUnitCommand(wstring coalition, wstring unitType, Coords location) :
coalition(coalition),
unitType(unitType),
location(location)
@ -82,7 +80,7 @@ public:
priority = CommandPriority::LOW;
type = CommandType::SPAWN_GROUND;
};
virtual void execute(lua_State* L);
virtual wstring getString(lua_State* L);
private:
const wstring coalition;
@ -91,23 +89,60 @@ private:
};
/* Spawn air unit command */
class SpawnAirCommand : public Command
class SpawnAircraftCommand : public Command
{
public:
SpawnAirCommand(wstring coalition, wstring unitType, Coords location, wstring payloadName) :
SpawnAircraftCommand(wstring coalition, wstring unitType, Coords location, wstring payloadName, wstring airbaseName) :
coalition(coalition),
unitType(unitType),
location(location),
payloadName(payloadName)
payloadName(payloadName),
airbaseName(airbaseName)
{
priority = CommandPriority::LOW;
type = CommandType::SPAWN_AIR;
};
virtual void execute(lua_State* L);
virtual wstring getString(lua_State* L);
private:
const wstring coalition;
const wstring unitType;
const Coords location;
const wstring payloadName;
const wstring airbaseName;
};
/* Clone unit command */
class CloneCommand : public Command
{
public:
CloneCommand(int ID) :
ID(ID)
{
priority = CommandPriority::LOW;
type = CommandType::CLONE;
};
virtual wstring getString(lua_State* L);
private:
const int ID;
};
/* Follow command */
class FollowCommand : public Command
{
public:
FollowCommand(int leaderID, int ID) :
leaderID(leaderID),
ID(ID)
{
priority = CommandPriority::LOW;
type = CommandType::FOLLOW;
};
virtual wstring getString(lua_State* L);
private:
const int leaderID;
const int ID;
};

View File

@ -14,26 +14,27 @@ public:
~Unit();
void update(json::value json);
json::value json();
void setPath(list<Coords> path);
void setActiveDestination(Coords newActiveDestination) { activeDestination = newActiveDestination; }
void setAlive(bool newAlive) { alive = newAlive; }
void setTarget(int targetID);
wstring getTarget();
wstring getCurrentTask();
void resetActiveDestination();
void setLeader(bool newLeader) { leader = newLeader; }
void setWingman(bool newWingman) { wingman = newWingman; }
void setWingmen(vector<Unit*> newWingmen) { wingmen = newWingmen; }
void setFormation(wstring newFormation) { formation = newFormation; }
virtual void changeSpeed(wstring change) {};
virtual void changeAltitude(wstring change) {};
virtual double getTargetSpeed() { return targetSpeed; };
virtual double getTargetAltitude() { return targetAltitude; };
void resetActiveDestination();
int getID() { return ID; }
wstring getName() { return name; }
wstring getUnitName() { return unitName; }
wstring getGroupName() { return groupName; }
json::value getType() { return type; } // This functions returns the complete type of the object (Level1, Level2, Level3, Level4)
json::value getType() { return type; } // This function returns the complete type of the object (Level1, Level2, Level3, Level4)
int getCountry() { return country; }
int getCoalitionID() { return coalitionID; }
double getLatitude() { return latitude; }
@ -42,34 +43,40 @@ public:
double getHeading() { return heading; }
json::value getFlags() { return flags; }
Coords getActiveDestination() { return activeDestination; }
virtual wstring getCategory() { return L"No category"; };
json::value json();
wstring getTarget();
wstring getCurrentTask() { return currentTask; }
virtual double getTargetSpeed() { return targetSpeed; };
virtual double getTargetAltitude() { return targetAltitude; };
protected:
int ID;
bool AI = false;
bool alive = true;
wstring name = L"undefined";
wstring unitName = L"undefined";
wstring groupName = L"undefined";
json::value type = json::value::null();
int country = NULL;
int coalitionID = NULL;
double latitude = NULL;
double longitude = NULL;
double altitude = NULL;
double heading = NULL;
double speed = NULL;
json::value flags = json::value::null();
Coords oldPosition = Coords(0); // Used to approximate speed
int targetID = NULL;
bool holding = false;
bool looping = false;
double targetSpeed = 0;
double targetAltitude = 0;
bool AI = false;
bool alive = true;
wstring name = L"undefined";
wstring unitName = L"undefined";
wstring groupName = L"undefined";
json::value type = json::value::null();
int country = NULL;
int coalitionID = NULL;
double latitude = NULL;
double longitude = NULL;
double altitude = NULL;
double heading = NULL;
double speed = NULL;
json::value flags = json::value::null();
Coords oldPosition = Coords(0); // Used to approximate speed
int targetID = NULL;
bool holding = false;
bool looping = false;
wstring taskOptions = L"{}";
wstring currentTask = L"";
bool leader = false;
bool wingman = false;
wstring formation = L"";
vector<Unit*> wingmen;
double targetSpeed = 0;
double targetAltitude = 0;
list<Coords> activePath;
Coords activeDestination = Coords(0);

View File

@ -1,4 +1,13 @@
#pragma once
#include "framework.h"
#define PROTECTED_CALL "Olympus = {}\n \
function Olympus.protectedCall(...)\n\n \
local status, retval = pcall(...)\n \
if not status then\n \
trigger.action.outText(\"ERROR: \" ..retval, 20)\n \
end\n \
end\n \
trigger.action.outText(\"Olympus.protectedCall registered successfully\", 10)\n"
void registerLuaFunctions(lua_State* L);

View File

@ -1,86 +1,87 @@
#include "commands.h"
#include "logger.h"
#include "dcstools.h"
/* Move command */
void MoveCommand::execute(lua_State* L)
wstring MoveCommand::getString(lua_State* L)
{
std::ostringstream command;
command.precision(10);
command << "Olympus.move(\"" << to_string(unitName) << "\", " << destination.lat << ", " << destination.lng << ", " << altitude << ", " << speed << ", \"" << to_string(unitCategory) << "\", \"" << to_string(targetName) << "\")";
lua_getglobal(L, "net");
lua_getfield(L, -1, "dostring_in");
lua_pushstring(L, "server");
lua_pushstring(L, command.str().c_str());
if (lua_pcall(L, 2, 0, 0) != 0)
{
log("Error executing MoveCommand");
}
else
{
log("MoveCommand executed successfully");
}
std::wostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.move, "
<< ID << ", "
<< destination.lat << ", "
<< destination.lng << ", "
<< altitude << ", "
<< speed << ", "
<< "\"" << unitCategory << "\"" << ", "
<< taskOptions;
return commandSS.str();
}
/* Smoke command */
void SmokeCommand::execute(lua_State* L)
wstring SmokeCommand::getString(lua_State* L)
{
std::ostringstream command;
command.precision(10);
command << "Olympus.smoke(\"" << to_string(color) << "\", " << location.lat << ", " << location.lng << ")";
lua_getglobal(L, "net");
lua_getfield(L, -1, "dostring_in");
lua_pushstring(L, "server");
lua_pushstring(L, command.str().c_str());
if (lua_pcall(L, 2, 0, 0) != 0)
{
log("Error executing SmokeCommand");
}
else
{
log("SmokeCommand executed successfully");
}
std::wostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.smoke, "
<< "\"" << color << "\"" << ", "
<< location.lat << ", "
<< location.lng;
return commandSS.str();
}
/* Spawn ground command */
void SpawnGroundCommand::execute(lua_State* L)
wstring SpawnGroundUnitCommand::getString(lua_State* L)
{
std::ostringstream command;
command.precision(10);
command << "Olympus.spawnGround(\"" << to_string(coalition) << "\", \"" << to_string(unitType) << "\", " << location.lat << ", " << location.lng << ")";
lua_getglobal(L, "net");
lua_getfield(L, -1, "dostring_in");
lua_pushstring(L, "server");
lua_pushstring(L, command.str().c_str());
if (lua_pcall(L, 2, 0, 0) != 0)
{
log("Error executing SpawnGroundCommand");
}
else
{
log("SpawnGroundCommand executed successfully");
}
std::wostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.spawnGroundUnit, "
<< "\"" << coalition << "\"" << ", "
<< "\"" << unitType << "\"" << ", "
<< location.lat << ", "
<< location.lng;
return commandSS.str();
}
/* Spawn air command */
void SpawnAirCommand::execute(lua_State* L)
wstring SpawnAircraftCommand::getString(lua_State* L)
{
std::ostringstream command;
command.precision(10);
command << "Olympus.spawnAir(\"" << to_string(coalition) << "\", \"" << to_string(unitType) << "\", " << location.lat << ", " << location.lng << "," << "\"" << to_string(payloadName) << "\")";
std::wostringstream optionsSS;
optionsSS.precision(10);
optionsSS << "{"
<< "payloadName = \"" << payloadName << "\", "
<< "airbaseName = \"" << airbaseName << "\","
<< "}";
lua_getglobal(L, "net");
lua_getfield(L, -1, "dostring_in");
lua_pushstring(L, "server");
lua_pushstring(L, command.str().c_str());
if (lua_pcall(L, 2, 0, 0) != 0)
{
log("Error executing SpawnAirCommand");
}
else
{
log("SpawnAirCommand executed successfully");
}
std::wostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.spawnAircraft, "
<< "\"" << coalition << "\"" << ", "
<< "\"" << unitType << "\"" << ", "
<< location.lat << ", "
<< location.lng << ","
<< optionsSS.str();
return commandSS.str();
}
/* Clone unit command */
wstring CloneCommand::getString(lua_State* L)
{
std::wostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.clone, "
<< ID;
return commandSS.str();
}
/* Follow unit command */
wstring FollowCommand::getString(lua_State* L)
{
std::wostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.follow, "
<< leaderID << ","
<< ID;
return commandSS.str();
}

View File

@ -33,42 +33,15 @@ void Scheduler::execute(lua_State* L)
{
if (command->getPriority() == priority)
{
log("Executing command");
switch (command->getType())
wstring commandString = L"Olympus.protectedCall(" + command->getString(L) + L")";
if (dostring_in(L, "server", to_string(commandString)))
{
case CommandType::MOVE:
{
MoveCommand* moveCommand = dynamic_cast<MoveCommand*>(command);
moveCommand->execute(L);
commands.remove(command);
break;
}
case CommandType::SMOKE:
{
SmokeCommand* smokeCommand = dynamic_cast<SmokeCommand*>(command);
smokeCommand->execute(L);
commands.remove(command);
break;
}
case CommandType::SPAWN_GROUND:
{
SpawnGroundCommand* spawnCommand = dynamic_cast<SpawnGroundCommand*>(command);
spawnCommand->execute(L);
commands.remove(command);
break;
}
case CommandType::SPAWN_AIR:
{
SpawnAirCommand* spawnCommand = dynamic_cast<SpawnAirCommand*>(command);
spawnCommand->execute(L);
commands.remove(command);
break;
}
default:
log("Unknown command of type " + to_string(command->getType()));
commands.remove(command);
break;
log(L"Error executing command " + commandString);
}
{
log(L"Command " + commandString + L" executed succesfully");
}
commands.remove(command);
return;
}
}
@ -130,7 +103,7 @@ void Scheduler::handleRequest(wstring key, json::value value)
double lng = value[L"location"][L"lng"].as_double();
log(L"Spawning " + coalition + L" ground unit of type " + type + L" at (" + to_wstring(lat) + L", " + to_wstring(lng) + L")");
Coords loc; loc.lat = lat; loc.lng = lng;
command = dynamic_cast<Command*>(new SpawnGroundCommand(coalition, type, loc));
command = dynamic_cast<Command*>(new SpawnGroundUnitCommand(coalition, type, loc));
}
else if (key.compare(L"spawnAir") == 0)
{
@ -140,15 +113,16 @@ void Scheduler::handleRequest(wstring key, json::value value)
double lng = value[L"location"][L"lng"].as_double();
Coords loc; loc.lat = lat; loc.lng = lng;
wstring payloadName = value[L"payloadName"].as_string();
log(L"Spawning " + coalition + L" air unit of type " + type + L" with payload " + payloadName + L" at (" + to_wstring(lat) + L", " + to_wstring(lng) + L")");
command = dynamic_cast<Command*>(new SpawnAirCommand(coalition, type, loc, payloadName));
wstring airbaseName = value[L"airbaseName"].as_string();
log(L"Spawning " + coalition + L" air unit of type " + type + L" with payload " + payloadName + L" at (" + to_wstring(lat) + L", " + to_wstring(lng) + L" " + airbaseName + L")");
command = dynamic_cast<Command*>(new SpawnAircraftCommand(coalition, type, loc, payloadName, airbaseName));
}
else if (key.compare(L"attackUnit") == 0)
{
int unitID = value[L"unitID"].as_integer();
int ID = value[L"ID"].as_integer();
int targetID = value[L"targetID"].as_integer();
Unit* unit = unitsFactory->getUnit(unitID);
Unit* unit = unitsFactory->getUnit(ID);
Unit* target = unitsFactory->getUnit(targetID);
wstring unitName;
@ -193,6 +167,43 @@ void Scheduler::handleRequest(wstring key, json::value value)
unit->changeAltitude(value[L"change"].as_string());
}
}
else if (key.compare(L"cloneUnit") == 0)
{
int ID = value[L"ID"].as_integer();
command = dynamic_cast<Command*>(new CloneCommand(ID));
log(L"Cloning unit " + to_wstring(ID));
}
else if (key.compare(L"setLeader") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsFactory->getUnit(ID);
json::value wingmenIDs = value[L"wingmenIDs"];
vector<Unit*> wingmen;
if (unit != nullptr)
{
for (auto itr = wingmenIDs.as_array().begin(); itr != wingmenIDs.as_array().end(); itr++)
{
Unit* wingman = unitsFactory->getUnit(itr->as_integer());
if (wingman != nullptr)
{
wingman->setWingman(true);
wingmen.push_back(wingman);
log(L"Setting " + wingman->getName() + L" as wingman leader");
}
}
unit->setWingmen(wingmen);
unit->setLeader(true);
unit->resetActiveDestination();
log(L"Setting " + unit->getName() + L" as formation leader");
}
}
else if (key.compare(L"setFormation") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsFactory->getUnit(ID);
wstring formation = value[L"formation"].as_string();
unit->setFormation(formation);
}
else
{
log(L"Unknown command: " + key);

View File

@ -116,59 +116,46 @@ wstring Unit::getTarget()
}
}
wstring Unit::getCurrentTask()
void Unit::AIloop()
{
if (activePath.size() == 0)
// For wingman units, the leader decides the active destination
if (!wingman)
{
return L"Idle";
}
else
{
if (getTarget().empty())
/* Set the active destination to be always equal to the first point of the active path. This is in common with all AI units */
if (activePath.size() > 0)
{
if (looping)
if (activeDestination != activePath.front())
{
return L"Looping";
}
else if (holding)
{
return L"Holding";
}
else
{
return L"Reaching destination";
activeDestination = activePath.front();
Command* command = dynamic_cast<Command*>(new MoveCommand(ID, activeDestination, getTargetSpeed(), getTargetAltitude(), getCategory(), taskOptions));
scheduler->appendCommand(command);
if (leader)
{
for (auto itr = wingmen.begin(); itr != wingmen.end(); itr++)
{
// Manually set the path and the active destination of the wingmen
(*itr)->setPath(activePath);
(*itr)->setActiveDestination(activeDestination);
Command* command = dynamic_cast<Command*>(new FollowCommand(ID, (*itr)->getID()));
scheduler->appendCommand(command);
}
}
}
}
else
{
return L"Attacking " + getTarget();
if (activeDestination != NULL)
{
log(unitName + L" no more points in active path");
activeDestination = Coords(0); // Set the active path to NULL
currentTask = L"Idle";
}
}
}
}
void Unit::AIloop()
{
/* Set the active destination to be always equal to the first point of the active path. This is in common with all AI units */
if (activePath.size() > 0)
{
if (activeDestination != activePath.front())
{
activeDestination = activePath.front();
Command* command = dynamic_cast<Command*>(new MoveCommand(ID, unitName, activeDestination, getTargetSpeed(), getTargetAltitude(), getCategory(), getTarget()));
scheduler->appendCommand(command);
}
}
else
{
if (activeDestination != NULL)
{
log(unitName + L" no more points in active path");
activeDestination = Coords(0); // Set the active path to NULL
}
}
}
/* This function calls again the MoveCommand to reach the active destination. This is useful to change speed and altitude, for example */
/* This function reset the activation so that the AI lopp will call again the MoveCommand. This is useful to change speed and altitude, for example */
void Unit::resetActiveDestination()
{
log(unitName + L" resetting active destination");
@ -197,6 +184,15 @@ json::value Unit::json()
json[L"flags"] = flags;
json[L"category"] = json::value::string(getCategory());
json[L"currentTask"] = json::value::string(getCurrentTask());
json[L"leader"] = leader;
json[L"wingman"] = wingman;
json[L"formation"] = json::value::string(formation);
int i = 0;
for (auto itr = wingmen.begin(); itr != wingmen.end(); itr++)
{
json[L"wingmenIDs"][i++] = (*itr)->getID();
}
/* Send the active path as a json object */
if (activePath.size() > 0) {
@ -224,6 +220,21 @@ AirUnit::AirUnit(json::value json, int ID) : Unit(json, ID)
void AirUnit::AIloop()
{
if (targetID != 0)
{
std::wostringstream taskOptionsSS;
taskOptionsSS << "{"
<< "id = 'EngageUnit'" << ","
<< "targetID = " << targetID << ","
<< "}";
taskOptions = taskOptionsSS.str();
currentTask = L"Attacking " + getTarget();
}
else
{
currentTask = L"Reaching destination";
}
/* Call the common AI loop */
Unit::AIloop();
@ -257,6 +268,7 @@ void AirUnit::AIloop()
activePath.push_back(point3);
activePath.push_back(Coords(latitude, longitude));
holding = true;
currentTask = L"Holding";
}
}

View File

@ -1,12 +1,39 @@
#include "scriptLoader.h"
#include "logger.h"
#include "luatools.h"
#include "dcstools.h"
#include <algorithm>
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);
}
else
{
log(path + " registered successfully");
}
}
/* 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");
}
char* buf = nullptr;
size_t sz = 0;
if (_dupenv_s(&buf, &sz, "DCSOLYMPUS_PATH") == 0 && buf != nullptr)
@ -20,97 +47,7 @@ void registerLuaFunctions(lua_State* L)
return;
}
{
ifstream f(modLocation + "\\Scripts\\mist_4_4_90.lua");
string str;
log("Reading MIST from " + modLocation + "\\Scripts\\mist_4_4_90.lua");
if (f) {
ostringstream ss;
ss << f.rdbuf();
str = ss.str();
log("MIST read succesfully");
}
else
{
log("Error reading MIST");
return;
}
lua_getglobal(L, "net");
lua_getfield(L, -1, "dostring_in");
lua_pushstring(L, "server");
lua_pushstring(L, str.c_str());
if (lua_pcall(L, 2, 0, 0) != 0)
{
log("Error registering MIST");
}
else
{
log("MIST registered successfully");
}
}
{
ifstream f(modLocation + "\\Scripts\\OlympusCommand.lua");
string str;
log("Reading OlympusCommand.lua from " + modLocation + "\\Scripts\\OlympusCommand.lua");
if (f) {
ostringstream ss;
ss << f.rdbuf();
str = ss.str();
log("OlympusCommand.lua read succesfully");
}
else
{
log("Error reading OlympusCommand.lua");
return;
}
lua_getglobal(L, "net");
lua_getfield(L, -1, "dostring_in");
lua_pushstring(L, "server");
lua_pushstring(L, str.c_str());
if (lua_pcall(L, 2, 0, 0) != 0)
{
log("Error registering OlympusCommand.lua");
}
else
{
log("OlympusCommand.lua registered successfully");
}
}
{
ifstream f(modLocation + "\\Scripts\\unitPayloads.lua");
string str;
log("Reading unitPayloads.lua from " + modLocation + "\\Scripts\\unitPayloads.lua");
if (f) {
ostringstream ss;
ss << f.rdbuf();
str = ss.str();
log("unitPayloads.lua read succesfully");
}
else
{
log("Error reading unitPayloads.lua");
return;
}
lua_getglobal(L, "net");
lua_getfield(L, -1, "dostring_in");
lua_pushstring(L, "server");
lua_pushstring(L, str.c_str());
if (lua_pcall(L, 2, 0, 0) != 0)
{
log("Error registering unitPayloads.lua");
}
else
{
log("unitPayloads.lua registered successfully");
}
}
executeLuaScript(L, modLocation + "\\Scripts\\mist_4_4_90.lua");
executeLuaScript(L, modLocation + "\\Scripts\\OlympusCommand.lua");
executeLuaScript(L, modLocation + "\\Scripts\\unitPayloads.lua");
}

View File

@ -6,6 +6,6 @@ void DllExport LogInfo(lua_State* L, string message);
void DllExport LogWarning(lua_State* L, string message);
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<int, json::value> DllExport getAllUnits(lua_State* L);

View File

@ -92,4 +92,14 @@ map<int, json::value> getAllUnits(lua_State* L)
exit:
STACK_CLEAN;
return units;
}
int dostring_in(lua_State* L, string target, string command)
{
lua_getglobal(L, "net");
lua_getfield(L, -1, "dostring_in");
lua_pushstring(L, target.c_str());
lua_pushstring(L, command.c_str());
return lua_pcall(L, 2, 0, 0);
}

37
www/css/AirbaseMarker.css Normal file
View File

@ -0,0 +1,37 @@
.airbasemarker-container-table {
height: 60px;
width: 60px;
left: -30px;
top: -30px;
border: 1px transparent solid;
position: absolute;
}
.airbasemarker-icon-img {
height: 60px;
width: 60px;
left: 0px;
top: 0px;
display: block;
opacity: 0.8;
position: absolute;
filter: drop-shadow(1px 1px 0 white) drop-shadow(1px -1px 0 white) drop-shadow(-1px 1px 0 white) drop-shadow(-1px -1px 0 white);
}
.airbasemarker-icon-img-blue {
filter: invert(37%) sepia(21%) saturate(7402%) hue-rotate(193deg) brightness(103%) contrast(104%) drop-shadow(1px 1px 0 white) drop-shadow(1px -1px 0 white) drop-shadow(-1px 1px 0 white) drop-shadow(-1px -1px 0 white);
}
.airbasemarker-icon-img-red {
filter: invert(21%) sepia(96%) saturate(4897%) hue-rotate(353deg) brightness(108%) contrast(90%) drop-shadow(1px 1px 0 white) drop-shadow(1px -1px 0 white) drop-shadow(-1px 1px 0 white) drop-shadow(-1px -1px 0 white);
}
.airbasemarker-name-div {
bottom: -20px;
position: absolute;
text-align: center;
font: 800 14px Arial;
white-space: nowrap;
-webkit-text-fill-color: white;
-webkit-text-stroke: 1px;
}

View File

@ -40,3 +40,47 @@ body{
color: rgb(255, 154, 154);
text-shadow: 1px 1px #000, -1px -1px #000, 1px -1px #000, -1px 1px #000;
}
/* The snackbar - position it at the bottom and in the middle of the screen */
#snackbar {
visibility: hidden; /* Hidden by default. Visible on click */
min-width: 250px; /* Set a default minimum width */
background-color: #2d3e50; /* Black background color */
color: #fff; /* White text color */
text-align: center; /* Centered text */
border-radius: 2px; /* Rounded borders */
padding: 16px; /* Padding */
position: fixed; /* Sit on top of the screen */
z-index: 1000; /* Add a z-index if needed */
top: 120px; /* 30px from the bottom */
}
/* Show the snackbar when clicking on a button (class added with JavaScript) */
#snackbar.show {
visibility: visible; /* Show the snackbar */
/* Add animation: Take 0.5 seconds to fade in and out the snackbar.
However, delay the fade out process for 2.5 seconds */
-webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s;
animation: fadein 0.5s, fadeout 0.5s 2.5s;
}
/* Animations to fade the snackbar in and out */
@-webkit-keyframes fadein {
from {top: 0; opacity: 0;}
to {top: 120px; opacity: 1;}
}
@keyframes fadein {
from {top: 0; opacity: 0;}
to {top: 120px; opacity: 1;}
}
@-webkit-keyframes fadeout {
from {top: 120px; opacity: 1;}
to {top: 0; opacity: 0;}
}
@keyframes fadeout {
from {top: 120px; opacity: 1;}
to {top: 0; opacity: 0;}
}

View File

@ -6,3 +6,23 @@
cursor: crosshair;
}
.leaflet-container.move-cursor-enabled {
cursor: crosshair;
}
.leaflet-container.attack-cursor-enabled {
cursor: url("../img/buttons/attack.png") 25 15,auto;
}
.leaflet-marker-icon.attack-cursor-enabled {
cursor: url("../img/buttons/attack.png") 25 15,auto;
}
.leaflet-container.formation-cursor-enabled {
cursor: url("../img/buttons/formation.png") 25 15,auto;
}
.leaflet-marker-icon.formation-cursor-enabled {
cursor: url("../img/buttons/formation.png") 25 15,auto;
}

View File

@ -8,14 +8,14 @@
position: fixed;
z-index: 1000;
left: 10px;
bottom: -80px;
bottom: -102px;
transition: bottom 0.2s;
}
.unit-control-panel {
background-color: #202831;
height: 40px;
width: 400px;
width: 200px;
border: solid white 1px;
font-size: 12px;
position: fixed;
@ -28,7 +28,23 @@
justify-content: space-evenly;
}
.control-panel {
.action-panel {
background-color: #202831;
height: 400px;
width: 40px;
border: solid white 1px;
font-size: 12px;
position: fixed;
z-index: 1000;
left: 10px;
top: 50px;
transition: height 0.2s;
display: flex;
align-content: flex-start;
flex-direction: column;
}
.settings-panel {
background-color: #202831;
height: 40px;
width: 100%;
@ -38,6 +54,19 @@
justify-content: left;
}
.formation-control-panel {
background-color: #202831;
height: 100px;
width: 300px;
border: solid white 1px;
font-size: 12px;
position: fixed;
z-index: 1000;
left: 830px;
bottom: -102px;
transition: bottom 0.2s;
}
.panel-table {
text-shadow: 1px 1px #000, -1px -1px #000, 1px -1px #000, -1px 1px #000;
height: 100%;
@ -70,8 +99,6 @@
display: flex;
align-items: center;
justify-content: space-evenly;
transition: font-size 0.05s;
text-shadow: 1px 1px #000, -1px -1px #000, 1px -1px #000, -1px 1px #000;
}
.panel-button:hover {
@ -80,6 +107,7 @@
.panel-button:active {
color: white;
filter: brightness(110%);
}
.panel-button-disabled {

BIN
www/img/airbase.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
www/img/buttons/attack.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
www/img/buttons/bomb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
www/img/buttons/carpet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
www/img/buttons/land.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

View File

@ -7,6 +7,7 @@
<link rel="stylesheet" href="css/index.css"/>
<link rel="stylesheet" href="css/panels.css"/>
<link rel="stylesheet" href="css/Map.css"/>
<link rel="stylesheet" href="css/AirbaseMarker.css"/>
<link rel="stylesheet" href="css/UnitMarker.css"/>
<link rel="stylesheet" href="css/SelectionWheel.css"/>
<link rel="stylesheet" href="css/SelectionScroll.css"/>
@ -18,36 +19,48 @@
<script src="https://unpkg.com/leaflet@1.9.2/dist/leaflet.js"
integrity="sha256-o9N1jGDZrf5tS+Ft4gbIK7mYMipq9lqpVJ91xHSyKhg="
crossorigin=""></script>
<script src="js/payloadNames.js"></script>
<script src="js/ControlPanel.js"></script>
<script src="js/DCSCommands.js"></script>
<script src="js/Utils.js"></script>
<script src="js/unitTypes.js"></script>
<script src="js/PanelButton.js"></script>
<script src="js/UnitInfoPanel.js"></script>
<script src="js/UnitControlPanel.js"></script>
<script src="js/Map.js"></script>
<script src="js/MissionData.js"></script>
<script src="js/Unit.js"></script>
<script src="js/UnitMarker.js"></script>
<script src="js/UnitsManager.js"></script>
<script src="js/SelectionWheel.js"></script>
<script src="js/SelectionScroll.js"></script>
<script src="js/DCS/payloadNames.js"></script>
<script src="js/DCS/DCSCommands.js"></script>
<script src="js/Panels/PanelButton.js"></script>
<script src="js/Panels/SettingsPanel.js"></script>
<script src="js/Panels/UnitInfoPanel.js"></script>
<script src="js/Panels/UnitControlPanel.js"></script>
<script src="js/Panels/FormationControlPanel.js"></script>
<script src="js/Panels/ActionPanel.js"></script>
<script src="js/Units/unitTypes.js"></script>
<script src="js/Units/Unit.js"></script>
<script src="js/Units/UnitMarker.js"></script>
<script src="js/Units/UnitsManager.js"></script>
<script src="js/Other/Utils.js"></script>
<script src="js/Other/AirbaseMarker.js"></script>
<script src="js/Other/MissionData.js"></script>
<script src="js/Map/Map.js"></script>
<script src="js/Map/SelectionWheel.js"></script>
<script src="js/Map/SelectionScroll.js"></script>
<script src="js/index.js"></script>
</head>
<body>
<table id="content-table">
<tr id="header">
<td>
<div class="control-panel" id="top-control-panel"></div>
<div class="settings-panel" id="settings-panel"></div>
</td>
</tr>
<tr>
<td id="map-container">
<div id="map"></div>
<div id="log"></div>
<div class="unit-info-panel" id="left-panel"></div>
<div class="unit-control-panel" id="top-panel"></div>
<div class="action-panel" id="action-panel"></div>
<div class="unit-info-panel" id="unit-info-panel"></div>
<div class="unit-control-panel" id="unit-control-panel"></div>
<div class="formation-control-panel" id="formation-control-panel"></div>
<div id="snackbar">ASD</div>
</td>
</tr>
</table>

View File

@ -32,7 +32,7 @@ function spawnGroundUnit(type, latlng, coalition)
xhr.send(JSON.stringify(data));
}
function spawnAirUnit(type, latlng, coalition, payloadName)
function spawnAircraft(type, latlng, coalition, payloadName = "", airbaseName = "")
{
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
@ -43,25 +43,42 @@ function spawnAirUnit(type, latlng, coalition, payloadName)
}
};
var command = {"type": type, "location": latlng, "coalition": coalition, "payloadName": payloadName};
var command = {"type": type, "location": latlng, "coalition": coalition, "payloadName": payloadName, "airbaseName": airbaseName};
var data = {"spawnAir": command}
xhr.send(JSON.stringify(data));
}
function attackUnit(unitID, targetID)
function attackUnit(ID, targetID)
{
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
console.log("Unit " + unitsManager.getUnit(unitID).unitName + " attack " + unitsManager.getUnit(targetID).unitName );
console.log("Unit " + unitsManager.getUnitByID(ID).unitName + " attack " + unitsManager.getUnitByID(targetID).unitName );
}
};
var command = {"unitID": unitID, "targetID": targetID};
var command = {"ID": ID, "targetID": targetID};
var data = {"attackUnit": command}
xhr.send(JSON.stringify(data));
}
function cloneUnit(ID)
{
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
console.log("Unit " + unitsManager.getUnitByID(ID).unitName + " cloned");
}
};
var command = {"ID": ID};
var data = {"cloneUnit": command}
xhr.send(JSON.stringify(data));
}

View File

@ -17,7 +17,10 @@ class Map
this._map.on('movestart', () => {this.removeSelectionWheel(); this.removeSelectionScroll();});
this._map.on('zoomstart', () => {this.removeSelectionWheel(); this.removeSelectionScroll();});
this._map.on('selectionend', (e) => unitsManager.selectFromBounds(e.selectionBounds));
this._map.on('keyup', (e) => unitsManager.handleKeyEvent(e));
this._map._container.classList.add("action-cursor");
this.setState("IDLE");
this._selectionWheel = undefined;
@ -52,14 +55,42 @@ class Map
setState(newState)
{
this._state = newState;
var cursorElements = document.getElementsByClassName("action-cursor");
for (let item of cursorElements)
{
item.classList.remove("move-cursor-enabled", "attack-cursor-enabled", "formation-cursor-enabled");
}
if (this._state === "IDLE")
{
L.DomUtil.removeClass(this._map._container, 'move-cursor-enabled');
}
else if (this._state === "UNIT_SELECTED")
else if (this._state === "MOVE_UNIT")
{
L.DomUtil.addClass(this._map._container, 'move-cursor-enabled');
for (let item of cursorElements)
{
item.classList.add("move-cursor-enabled");
}
}
else if (this._state === "ATTACK")
{
for (let item of cursorElements)
{
item.classList.add("attack-cursor-enabled");
}
}
else if (this._state === "FORMATION")
{
for (let item of cursorElements)
{
item.classList.add("formation-cursor-enabled");
}
}
}
getState()
{
return this._state;
}
/* Set the active coalition (for persistency) */
@ -77,6 +108,7 @@ class Map
// Right click
_onContextMenu(e)
{
this.setState("IDLE");
unitsManager.deselectAllUnits();
this.removeSelectionWheel();
this.removeSelectionScroll();
@ -90,7 +122,7 @@ class Map
{
}
else if (this._state === "UNIT_SELECTED")
else if (this._state === "MOVE_UNIT")
{
if (!e.originalEvent.ctrlKey)
{
@ -102,13 +134,16 @@ class Map
_onDoubleClick(e)
{
var options = [
{'tooltip': 'Air unit', 'src': 'spawnAir.png', 'callback': () => this._unitSelectAir(e)},
{'tooltip': 'Ground unit', 'src': 'spawnGround.png', 'callback': () => this._groundSpawnMenu(e)},
{'tooltip': 'Smoke', 'src': 'spawnSmoke.png', 'callback': () => this._smokeSpawnMenu(e)},
{'tooltip': 'Explosion', 'src': 'spawnExplosion.png', 'callback': () => this._explosionSpawnMenu(e)}
]
this._selectionWheel = new SelectionWheel(e.originalEvent.x, e.originalEvent.y, options);
if (this._state == 'IDLE')
{
var options = [
{'tooltip': 'Air unit', 'src': 'spawnAir.png', 'callback': () => this._aircraftSpawnMenu(e)},
{'tooltip': 'Ground unit', 'src': 'spawnGround.png', 'callback': () => this._groundUnitSpawnMenu(e)},
{'tooltip': 'Smoke', 'src': 'spawnSmoke.png', 'callback': () => this._smokeSpawnMenu(e)},
{'tooltip': 'Explosion', 'src': 'spawnExplosion.png', 'callback': () => this._explosionSpawnMenu(e)}
]
this._selectionWheel = new SelectionWheel(e.originalEvent.x, e.originalEvent.y, options);
}
}
/* Selection wheel and selection scroll functions */
@ -130,30 +165,42 @@ class Map
}
}
/* Spawn a new air unit selection wheel (TODO, divide units by type, like bomber, fighter, tanker etc)*/
_airSpawnMenu(e)
/* Show unit selection for air units */
spawnFromAirbase(e)
{
this._selectAircraft(e);
}
/* Spawn a new ground unit selection wheel */
_aircraftSpawnMenu(e)
{
this.removeSelectionWheel();
this.removeSelectionScroll();
var options = [
{'coalition': true, 'tooltip': 'CAP', 'src': 'spawnCAP.png', 'callback': () => this._selectAircraft(e, "CAP")},
{'coalition': true, 'tooltip': 'CAS', 'src': 'spawnCAS.png', 'callback': () => this._selectAircraft(e, "CAS")},
{'coalition': true, 'tooltip': 'Tanker', 'src': 'spawnTanker.png', 'callback': () => this._selectAircraft(e, "tanker")},
{'coalition': true, 'tooltip': 'AWACS', 'src': 'spawnAWACS.png', 'callback': () => this._selectAircraft(e, "awacs")},
{'coalition': true, 'tooltip': 'Strike', 'src': 'spawnStrike.png', 'callback': () => this._selectAircraft(e, "strike")},
{'coalition': true, 'tooltip': 'Drone', 'src': 'spawnDrone.png', 'callback': () => this._selectAircraft(e, "drone")},
{'coalition': true, 'tooltip': 'Transport', 'src': 'spawnTransport.png','callback': () => this._selectAircraft(e, "transport")},
]
this._selectionWheel = new SelectionWheel(e.originalEvent.x, e.originalEvent.y, options);
}
/* Spawn a new ground unit selection wheel */
_groundSpawnMenu(e)
_groundUnitSpawnMenu(e)
{
this.removeSelectionWheel();
this.removeSelectionScroll();
var options = [
{'coalition': true, 'tooltip': 'Howitzer', 'src': 'spawnHowitzer.png', 'callback': () => this._unitSelectGround(e, "Howitzers")},
{'coalition': true, 'tooltip': 'SAM', 'src': 'spawnSAM.png', 'callback': () => this._unitSelectGround(e, "SAM")},
{'coalition': true, 'tooltip': 'IFV', 'src': 'spawnIFV.png', 'callback': () => this._unitSelectGround(e, "IFV")},
{'coalition': true, 'tooltip': 'Tank', 'src': 'spawnTank.png', 'callback': () => this._unitSelectGround(e, "Tanks")},
{'coalition': true, 'tooltip': 'MLRS', 'src': 'spawnMLRS.png', 'callback': () => this._unitSelectGround(e, "MLRS")},
{'coalition': true, 'tooltip': 'Radar', 'src': 'spawnRadar.png', 'callback': () => this._unitSelectGround(e, "Radar")},
{'coalition': true, 'tooltip': 'Unarmed', 'src': 'spawnUnarmed.png', 'callback': () => this._unitSelectGround(e, "Unarmed")}
{'coalition': true, 'tooltip': 'Howitzer', 'src': 'spawnHowitzer.png', 'callback': () => this._selectGroundUnit(e, "Howitzers")},
{'coalition': true, 'tooltip': 'SAM', 'src': 'spawnSAM.png', 'callback': () => this._selectGroundUnit(e, "SAM")},
{'coalition': true, 'tooltip': 'IFV', 'src': 'spawnIFV.png', 'callback': () => this._selectGroundUnit(e, "IFV")},
{'coalition': true, 'tooltip': 'Tank', 'src': 'spawnTank.png', 'callback': () => this._selectGroundUnit(e, "Tanks")},
{'coalition': true, 'tooltip': 'MLRS', 'src': 'spawnMLRS.png', 'callback': () => this._selectGroundUnit(e, "MLRS")},
{'coalition': true, 'tooltip': 'Radar', 'src': 'spawnRadar.png', 'callback': () => this._selectGroundUnit(e, "Radar")},
{'coalition': true, 'tooltip': 'Unarmed', 'src': 'spawnUnarmed.png', 'callback': () => this._selectGroundUnit(e, "Unarmed")}
]
this._selectionWheel = new SelectionWheel(e.originalEvent.x, e.originalEvent.y, options);
}
@ -185,11 +232,11 @@ class Map
}
/* Show unit selection for air units */
_unitSelectAir(e)
_selectAircraft(e, group)
{
this.removeSelectionWheel();
this.removeSelectionScroll();
var options = unitTypes.air;
var options = unitTypes.air[group];
options.sort();
this._selectionScroll = new SelectionScroll(e.originalEvent.x, e.originalEvent.y, options, (unitType) => {
this.removeSelectionWheel();
@ -205,16 +252,23 @@ class Map
this.removeSelectionScroll();
var options = [];
options = payloadNames[unitType]
options.sort();
this._selectionScroll = new SelectionScroll(e.originalEvent.x, e.originalEvent.y, options, (payloadName) => {
this.removeSelectionWheel();
this.removeSelectionScroll();
spawnAirUnit(unitType, e.latlng, this._activeCoalition, payloadName);
});
if (options != undefined && options.length > 0)
{
options.sort();
this._selectionScroll = new SelectionScroll(e.originalEvent.x, e.originalEvent.y, options, (payloadName) => {
this.removeSelectionWheel();
this.removeSelectionScroll();
spawnAircraft(unitType, e.latlng, this._activeCoalition, payloadName, e.airbaseName);
});
}
else
{
spawnAircraft(unitType, e.latlng, this._activeCoalition);
}
}
/* Show unit selection for ground units */
_unitSelectGround(e, group)
_selectGroundUnit(e, group)
{
this.removeSelectionWheel();
this.removeSelectionScroll();

View File

@ -36,6 +36,7 @@ class SelectionWheel
var image = document.createElement("img");
image.classList.add("selection-wheel-image");
image.src = `img/buttons/${this._options[id].src}`
image.title = this._options[id].tooltip;
if ('tint' in this._options[id])
{
button.style.setProperty('background-color', this._options[id].tint);

View File

@ -1,39 +0,0 @@
class MissionData
{
constructor()
{
this._bullseye = undefined;
this._bullseyeMarker = undefined;
}
update(data)
{
this._bullseye = data.missionData.bullseye;
this._unitsData = data.missionData.unitsData;
this._drawBullseye();
}
getUnitData(ID)
{
if (ID in this._unitsData)
{
return this._unitsData[ID];
}
else
{
return undefined;
}
}
_drawBullseye()
{
if (this._bullseyeMarker === undefined)
{
this._bullseyeMarker = L.marker([this._bullseye.lat, this._bullseye.lng]).addTo(map.getMap());
}
else
{
this._bullseyeMarker .setLatLng(new L.LatLng(this._bullseye.lat, this._bullseye.lng));
}
}
}

View File

@ -0,0 +1,53 @@
L.Marker.AirbaseMarker = L.Marker.extend(
{
options: {
name: "No name",
position: undefined,
coalitionID: 2,
iconSrc: "img/airbase.png"
},
// Marker constructor
initialize: function(latlng, options) {
this._latlng = latlng;
if (options != undefined)
{
L.setOptions(this, options);
}
var icon = new L.DivIcon({
html: `<table class="unitmarker-container-table" id="container-table">
<tr>
<td>
<img class="airbasemarker-icon-img" id="icon-img" src="${this.options.iconSrc}">
<div class="airbasemarker-name-div" id="name">${this.options.name}</div>
</td>
</tr>
</table>`,
className: 'airbase-marker-icon'}); // Set the marker, className must be set to avoid white square
this.setIcon(icon);
},
setCoalitionID: function(coalitionID)
{
this.options.coalitionID = coalitionID;
// Set the coalitionID
var img = this._icon.querySelector("#icon-img");
img.classList.remove("airbasemarker-icon-img-blue");
img.classList.remove("airbasemarker-icon-img-red");
if (this.options.coalitionID == 2)
{
img.classList.add("airbasemarker-icon-img-blue");
}
else if (this.options.coalitionID == 1)
{
img.classList.add("airbasemarker-icon-img-red");
}
}
}
)
// By default markers can be hovered and clicked
L.Marker.AirbaseMarker.addInitHook(function()
{
});

View File

@ -0,0 +1,67 @@
class MissionData
{
constructor()
{
this._bullseye = undefined;
this._bullseyeMarker = undefined;
this._airbasesMarkers = {};
}
update(data)
{
this._bullseye = data.missionData.bullseye;
this._unitsData = data.missionData.unitsData;
this._airbases = data.missionData.airbases;
this._drawBullseye();
this._drawAirbases();
}
getUnitData(ID)
{
if (ID in this._unitsData)
{
return this._unitsData[ID];
}
else
{
return undefined;
}
}
_drawBullseye()
{
if (this._bullseyeMarker === undefined)
{
this._bullseyeMarker = new L.Marker([this._bullseye.lat, this._bullseye.lng]).addTo(map.getMap());
}
else
{
this._bullseyeMarker.setLatLng(new L.LatLng(this._bullseye.lat, this._bullseye.lng));
}
}
_drawAirbases()
{
for (let idx in this._airbases)
{
var airbase = this._airbases[idx]
if (this._airbasesMarkers[idx] === undefined)
{
this._airbasesMarkers[idx] = new L.Marker.AirbaseMarker(new L.LatLng(airbase.lat, airbase.lng), {name: airbase.callsign}).addTo(map.getMap());
this._airbasesMarkers[idx].on('click', (e) => this._onAirbaseClick(e));
}
else
{
this._airbasesMarkers[idx].setCoalitionID(airbase.coalition);
this._airbasesMarkers[idx].setLatLng(new L.LatLng(airbase.lat, airbase.lng));
}
}
}
_onAirbaseClick(e)
{
e.airbaseName = e.sourceTarget.options.name;
e.coalitionID = e.sourceTarget.coalitionID;
map.spawnFromAirbase(e);
}
}

View File

@ -0,0 +1,30 @@
class ActionPanel
{
constructor(id)
{
this._panel = document.getElementById(id);
this._attackButton = new PanelButton(this._panel, "img/buttons/attack.png", "Attack unit");
this._bombButton = new PanelButton(this._panel, "img/buttons/bomb.png", "Precision bombing");
this._carpetButton = new PanelButton(this._panel, "img/buttons/carpet.png", "Carpet bombing");
this._landButton = new PanelButton(this._panel, "img/buttons/land.png", "Land here");
this._formationButton = new PanelButton(this._panel, "img/buttons/formation.png", "Create formation");
this._attackButton.addCallback(() => map.setState("ATTACK"));
this._bombButton.addCallback(() => map.setState("BOMB"));
this._carpetButton.addCallback(() => map.setState("CARPET_BOMB"));
this._landButton.addCallback(() => map.setState("LAND"));
this._formationButton.addCallback(() => map.setState("FORMATION"));
this.setEnabled(false);
}
setEnabled(enabled)
{
this._attackButton.setEnabled(enabled);
this._bombButton.setEnabled(false);
this._carpetButton.setEnabled(false);
this._landButton.setEnabled(false);
this._formationButton.setEnabled(enabled);
}
}

View File

@ -0,0 +1,82 @@
class FormationControlPanel
{
constructor(id)
{
this._panel = document.getElementById(id);
this._formations = ["", "Echelon", "Line abreast", "Box", "Trail", "Finger tip", "Tactical line abreast", "Fluid four", "Spread four"];
}
update(selectedUnits)
{
if (selectedUnits.length == 1)
{
// Don't update if user is editing
if (selectedUnits[0].leader && !this._editing)
{
this._panel.style.bottom = "15px";
this._showFormationControls(selectedUnits[0]);
}
}
else
{
this._panel.style.bottom = (-this._panel.offsetHeight - 2) + "px";
this._showFormationControls(); // Empty, cleans the panel
}
}
_showFormationControls(selectedUnit)
{
if (selectedUnit !== undefined)
{
this._panel.innerHTML = `
<div style="display: flex">
<table class="panel-table" id="unit-info-table">
<tr>
<td colspan="4" class="panel-title">
FORMATION CONTROL
</td>
</tr>
<tr>
<td class="panel-label">
Formation:
</td>
<td class="panel-content">
${selectedUnit.formationID}
</td>
</tr>
<tr>
<td class="panel-label">
Formation type:
</td>
<td class="panel-content">
<select id="formation-type-select"></select>
</td>
</tr>
</table>
</div>
`;
var select = document.getElementById("formation-type-select");
for(var i = 0; i < this._formations.length; i++) {
var opt = this._formations[i];
var el = document.createElement("option");
el.textContent = opt;
el.value = opt;
select.appendChild(el);
}
select.addEventListener("focus", () => this._editing = true)
select.addEventListener("blur", () => this._editing = false)
object.addEventListener("change", () => leader.setformation());
select.value = selectedUnit.formation;
}
else
{
this._panel.innerHTML = ``;
}
}
}

View File

@ -1,9 +1,9 @@
class PanelButton
{
constructor(parent, icon)
constructor(parent, icon, tooltip)
{
this._div = document.createElement("div");
this.setIcon(icon);
this.setIcon(icon, tooltip);
this.setSlashed(false);
this._div.classList.add("panel-button");
@ -38,9 +38,16 @@ class PanelButton
this._callbacks = [];
}
setIcon(icon)
setIcon(icon, tooltip)
{
this._baseIcon = `<i class="fa ${icon}"></i>`;
if (icon.includes("png"))
{
this._baseIcon = `<img src="${icon}" title="${tooltip}">`;
}
else
{
this._baseIcon = `<i class="fa ${icon}" title="${tooltip}"></i>`;
}
this._div.innerHTML = this._baseIcon;
}

View File

@ -1,20 +1,20 @@
class ControlPanel
class SettingsPanel
{
constructor(id)
{
this._panel = document.getElementById(id);
/* Create all buttons, disabled by default */
/* Create all buttons, disabled by default */
this._humanIcon = "fa-user";
this._AIIcon = "fa-desktop";
this._weaponsIcon = "fa-bomb";
this._labelsIcon = "fa-font";
this._deadIcon = "fa-skull";
this._humanButton = new PanelButton(this._panel, this._humanIcon);
this._AIButton = new PanelButton(this._panel, this._AIIcon);
this._weaponsButton = new PanelButton(this._panel, this._weaponsIcon);
this._deadAliveButton = new PanelButton(this._panel, this._deadIcon);
this._humanButton = new PanelButton(this._panel, this._humanIcon, "Player visibility");
this._AIButton = new PanelButton(this._panel, this._AIIcon, "AI visibility");
this._weaponsButton = new PanelButton(this._panel, this._weaponsIcon, "Weapons visibility");
this._deadAliveButton = new PanelButton(this._panel, this._deadIcon, "Dead units visibility");
this._humanButton.addCallback(() => this._onHumanButton());
this._AIButton.addCallback(() => this._onAIButton());

View File

@ -0,0 +1,35 @@
class UnitControlPanel
{
constructor(id)
{
this._panel = document.getElementById(id);
/* Create all buttons, disabled by default */
//this._moveButton = new PanelButton(this._panel, "fa-play");
//this._stopButton = new PanelButton(this._panel, "fa-pause");
this._slowButton = new PanelButton(this._panel, "fa-angle-right", "Decelerate");
this._fastButton = new PanelButton(this._panel, "fa-angle-double-right", "Accelerate");
this._descendButton = new PanelButton(this._panel, "fa-arrow-down", "Descend");
this._climbButton = new PanelButton(this._panel, "fa-arrow-up", "Climb");
//this._repeatButton = new PanelButton(this._panel, "fa-undo");
this.setEnabled(false);
//this._moveButton.addCallback(unitsManager.selectedUnitsMove);
//this._stopButton.addCallback(() => unitsManager.selectedUnitsChangeSpeed('stop'));
this._slowButton.addCallback(() => unitsManager.selectedUnitsChangeSpeed('slow'));
this._fastButton.addCallback(() => unitsManager.selectedUnitsChangeSpeed('fast'));
this._descendButton.addCallback(() => unitsManager.selectedUnitsChangeAltitude('descend'));
this._climbButton.addCallback(() => unitsManager.selectedUnitsChangeAltitude('climb'));
}
setEnabled(enabled)
{
//this._moveButton.setEnabled(true);
//this._stopButton.setEnabled(true);
this._slowButton.setEnabled(enabled);
this._fastButton.setEnabled(enabled);
this._descendButton.setEnabled(enabled);
this._climbButton.setEnabled(enabled);
}
}

View File

@ -7,23 +7,15 @@ class UnitInfoPanel
update(selectedUnits)
{
if (selectedUnits.length > 0)
if (selectedUnits.length == 1)
{
this._panel.style.bottom = "15px";
if (selectedUnits.length == 1)
{
this._showUnitData(selectedUnits[0]);
}
else
{
this._showUnitData();
this._panel.style.bottom = "-80px";
}
this._showUnitData(selectedUnits[0]);
}
else
{
this._showUnitData();
this._panel.style.bottom = "-80px";
this._panel.style.bottom = (-this._panel.offsetHeight - 2) + "px";
this._showUnitData(); // Empty, cleans the panel
}
}
@ -40,7 +32,7 @@ class UnitInfoPanel
var displayName = ammo.desc.displayName;
var amount = ammo.count;
loadout += amount + "x" + displayName;
if (parseInt(index) < Object.keys(selectedUnit.missionData.ammo).length - 1)
if (parseInt(index) < Object.keys(selectedUnit.missionData.ammo).length)
{
loadout += ", ";
}
@ -143,15 +135,7 @@ class UnitInfoPanel
}
else
{
this._panel.innerHTML = `
<table class="panel-table">
<tr>
<td class="panel-title">
UNIT INFO
</td>
</tr>
</table>
`;
this._panel.innerHTML = ``;
}
}
}

View File

@ -1,47 +0,0 @@
class UnitControlPanel
{
constructor(id)
{
this._panel = document.getElementById(id);
/* Create all buttons, disabled by default */
this._moveButton = new PanelButton(this._panel, "fa-play");
this._stopButton = new PanelButton(this._panel, "fa-pause");
this._slowButton = new PanelButton(this._panel, "fa-angle-right");
this._fastButton = new PanelButton(this._panel, "fa-angle-double-right");
this._descendButton = new PanelButton(this._panel, "fa-arrow-down");
this._climbButton = new PanelButton(this._panel, "fa-arrow-up");
this._repeatButton = new PanelButton(this._panel, "fa-undo");
this._moveButton.addCallback(unitsManager.selectedUnitsMove);
this._stopButton.addCallback(() => unitsManager.selectedUnitsChangeSpeed('stop'));
this._slowButton.addCallback(() => unitsManager.selectedUnitsChangeSpeed('slow'));
this._fastButton.addCallback(() => unitsManager.selectedUnitsChangeSpeed('fast'));
this._descendButton.addCallback(() => unitsManager.selectedUnitsChangeAltitude('descend'));
this._climbButton.addCallback(() => unitsManager.selectedUnitsChangeAltitude('climb'));
}
enableButtons(enableAltitudeButtons)
{
this._moveButton.setEnabled(true);
this._stopButton.setEnabled(true);
this._slowButton.setEnabled(true);
this._fastButton.setEnabled(true);
if (enableAltitudeButtons)
{
this._descendButton.setEnabled(true);
this._climbButton.setEnabled(true);
}
}
disableButtons()
{
this._moveButton.setEnabled(false);
this._stopButton.setEnabled(false);
this._slowButton.setEnabled(false);
this._fastButton.setEnabled(false);
this._descendButton.setEnabled(false);
this._climbButton.setEnabled(false);
}
}

View File

@ -8,7 +8,6 @@ class Unit
// The marker is set by the inherited class
this.marker = marker;
this.marker.on('click', (e) => this.onClick(e));
this.marker.on('contextmenu', (e) => this.onRightClick(e));
this._selected = false;
@ -18,6 +17,10 @@ class Unit
this._pathPolyline.addTo(map.getMap());
this._targetsPolylines = [];
this.leader = true;
this.wingmen = [];
this.formation = undefined;
}
update(response)
@ -37,6 +40,19 @@ class Unit
this.speed = response["speed"];
this.currentTask = response["currentTask"];
this.leader = response["leader"];
this.wingman = response["wingman"];
this.wingmen = [];
if (response["wingmenIDs"] != undefined)
{
for (let ID of response["wingmenIDs"])
{
this.wingmen.push(unitsManager.getUnitByID(ID));
}
}
this.formation = response["formation"];
this.missionData = missionData.getUnitData(this.ID)
this.setSelected(this.getSelected() & this.alive)
@ -113,22 +129,28 @@ class Unit
onClick(e)
{
if (!e.originalEvent.ctrlKey)
if (map.getState() === 'IDLE' || map.getState() === 'MOVE_UNIT' || e.originalEvent.ctrlKey)
{
unitsManager.deselectAllUnits();
if (!e.originalEvent.ctrlKey)
{
unitsManager.deselectAllUnits();
}
this.setSelected(true);
}
else if (map.getState() === 'ATTACK')
{
unitsManager.attackUnit(this.ID);
}
else if (map.getState() === 'FORMATION')
{
unitsManager.createFormation(this.ID);
}
this.setSelected(true);
}
onRightClick(e)
{
unitsManager.onUnitRightClick(this.ID);
}
drawMarker(settings)
{
// Hide the marker if disabled
if ((settings === 'none' || (controlPanel.getSettings().deadAlive === "alive" && !this.alive)))
if ((settings === 'none' || (settingsPanel.getSettings().deadAlive === "alive" && !this.alive)))
{
// Remove the marker if present
if (map.getMap().hasLayer(this.marker))
@ -204,30 +226,32 @@ class Unit
for (let index in this.missionData.targets[typeIndex])
{
var targetData = this.missionData.targets[typeIndex][index];
var target = unitsManager.getUnit(targetData.object["id_"])
var startLatLng = new L.LatLng(this.latitude, this.longitude)
var endLatLng = new L.LatLng(target.latitude, target.longitude)
var color;
if (typeIndex === "radar")
{
color = "#FFFF00";
var target = unitsManager.getUnitByID(targetData.object["id_"])
if (target != undefined){
var startLatLng = new L.LatLng(this.latitude, this.longitude)
var endLatLng = new L.LatLng(target.latitude, target.longitude)
var color;
if (typeIndex === "radar")
{
color = "#FFFF00";
}
else if (typeIndex === "visual")
{
color = "#FF00FF";
}
else if (typeIndex === "rwr")
{
color = "#00FF00";
}
else
{
color = "#FFFFFF";
}
var targetPolyline = new L.Polyline([startLatLng, endLatLng], {color: color, weight: 3, opacity: 1, smoothFactor: 1});
targetPolyline.addTo(map.getMap());
this._targetsPolylines.push(targetPolyline)
}
else if (typeIndex === "visual")
{
color = "#FF00FF";
}
else if (typeIndex === "rwr")
{
color = "#00FF00";
}
else
{
color = "#FFFFFF";
}
var targetPolyline = new L.Polyline([startLatLng, endLatLng], {color: color, weight: 3, opacity: 1, smoothFactor: 1});
targetPolyline.addTo(map.getMap());
this._targetsPolylines.push(targetPolyline)
}
}
}
@ -243,7 +267,14 @@ class Unit
attackUnit(targetID)
{
// Call DCS attackUnit function
attackUnit(this.ID, targetID);
if (this.ID != targetID)
{
attackUnit(this.ID, targetID);
}
else
{
// TODO: show a message
}
}
changeSpeed(speedChange)
@ -281,19 +312,55 @@ class Unit
xhr.send(JSON.stringify(data));
}
setformation(formation)
{
// TODO move in dedicated file
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
console.log(this.unitName + " formation change: " + formation);
}
};
var command = {"ID": this.ID, "formation": formation}
var data = {"setFormation": command}
xhr.send(JSON.stringify(data));
}
setLeader(wingmenIDs)
{
// TODO move in dedicated file
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
console.log(this.unitName + " created formation with: " + wingmenIDs);
}
};
var command = {"ID": this.ID, "wingmenIDs": wingmenIDs}
var data = {"setLeader": command}
xhr.send(JSON.stringify(data));
}
}
class AirUnit extends Unit
{
drawMarker()
{
if (this.flags.human)
if (this.flags.Human)
{
super.drawMarker(controlPanel.getSettings().human);
super.drawMarker(settingsPanel.getSettings().human);
}
else
{
super.drawMarker(controlPanel.getSettings().AI);
super.drawMarker(settingsPanel.getSettings().AI);
}
}
}
@ -344,7 +411,7 @@ class GroundUnit extends Unit
drawMarker()
{
super.drawMarker(controlPanel.getSettings().AI);
super.drawMarker(settingsPanel.getSettings().AI);
}
}
@ -364,7 +431,7 @@ class NavyUnit extends Unit
drawMarker()
{
super.drawMarker(controlPanel.getSettings().AI);
super.drawMarker(settingsPanel.getSettings().AI);
}
}
@ -372,25 +439,20 @@ class Weapon extends Unit
{
constructor(ID, data)
{
// Weapons can not be selected
self.selectable = false;
super(ID, data);
// Weapons can not be selected
this.selectable = false;
}
drawMarker()
{
super.drawMarker(controlPanel.getSettings().weapons);
super.drawMarker(settingsPanel.getSettings().weapons);
}
onClick(e)
{
// Weapons can not be clicked
}
onRightClick(e)
{
// Weapons can not be clicked
}
}
class Missile extends Weapon

View File

@ -25,7 +25,7 @@ L.Marker.UnitMarker = L.Marker.extend(
</td>
</tr>
</table>`,
className: 'unit-marker-icon'}); // Set the unit marker, className must be set to avoid white square
className: 'action-cursor'}); // Set the unit marker, className must be set to avoid white square
this.setIcon(icon);
},
@ -38,18 +38,6 @@ L.Marker.UnitMarker = L.Marker.extend(
this._icon.style.outline = "transparent"; // Removes the rectangular outline
// Set the unit name in the marker
var unitNameDiv = this._icon.querySelector("#unitName");
if (this.options.human)
{
unitNameDiv.innerHTML = `<i class="fas fa-user"></i> ${this.options.unitName}`;
}
else
{
unitNameDiv.innerHTML = `${this.options.unitName}`;
}
unitNameDiv.style.left = (-(unitNameDiv.offsetWidth - this._icon.querySelector("#icon-img").height) / 2) + "px";
// Set the unit name in the marker
var nameDiv = this._icon.querySelector("#name");
nameDiv.innerHTML = this.options.name;
@ -70,6 +58,7 @@ L.Marker.UnitMarker = L.Marker.extend(
// If the unit is not alive it is drawn with darker colours
setAlive: function(alive)
{
this.alive = alive
var table = this._icon.querySelector("#container-table");
if (alive)
{
@ -114,7 +103,7 @@ L.Marker.UnitMarker = L.Marker.extend(
}
else
{
if (img.classList.contains("unitmarker-icon-img-hovered")) img.classList.remove("unitmarker-icon-img-hovered");
img.classList.remove("unitmarker-icon-img-hovered");
}
}
},
@ -151,8 +140,11 @@ L.Marker.UnitMarker = L.Marker.extend(
setLabelsVisibility(visibility)
{
this._icon.querySelector("#unitName").style.opacity = visibility ? "1": "0";
this._icon.querySelector("#unitName").innerHTML = visibility ? this.options.unitName : "";
var unitNameDiv = this._icon.querySelector("#unitName");
unitNameDiv.style.opacity = visibility ? "1": "0";
var unitName = this.options.human ? `<i class="fas fa-user"></i> ${this.options.unitName}` : `${this.options.unitName}`;
unitNameDiv.innerHTML = visibility ? unitName : "";
unitNameDiv.style.left = (-(unitNameDiv.offsetWidth - this._icon.querySelector("#icon-img").height) / 2) + "px";
//this._icon.querySelector("#name").style.opacity = visibility ? "1": "0";
//this._icon.querySelector("#name").innerHTML = visibility ? this.options.name : "";
this._icon.querySelector("#altitude-div").style.opacity = visibility ? "1": "0";
@ -225,8 +217,9 @@ L.Marker.UnitMarker.WeaponMarker = L.Marker.UnitMarker.extend({})
L.Marker.UnitMarker.WeaponMarker.addInitHook(function()
{
// Weapons are not selectable
this.on('mouseover', function(e) {});
this.on('mouseout', function(e) {});
this.on('mouseover', function(e) {
e.target.setHovered(false);
});
});
// Missile

View File

@ -3,6 +3,7 @@ class UnitsManager
constructor()
{
this._units = {};
this._copiedUnits = [];
}
addUnit(ID, data)
@ -15,7 +16,7 @@ class UnitsManager
}
}
getUnit(ID)
getUnitByID(ID)
{
return this._units[ID];
}
@ -50,22 +51,15 @@ class UnitsManager
{
if (this.getSelectedUnits().length > 0)
{
map.setState("UNIT_SELECTED");
unitControlPanel.enableButtons(true);
map.setState("MOVE_UNIT");
unitControlPanel.setEnabled(true);
actionPanel.setEnabled(true);
}
else
{
map.setState("IDLE");
unitControlPanel.disableButtons();
}
}
onUnitRightClick(ID)
{
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
selectedUnits[idx].attackUnit(ID);
unitControlPanel.setEnabled(false);
actionPanel.setEnabled(false);
}
}
@ -135,4 +129,102 @@ class UnitsManager
selectedUnits[idx].changeAltitude(altitudeChange);
}
}
handleKeyEvent(e)
{
if (e.originalEvent.code === 'KeyC' && e.originalEvent.ctrlKey)
{
this.copyUnits();
}
else if (e.originalEvent.code === 'KeyV' && e.originalEvent.ctrlKey)
{
this.pasteUnits();
}
}
copyUnits()
{
this._copiedUnits = this.getSelectedUnits();
}
pasteUnits()
{
for (let idx in this._copiedUnits)
{
var unit = this._copiedUnits[idx];
cloneUnit(unit.ID);
}
}
attackUnit(ID)
{
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
selectedUnits[idx].attackUnit(ID);
}
}
createFormation(ID)
{
var selectedUnits = this.getSelectedUnits();
var wingmenIDs = [];
for (let idx in selectedUnits)
{
if (selectedUnits[idx].wingman)
{
showMessage(selectedUnits[idx].unitName + " is already in a formation.");
return;
}
else if (selectedUnits[idx].leader)
{
showMessage(selectedUnits[idx].unitName + " is already in a formation.");
return;
}
else
{
if (selectedUnits[idx].category !== this.getUnitByID(ID).category)
{
showMessage("All units must be of the same category to create a formation.");
}
if (selectedUnits[idx].ID != ID)
{
wingmenIDs.push(selectedUnits[idx].ID);
}
}
}
if (wingmenIDs.length > 0)
{
this.getUnitByID(ID).setLeader(wingmenIDs);
}
else
{
showMessage("At least 2 units must be selected to create a formation.");
}
}
getUnitsByFormationID(formationID)
{
var formationUnits = [];
for (let ID in this._units)
{
if (this._units[ID].formationID == formationID)
{
formationUnits.push(this._units[ID]);
}
}
return formationUnits;
}
getLeaderByFormationID(formationID)
{
var formationUnits = this.getUnitsByFormationID(formationID);
for (let unit of formationUnits)
{
if (unit.leader)
{
return unit;
}
}
}
}

View File

@ -206,68 +206,70 @@ unitTypes.vehicles.Unarmed = [
]
/* AIRPLANES */
unitTypes.air = [
"Tornado GR4",
"Tornado IDS",
"F/A-18A",
unitTypes.air = {}
unitTypes.air.CAP = [
"F-4E",
"F/A-18C",
"MiG-29S",
"MiG-29A",
"F-14A",
"Tu-22M3",
"F-4E",
"B-52H",
"MiG-27K",
"F-111F",
"A-10A",
"Su-27",
"MiG-29G",
"MiG-23MLD",
"Su-25",
"Su-25TM",
"Su-25T",
"Su-33",
"MiG-25PD",
"MiG-25RBT",
"Su-30",
"MiG-31",
"Mirage 2000-5",
"F-15C",
"F-5E",
"F-16C bl.52d",
]
unitTypes.air.CAS = [
"Tornado IDS",
"F-4E",
"F/A-18C",
"MiG-27K",
"A-10C",
"Su-25",
"Su-34",
"Su-17M4",
"MiG-31",
"F-15E",
]
unitTypes.air.strike = [
"Tu-22M3",
"B-52H",
"F-111F",
"Tu-95MS",
"Su-24M",
"Tu-160",
"F-117A",
"B-1B",
"S-3B",
"S-3B Tanker",
"Mirage 2000-5",
"F-15C",
"F-15E",
"F-5E",
"MiG-29K",
"Tu-142",
]
unitTypes.air.tank = [
"S-3B Tanker",
"KC-135",
"IL-78M",
]
unitTypes.air.awacs = [
"A-50",
"E-3A",
"E-2D",
]
unitTypes.air.drone = [
"MQ-1A Predator",
"MQ-9 Reaper",
]
unitTypes.air.transport = [
"C-130",
"An-26B",
"An-30M",
"C-17A",
"A-50",
"E-3A",
"IL-78M",
"E-2D",
"IL-76MD",
"F-16C bl.50",
"F-16C bl.52d",
"Su-24MR",
"F-16A",
"F-16A MLU",
"MQ-1A Predator",
"Yak-40",
"A-10C",
"KC-135",
"L-39ZA",
"P-51D",
"MQ-9 Reaper",
"FW-190D9",
"TF-51D"
]
]

View File

@ -1,8 +1,10 @@
var missionData;
var controlPanel;
var settingsPanel;
var unitsManager;
var unitInfoPanel;
var unitControlPanel;
var unitActionPanel;
var formationControlPanel;
var map;
var RESTaddress = "http://localhost:30000/restdemo";
@ -12,10 +14,11 @@ function setup()
missionData = new MissionData();
unitsManager = new UnitsManager();
unitInfoPanel = new UnitInfoPanel("left-panel");
unitControlPanel = new UnitControlPanel("top-panel");
controlPanel = new ControlPanel("top-control-panel");
unitInfoPanel = new UnitInfoPanel("unit-info-panel");
unitControlPanel = new UnitControlPanel("unit-control-panel");
formationControlPanel = new FormationControlPanel("formation-control-panel");
settingsPanel = new SettingsPanel("settings-panel");
actionPanel = new ActionPanel("action-panel")
map = new Map();
// Main update rate. 250ms is minimum time, equal to server update time.
@ -26,7 +29,9 @@ function resize()
{
var unitControlPanelHeight = document.getElementById("header").offsetHeight;
document.getElementById("map").style.height = `${window.innerHeight - unitControlPanelHeight - 10}px`;
document.getElementById("top-panel").style.left = `${window.innerWidth / 2 - document.getElementById("top-panel").offsetWidth / 2}px`
document.getElementById("unit-control-panel").style.left = `${window.innerWidth / 2 - document.getElementById("unit-control-panel").offsetWidth / 2}px`
document.getElementById("action-panel").style.top = `${window.innerHeight / 2 - document.getElementById("action-panel").offsetHeight / 2}px`
document.getElementById("snackbar").style.left = `${window.innerWidth / 2 - document.getElementById("snackbar").offsetWidth / 2}px`
}
/* GET new data from the server */
@ -42,6 +47,7 @@ function update()
missionData.update(data);
unitsManager.update(data);
unitInfoPanel.update(unitsManager.getSelectedUnits());
formationControlPanel.update(unitsManager.getSelectedUnits());
};
xmlHttp.onerror = function () {
@ -77,4 +83,18 @@ window.console = {
},
lastMessage: "none"
}
function showMessage(message)
{
// Get the snackbar DIV
var x = document.getElementById("snackbar");
// Add the "show" class to DIV
x.className = "show";
x.innerHTML = message;
// After 3 seconds, remove the show class from DIV
setTimeout(function(){ x.className = x.className.replace("show", ""); }, 3000);
}

View File

@ -1 +0,0 @@
python -m http.server

11
www/tsconfig.json Normal file
View File

@ -0,0 +1,11 @@
{
"compilerOptions": {
"outDir": "./dist",
"allowJs": true,
"target": "es5"
},
"include": [
"./js/*",
"./js/**/*"
]
}