Added feature: select unit payload at spawn and visibility.

This commit is contained in:
Davide Passoni 2022-12-30 17:43:20 +01:00
parent 92eab6fd41
commit 036f70db34
38 changed files with 14104 additions and 563 deletions

View File

@ -24,7 +24,8 @@ begin
end;
[Registry]
Root: HKCU; Subkey: "Environment"; ValueType:string; ValueName: "OLYMPUS"; ValueData: "{app}\Mods\Services\Olympus"; Flags: preservestringtype
Root: HKCU; Subkey: "Environment"; ValueType:string; ValueName: "DCSOLYMPUS_PATH"; ValueData: "{app}\Mods\Services\Olympus"; Flags: preservestringtype
; Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};DCSOLYMPUS_PATH\bin"
[Setup]
; Tell Windows Explorer to reload the environment

Binary file not shown.

View File

@ -5,34 +5,63 @@ function Olympus.notify(message, displayFor)
trigger.action.outText(message, displayFor)
end
function Olympus.move(unitName, lat, lng, v, category)
Olympus.notify("Olympus.move " .. unitName .. " (" .. lat .. ", " .. lng ..")", 10)
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
if category == 1 then
local startPoint = mist.getLeadPos(unit:getGroup())
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 = {}
path[#path + 1] = mist.fixedWing.buildWP(startPoint, flyOverPoint, v, startPoint.y, 'BARO')
path[#path + 1] = mist.fixedWing.buildWP(endPoint, turningPoint, v, startPoint.y, 'BARO')
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
end
mist.goRoute(unit:getGroup(), path)
local missionTask = {
id = 'Mission',
params = {
route = {
points = mist.utils.deepCopy(path),
},
},
}
group = unit:getGroup()
local groupCon = group:getController()
if groupCon then
groupCon:setTask(missionTask)
end
Olympus.notify("Olympus.move executed succesfully on a air unit", 10)
elseif category == 2 then
elseif category == "GroundUnit" then
vars =
{
group = unit:getGroup(),
point = coord.LLtoLO(lat, lng, 0),
form = "Off Road",
heading = 0,
speed = v,
speed = speed,
disableRoads = true
}
mist.groupToRandomPoint(vars)
Olympus.notify("Olympus.move executed succesfully on a ground unit", 10)
else
Olympus.notify("Olympus.move not implemented yet for navy units", 10)
Olympus.notify("Olympus.move not implemented yet for " .. category, 10)
end
else
Olympus.notify("Error in Olympus.move " .. unitName, 10)
@ -90,23 +119,26 @@ function Olympus.spawnGround(coalition, type, lat, lng, ID)
Olympus.notify("Olympus.spawnGround completed succesfully", 10)
end
function Olympus.spawnAir(coalition, type, lat, lng, alt)
Olympus.notify("Olympus.spawnAir " .. coalition .. " " .. type .. " (" .. lat .. ", " .. lng ..")", 10)
function Olympus.spawnAir(coalition, unitType, lat, lng, payloadName)
local alt = 5000
Olympus.notify("Olympus.spawnAir " .. coalition .. " " .. unitType .. " (" .. lat .. ", " .. lng ..") " .. payloadName, 10)
local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0))
local payload = {}
if Olympus.unitPayloads[unitType][payloadName] ~= nil then
payload = Olympus.unitPayloads[unitType][payloadName]
end
local unitTable =
{
[1] =
{
["type"] = type,
["type"] = unitType,
["x"] = spawnLocation.x,
["y"] = spawnLocation.z,
["alt"] = alt,
["skill"] = "Excellent",
["payload"] =
{
["pylons"] =
{
},
["pylons"] = payload,
["fuel"] = 4900,
["flare"] = 60,
["ammo_type"] = 1,
@ -146,53 +178,4 @@ function Olympus.spawnAir(coalition, type, lat, lng, alt)
Olympus.notify("Olympus.spawnAir completed succesfully", 10)
end
function Olympus.attackUnit(unitName, targetName, lat, lng)
Olympus.notify("Olympus.attackUnit " .. unitName .. " " .. targetName, 10)
local targetID = Unit.getByName(targetName):getID()
local unit = Unit.getByName(unitName)
local category = 1
if unit ~= nil then
if category == 1 then
local startPoint = mist.getLeadPos(unit:getGroup())
local endPoint = coord.LLtoLO(lat, lng, 0)
local attackTask = {
id = 'EngageUnit',
params = {
unitId = targetID,
}
}
local path = {}
path[#path + 1] = mist.fixedWing.buildWP(startPoint, flyOverPoint, v, startPoint.y, 'BARO')
path[#path].task = attackTask
path[#path + 1] = mist.fixedWing.buildWP(endPoint, turningPoint, v, startPoint.y, 'BARO')
path[#path].task = attackTask
local missionTask = {
id = 'Mission',
params = {
route = {
points = mist.utils.deepCopy(path),
},
},
}
group = unit:getGroup()
local groupCon = group:getController()
if groupCon then
groupCon:setTask(missionTask)
end
Olympus.notify("Olympus.attackUnit completed succesfully", 10)
elseif category == 2 then
Olympus.notify("Olympus.attackUnit not implemented yet for ground units", 10)
else
Olympus.notify("Olympus.attackUnit not implemented yet for navy units", 10)
end
end
end
Olympus.notify("OlympusCommand script loaded correctly", 10)

View File

@ -5,29 +5,86 @@ function Olympus.notify(message, displayFor)
end
function Olympus.setMissionData(arg, time)
local missionData = {}
-- Bullseye data
local bullseyeVec3 = coalition.getMainRefPoint(0)
local bullseyeLatitude, bullseyeLongitude, bullseyeAltitude = coord.LOtoLL(bullseyeVec3)
local command = "Olympus.missionData = " ..
"{" ..
"bullseye = {" ..
"x = " .. bullseyeVec3.x .. "," ..
"y = " .. bullseyeVec3.z .. "," ..
"lat = " .. bullseyeLatitude .. "," ..
"lng = " .. bullseyeLongitude .. "," ..
"}," ..
"}\n" ..
"Olympus.OlympusDLL.setMissionData()"
local bullseye = {}
bullseye["x"] = bullseyeVec3.x
bullseye["y"] = bullseyeVec3.z
bullseye["lat"] = bullseyeLatitude
bullseye["lng"] = bullseyeLongitude
for groupName, group in pairs(mist.DBs.groupsByName) do
if groupName and group then
local hasTask = Group.getByName(groupName):getController():hasTask()
Olympus.notify(groupName .. ": " .. tostring(hasTask), 2)
-- Units tactical data
-- TODO find some way to spread the load of getting this data (split)
local unitsData = {}
for groupName, gp in pairs(mist.DBs.groupsByName) do
if groupName ~= nil then
local group = Group.getByName(groupName)
if group ~= nil then
local controller = group:getController()
for index, unit in pairs(group:getUnits()) do
local table = {}
table["targets"] = {}
table["targets"]["visual"] = controller:getDetectedTargets(1)
table["targets"]["radar"] = controller:getDetectedTargets(4)
table["targets"]["rwr"] = controller:getDetectedTargets(16)
table["targets"]["other"] = controller:getDetectedTargets(2, 8, 32)
table["ammo"] = unit:getAmmo()
table["fuel"] = unit:getFuel()
table["life"] = unit:getLife() / unit:getLife0()
unitsData[unit:getObjectID()] = table
end
end
end
end
-- Assemble missionData table
missionData["bullseye"] = bullseye
missionData["unitsData"] = unitsData
local command = "Olympus.missionData = " .. Olympus.serializeTable(missionData) .. "\n" .. "Olympus.OlympusDLL.setMissionData()"
net.dostring_in("export", command)
return time + 5
end
function Olympus.serializeTable(val, name, skipnewlines, depth)
skipnewlines = skipnewlines or false
depth = depth or 0
local tmp = string.rep(" ", depth)
if name then
if type(name) == "number" then
tmp = tmp .. "[" .. name .. "]" .. " = "
else
tmp = tmp .. name .. " = "
end
end
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)
elseif type(val) == "string" then
tmp = tmp .. string.format("%q", val)
elseif type(val) == "boolean" then
tmp = tmp .. (val and "true" or "false")
else
tmp = tmp .. "\"[inserializeable datatype:" .. type(val) .. "]\""
end
return tmp
end
timer.scheduleFunction(Olympus.setMissionData, {}, timer.getTime() + 1)
Olympus.notify("OlympusMission script loaded correctly", 10)

View File

@ -0,0 +1,75 @@
from slpp import slpp as lua
import os
import json
import logging
SEARCH_FOLDER = "D:\\Eagle Dynamics\\DCS World OpenBeta"
def dump_lua(data):
if type(data) is str:
return f'"{data}"'
if type(data) in (int, float):
return f'{data}'
if type(data) is bool:
return data and "true" or "false"
if type(data) is list:
l = "{"
l += ",\n ".join([dump_lua(item) for item in data])
l += "}"
return l
if type(data) is dict:
t = "{"
t += ",\n ".join([f'[{k}] = {dump_lua(v)}' if type(k) == int else f'["{k}"]={dump_lua(v)}' for k,v in data.items()])
t += "}"
return t
logging.error(f"Unknown type {type(data)}")
filenames = [os.path.join(dp, f) for dp, dn, filenames in os.walk(SEARCH_FOLDER) for f in filenames if os.path.splitext(f)[1] == '.lua']
for filename in list(filenames):
with open(filename, 'r') as f:
try:
if f.read().find("unitPayloads") == -1:
filenames.remove(filename)
except:
filenames.remove(filename)
logging.warning(f"{filename} skipped...")
pass
names = {}
payloads = {}
for filename in filenames:
with open(os.path.join(os.getcwd(), filename), 'r') as f: # open in readonly mode
try:
lines = f.read()
searchString = "local unitPayloads = {"
start = lines.find(searchString)
end = lines.rfind("}")
tmp = lua.decode(lines[start + len(searchString) - 1: end + 1])
if type(tmp['payloads']) == dict:
src = tmp['payloads'].values()
else:
src = tmp['payloads']
names[tmp['unitType']] = []
payloads[tmp['unitType']] = {}
for payload in src:
names[tmp['unitType']].append(payload['name'])
if type(payload['pylons']) == dict:
payloads[tmp['unitType']][payload['name']] = {payload['pylons'][key]['num']: {"CLSID" : payload['pylons'][key]['CLSID']} for key in payload['pylons']}
else:
payloads[tmp['unitType']][payload['name']] = {payload['pylons'][key]['num']: {"CLSID" : payload['pylons'][key]['CLSID']} for key in range(len(payload['pylons']))}
except:
pass
with open('payloadNames.js', 'w') as f:
f.write("payloadNames = ")
json.dump(names, f, ensure_ascii = False)
with open('unitPayloads.lua', 'w') as f:
f.write("Olympus.unitPayloads = " + dump_lua(payloads))

1
scripts/payloadNames.js Normal file

File diff suppressed because one or more lines are too long

12586
scripts/unitPayloads.lua Normal file

File diff suppressed because it is too large Load Diff

View File

@ -17,112 +17,21 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "olympus", "olympus\olympus.
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
MinSizeRel|x64 = MinSizeRel|x64
MinSizeRel|x86 = MinSizeRel|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
RelWithDebInfo|x64 = RelWithDebInfo|x64
RelWithDebInfo|x86 = RelWithDebInfo|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8A48D855-0E01-42BA-BD8C-07B0877C68DF}.Debug|x64.ActiveCfg = Debug|x64
{8A48D855-0E01-42BA-BD8C-07B0877C68DF}.Debug|x64.Build.0 = Debug|x64
{8A48D855-0E01-42BA-BD8C-07B0877C68DF}.Debug|x86.ActiveCfg = Debug|Win32
{8A48D855-0E01-42BA-BD8C-07B0877C68DF}.Debug|x86.Build.0 = Debug|Win32
{8A48D855-0E01-42BA-BD8C-07B0877C68DF}.MinSizeRel|x64.ActiveCfg = Release|x64
{8A48D855-0E01-42BA-BD8C-07B0877C68DF}.MinSizeRel|x64.Build.0 = Release|x64
{8A48D855-0E01-42BA-BD8C-07B0877C68DF}.MinSizeRel|x86.ActiveCfg = Release|Win32
{8A48D855-0E01-42BA-BD8C-07B0877C68DF}.MinSizeRel|x86.Build.0 = Release|Win32
{8A48D855-0E01-42BA-BD8C-07B0877C68DF}.Release|x64.ActiveCfg = Release|x64
{8A48D855-0E01-42BA-BD8C-07B0877C68DF}.Release|x64.Build.0 = Release|x64
{8A48D855-0E01-42BA-BD8C-07B0877C68DF}.Release|x86.ActiveCfg = Release|Win32
{8A48D855-0E01-42BA-BD8C-07B0877C68DF}.Release|x86.Build.0 = Release|Win32
{8A48D855-0E01-42BA-BD8C-07B0877C68DF}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{8A48D855-0E01-42BA-BD8C-07B0877C68DF}.RelWithDebInfo|x64.Build.0 = Release|x64
{8A48D855-0E01-42BA-BD8C-07B0877C68DF}.RelWithDebInfo|x86.ActiveCfg = Release|Win32
{8A48D855-0E01-42BA-BD8C-07B0877C68DF}.RelWithDebInfo|x86.Build.0 = Release|Win32
{873ECABE-FCFE-4217-AC15-91959C3CF1C6}.Debug|x64.ActiveCfg = Debug|x64
{873ECABE-FCFE-4217-AC15-91959C3CF1C6}.Debug|x64.Build.0 = Debug|x64
{873ECABE-FCFE-4217-AC15-91959C3CF1C6}.Debug|x86.ActiveCfg = Debug|Win32
{873ECABE-FCFE-4217-AC15-91959C3CF1C6}.Debug|x86.Build.0 = Debug|Win32
{873ECABE-FCFE-4217-AC15-91959C3CF1C6}.MinSizeRel|x64.ActiveCfg = Release|x64
{873ECABE-FCFE-4217-AC15-91959C3CF1C6}.MinSizeRel|x64.Build.0 = Release|x64
{873ECABE-FCFE-4217-AC15-91959C3CF1C6}.MinSizeRel|x86.ActiveCfg = Release|Win32
{873ECABE-FCFE-4217-AC15-91959C3CF1C6}.MinSizeRel|x86.Build.0 = Release|Win32
{873ECABE-FCFE-4217-AC15-91959C3CF1C6}.Release|x64.ActiveCfg = Release|x64
{873ECABE-FCFE-4217-AC15-91959C3CF1C6}.Release|x64.Build.0 = Release|x64
{873ECABE-FCFE-4217-AC15-91959C3CF1C6}.Release|x86.ActiveCfg = Release|Win32
{873ECABE-FCFE-4217-AC15-91959C3CF1C6}.Release|x86.Build.0 = Release|Win32
{873ECABE-FCFE-4217-AC15-91959C3CF1C6}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{873ECABE-FCFE-4217-AC15-91959C3CF1C6}.RelWithDebInfo|x64.Build.0 = Release|x64
{873ECABE-FCFE-4217-AC15-91959C3CF1C6}.RelWithDebInfo|x86.ActiveCfg = Release|Win32
{873ECABE-FCFE-4217-AC15-91959C3CF1C6}.RelWithDebInfo|x86.Build.0 = Release|Win32
{2B255368-39A0-431A-A6DE-CC739AC70DC1}.Debug|x64.ActiveCfg = Debug|x64
{2B255368-39A0-431A-A6DE-CC739AC70DC1}.Debug|x64.Build.0 = Debug|x64
{2B255368-39A0-431A-A6DE-CC739AC70DC1}.Debug|x86.ActiveCfg = Debug|Win32
{2B255368-39A0-431A-A6DE-CC739AC70DC1}.Debug|x86.Build.0 = Debug|Win32
{2B255368-39A0-431A-A6DE-CC739AC70DC1}.MinSizeRel|x64.ActiveCfg = Release|x64
{2B255368-39A0-431A-A6DE-CC739AC70DC1}.MinSizeRel|x64.Build.0 = Release|x64
{2B255368-39A0-431A-A6DE-CC739AC70DC1}.MinSizeRel|x86.ActiveCfg = Release|Win32
{2B255368-39A0-431A-A6DE-CC739AC70DC1}.MinSizeRel|x86.Build.0 = Release|Win32
{2B255368-39A0-431A-A6DE-CC739AC70DC1}.Release|x64.ActiveCfg = Release|x64
{2B255368-39A0-431A-A6DE-CC739AC70DC1}.Release|x64.Build.0 = Release|x64
{2B255368-39A0-431A-A6DE-CC739AC70DC1}.Release|x86.ActiveCfg = Release|Win32
{2B255368-39A0-431A-A6DE-CC739AC70DC1}.Release|x86.Build.0 = Release|Win32
{2B255368-39A0-431A-A6DE-CC739AC70DC1}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{2B255368-39A0-431A-A6DE-CC739AC70DC1}.RelWithDebInfo|x64.Build.0 = Release|x64
{2B255368-39A0-431A-A6DE-CC739AC70DC1}.RelWithDebInfo|x86.ActiveCfg = Release|Win32
{2B255368-39A0-431A-A6DE-CC739AC70DC1}.RelWithDebInfo|x86.Build.0 = Release|Win32
{DE139EC1-4F88-47D5-BE73-F41915FE14A3}.Debug|x64.ActiveCfg = Debug|x64
{DE139EC1-4F88-47D5-BE73-F41915FE14A3}.Debug|x64.Build.0 = Debug|x64
{DE139EC1-4F88-47D5-BE73-F41915FE14A3}.Debug|x86.ActiveCfg = Debug|Win32
{DE139EC1-4F88-47D5-BE73-F41915FE14A3}.Debug|x86.Build.0 = Debug|Win32
{DE139EC1-4F88-47D5-BE73-F41915FE14A3}.MinSizeRel|x64.ActiveCfg = Release|x64
{DE139EC1-4F88-47D5-BE73-F41915FE14A3}.MinSizeRel|x64.Build.0 = Release|x64
{DE139EC1-4F88-47D5-BE73-F41915FE14A3}.MinSizeRel|x86.ActiveCfg = Release|Win32
{DE139EC1-4F88-47D5-BE73-F41915FE14A3}.MinSizeRel|x86.Build.0 = Release|Win32
{DE139EC1-4F88-47D5-BE73-F41915FE14A3}.Release|x64.ActiveCfg = Release|x64
{DE139EC1-4F88-47D5-BE73-F41915FE14A3}.Release|x64.Build.0 = Release|x64
{DE139EC1-4F88-47D5-BE73-F41915FE14A3}.Release|x86.ActiveCfg = Release|Win32
{DE139EC1-4F88-47D5-BE73-F41915FE14A3}.Release|x86.Build.0 = Release|Win32
{DE139EC1-4F88-47D5-BE73-F41915FE14A3}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{DE139EC1-4F88-47D5-BE73-F41915FE14A3}.RelWithDebInfo|x64.Build.0 = Release|x64
{DE139EC1-4F88-47D5-BE73-F41915FE14A3}.RelWithDebInfo|x86.ActiveCfg = Release|Win32
{DE139EC1-4F88-47D5-BE73-F41915FE14A3}.RelWithDebInfo|x86.Build.0 = Release|Win32
{B85009CE-4A5C-4A5A-B85D-001B3A2651B2}.Debug|x64.ActiveCfg = Debug|x64
{B85009CE-4A5C-4A5A-B85D-001B3A2651B2}.Debug|x64.Build.0 = Debug|x64
{B85009CE-4A5C-4A5A-B85D-001B3A2651B2}.Debug|x86.ActiveCfg = Debug|Win32
{B85009CE-4A5C-4A5A-B85D-001B3A2651B2}.Debug|x86.Build.0 = Debug|Win32
{B85009CE-4A5C-4A5A-B85D-001B3A2651B2}.MinSizeRel|x64.ActiveCfg = Release|x64
{B85009CE-4A5C-4A5A-B85D-001B3A2651B2}.MinSizeRel|x64.Build.0 = Release|x64
{B85009CE-4A5C-4A5A-B85D-001B3A2651B2}.MinSizeRel|x86.ActiveCfg = Release|Win32
{B85009CE-4A5C-4A5A-B85D-001B3A2651B2}.MinSizeRel|x86.Build.0 = Release|Win32
{B85009CE-4A5C-4A5A-B85D-001B3A2651B2}.Release|x64.ActiveCfg = Release|x64
{B85009CE-4A5C-4A5A-B85D-001B3A2651B2}.Release|x64.Build.0 = Release|x64
{B85009CE-4A5C-4A5A-B85D-001B3A2651B2}.Release|x86.ActiveCfg = Release|Win32
{B85009CE-4A5C-4A5A-B85D-001B3A2651B2}.Release|x86.Build.0 = Release|Win32
{B85009CE-4A5C-4A5A-B85D-001B3A2651B2}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{B85009CE-4A5C-4A5A-B85D-001B3A2651B2}.RelWithDebInfo|x64.Build.0 = Release|x64
{B85009CE-4A5C-4A5A-B85D-001B3A2651B2}.RelWithDebInfo|x86.ActiveCfg = Release|Win32
{B85009CE-4A5C-4A5A-B85D-001B3A2651B2}.RelWithDebInfo|x86.Build.0 = Release|Win32
{5F3FC91E-1FBC-4223-8011-9708DE913474}.Debug|x64.ActiveCfg = Debug|x64
{5F3FC91E-1FBC-4223-8011-9708DE913474}.Debug|x64.Build.0 = Debug|x64
{5F3FC91E-1FBC-4223-8011-9708DE913474}.Debug|x86.ActiveCfg = Debug|x64
{5F3FC91E-1FBC-4223-8011-9708DE913474}.Debug|x86.Build.0 = Debug|x64
{5F3FC91E-1FBC-4223-8011-9708DE913474}.MinSizeRel|x64.ActiveCfg = Release|x64
{5F3FC91E-1FBC-4223-8011-9708DE913474}.MinSizeRel|x64.Build.0 = Release|x64
{5F3FC91E-1FBC-4223-8011-9708DE913474}.MinSizeRel|x86.ActiveCfg = Release|x64
{5F3FC91E-1FBC-4223-8011-9708DE913474}.MinSizeRel|x86.Build.0 = Release|x64
{5F3FC91E-1FBC-4223-8011-9708DE913474}.Release|x64.ActiveCfg = Release|x64
{5F3FC91E-1FBC-4223-8011-9708DE913474}.Release|x64.Build.0 = Release|x64
{5F3FC91E-1FBC-4223-8011-9708DE913474}.Release|x86.ActiveCfg = Release|x64
{5F3FC91E-1FBC-4223-8011-9708DE913474}.Release|x86.Build.0 = Release|x64
{5F3FC91E-1FBC-4223-8011-9708DE913474}.RelWithDebInfo|x64.ActiveCfg = Release|x64
{5F3FC91E-1FBC-4223-8011-9708DE913474}.RelWithDebInfo|x64.Build.0 = Release|x64
{5F3FC91E-1FBC-4223-8011-9708DE913474}.RelWithDebInfo|x86.ActiveCfg = Release|x64
{5F3FC91E-1FBC-4223-8011-9708DE913474}.RelWithDebInfo|x86.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -1,2 +0,0 @@
#include "pch.h"

View File

@ -149,14 +149,14 @@
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>include; ..\..\third-party\lua\include; ..\utils\include; ..\shared\include</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>include;..\..\third-party\GeographicLib-2.1.1\include;..\..\third-party\lua\include;..\utils\include;..\shared\include;..\dcstools\include;..\logger\include;..\luatools\include</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalDependencies>lua.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>lua.lib; GeographicLib-i.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\..\third-party\lua</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
@ -171,7 +171,7 @@
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>include;..\..\third-party\GeographicLib-2.1.1\include;..\..\third-party\lua\include;..\utils\include;..\shared\include;..\dcstools\include;..\logger\include;..\luatools\include</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>include;..\..\third-party\lua\include;..\utils\include;..\shared\include;..\dcstools\include;..\logger\include;..\luatools\include</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
@ -181,7 +181,7 @@
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalDependencies>lua.lib; GeographicLib-i.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\..\third-party\lua; ..\..\third-party\GeographicLib-2.1.1\lib</AdditionalLibraryDirectories>
<AdditionalLibraryDirectories>..\..\third-party\lua; </AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />

View File

@ -8,7 +8,7 @@ namespace CommandPriority {
};
namespace CommandType {
enum CommandTypes { NO_TYPE, MOVE, SMOKE, LASE, EXPLODE, SPAWN_AIR, SPAWN_GROUND, ATTACK_UNIT };
enum CommandTypes { NO_TYPE, MOVE, SMOKE, LASE, EXPLODE, SPAWN_AIR, SPAWN_GROUND };
};
/* Base command class */
@ -17,7 +17,7 @@ class Command
public:
int getPriority() { return priority; }
int getType() { return type; }
virtual void execute(lua_State* L) {};
virtual void execute(lua_State* L) = 0;
protected:
int priority = CommandPriority::LOW;
@ -28,11 +28,14 @@ protected:
class MoveCommand : public Command
{
public:
MoveCommand(int ID, wstring unitName, Coords destination, int unitCategory) :
MoveCommand(int ID, wstring unitName, Coords destination, double speed, double altitude, wstring unitCategory, wstring targetName):
ID(ID),
unitName(unitName),
destination(destination),
unitCategory(unitCategory)
speed(speed),
altitude(altitude),
unitCategory(unitCategory),
targetName(targetName)
{
priority = CommandPriority::LOW;
type = CommandType::MOVE;
@ -43,7 +46,10 @@ private:
const int ID;
const wstring unitName;
const Coords destination;
const int unitCategory;
const wstring unitCategory;
const double speed;
const double altitude;
const wstring targetName;
};
/* Smoke command */
@ -88,10 +94,11 @@ private:
class SpawnAirCommand : public Command
{
public:
SpawnAirCommand(wstring coalition, wstring unitType, Coords location) :
SpawnAirCommand(wstring coalition, wstring unitType, Coords location, wstring payloadName) :
coalition(coalition),
unitType(unitType),
location(location)
location(location),
payloadName(payloadName)
{
priority = CommandPriority::LOW;
type = CommandType::SPAWN_AIR;
@ -102,24 +109,5 @@ private:
const wstring coalition;
const wstring unitType;
const Coords location;
const wstring payloadName;
};
/* Attack unit command */
class AttackUnitCommand : public Command
{
public:
AttackUnitCommand(wstring unitName, wstring targetName, Coords location) :
unitName(unitName),
targetName(targetName),
location(location)
{
priority = CommandPriority::MEDIUM;
type = CommandType::ATTACK_UNIT;
};
virtual void execute(lua_State* L);
private:
const wstring unitName;
const wstring targetName;
const Coords location;
};

View File

@ -7,10 +7,6 @@
#define GROUND_DEST_DIST_THR 100
#define AIR_DEST_DIST_THR 2000
namespace UnitCategory {
enum UnitCategories { NO_CATEGORY, AIR, GROUND, NAVY }; // Do not edit, this codes are tied to values in DCS
};
class Unit
{
public:
@ -21,6 +17,17 @@ public:
void setPath(list<Coords> path);
void setAlive(bool newAlive) { alive = newAlive; }
void setTarget(int targetID);
wstring getTarget();
wstring getCurrentTask();
void resetActiveDestination();
virtual void changeSpeed(wstring change) {};
virtual void changeAltitude(wstring change) {};
virtual double getTargetSpeed() { return targetSpeed; };
virtual double getTargetAltitude() { return targetAltitude; };
int getID() { return ID; }
wstring getName() { return name; }
@ -34,9 +41,10 @@ public:
double getAltitude() { return altitude; }
double getHeading() { return heading; }
json::value getFlags() { return flags; }
int getCategory();
Coords getActiveDestination() { return activeDestination; }
virtual wstring getCategory() { return L"No category"; };
json::value json();
protected:
@ -47,21 +55,131 @@ protected:
wstring unitName = L"undefined";
wstring groupName = L"undefined";
json::value type = json::value::null();
int country = 0;
int coalitionID = 0;
double latitude = 0;
double longitude = 0;
double altitude = 0;
double heading = 0;
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;
list<Coords> activePath;
Coords activeDestination = Coords(0);
private:
virtual void AIloop();
double oldDist = 0;
private:
mutex mutexLock;
};
class AirUnit : public Unit
{
public:
AirUnit(json::value json, int ID);
virtual wstring getCategory() = 0;
protected:
virtual void AIloop();
};
class Aircraft : public AirUnit
{
public:
Aircraft(json::value json, int ID);
virtual wstring getCategory() { return L"Aircraft"; };
virtual void changeSpeed(wstring change);
virtual void changeAltitude(wstring change);
virtual double getTargetSpeed() { return targetSpeed; };
virtual double getTargetAltitude() { return targetAltitude; };
protected:
double targetSpeed = 150;
double targetAltitude = 5000;
};
class Helicopter : public AirUnit
{
public:
Helicopter(json::value json, int ID);
virtual wstring getCategory() { return L"Helicopter"; };
virtual void changeSpeed(wstring change);
virtual void changeAltitude(wstring change);
virtual double getTargetSpeed() { return targetSpeed; };
virtual double getTargetAltitude() { return targetAltitude; };
protected:
double targetSpeed = 50;
double targetAltitude = 1000;
};
class GroundUnit : public Unit
{
public:
GroundUnit(json::value json, int ID);
virtual void AIloop();
virtual wstring getCategory() { return L"GroundUnit"; };
virtual void changeSpeed(wstring change);
virtual void changeAltitude(wstring change) {};
virtual double getTargetSpeed() { return targetSpeed; };
protected:
double targetSpeed = 10;
};
class NavyUnit : public Unit
{
public:
NavyUnit(json::value json, int ID);
virtual void AIloop();
virtual wstring getCategory() { return L"NavyUnit"; };
virtual void changeSpeed(wstring change);
virtual void changeAltitude(wstring change) {};
virtual double getTargetSpeed() { return targetSpeed; };
protected:
double targetSpeed = 10;
};
class Weapon : public Unit
{
public:
Weapon(json::value json, int ID);
virtual wstring getCategory() = 0;
protected:
/* Weapons are not controllable and have no AIloop */
virtual void AIloop() {};
};
class Missile : public Weapon
{
public:
Missile(json::value json, int ID);
virtual wstring getCategory() { return L"Missile"; };
};
class Bomb : public Weapon
{
public:
Bomb(json::value json, int ID);
virtual wstring getCategory() { return L"Bomb"; };
};

View File

@ -6,7 +6,7 @@ void MoveCommand::execute(lua_State* L)
{
std::ostringstream command;
command.precision(10);
command << "Olympus.move(\"" << to_string(unitName) << "\", " << destination.lat << ", " << destination.lng << ", " << 10 << ", " << unitCategory << ")";
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");
@ -69,7 +69,7 @@ void SpawnAirCommand::execute(lua_State* L)
{
std::ostringstream command;
command.precision(10);
command << "Olympus.spawnAir(\"" << to_string(coalition) << "\", \"" << to_string(unitType) << "\", " << location.lat << ", " << location.lng << ")";
command << "Olympus.spawnAir(\"" << to_string(coalition) << "\", \"" << to_string(unitType) << "\", " << location.lat << ", " << location.lng << "," << "\"" << to_string(payloadName) << "\")";
lua_getglobal(L, "net");
lua_getfield(L, -1, "dostring_in");
@ -84,24 +84,3 @@ void SpawnAirCommand::execute(lua_State* L)
log("SpawnAirCommand executed successfully");
}
}
/* Attack unit command */
void AttackUnitCommand::execute(lua_State* L)
{
std::ostringstream command;
command.precision(10);
command << "Olympus.attackUnit(\"" << to_string(unitName) << "\", \"" << to_string(targetName) << "\", " << 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 AttackUnitCommand");
}
else
{
log("AttackUnitCommand executed successfully");
}
}

View File

@ -64,13 +64,6 @@ void Scheduler::execute(lua_State* L)
commands.remove(command);
break;
}
case CommandType::ATTACK_UNIT:
{
AttackUnitCommand* attackCommand = dynamic_cast<AttackUnitCommand*>(command);
attackCommand->execute(L);
commands.remove(command);
break;
}
default:
log("Unknown command of type " + to_string(command->getType()));
commands.remove(command);
@ -93,26 +86,30 @@ void Scheduler::handleRequest(wstring key, json::value value)
if (key.compare(L"setPath") == 0)
{
int ID = value[L"ID"].as_integer();
wstring unitName = value[L"unitName"].as_string();
json::value path = value[L"path"];
list<Coords> newPath;
for (auto const& e : path.as_object())
Unit* unit = unitsFactory->getUnit(ID);
if (unit != nullptr)
{
wstring WP = e.first;
double lat = path[WP][L"lat"].as_double();
double lng = path[WP][L"lng"].as_double();
log(unitName + L" set path destination " + WP + L" (" + to_wstring(lat) + L", " + to_wstring(lng) + L")");
Coords dest; dest.lat = lat; dest.lng = lng;
newPath.push_back(dest);
Unit* unit = unitsFactory->getUnit(ID);
if (unit != nullptr)
wstring unitName = unit->getUnitName();
json::value path = value[L"path"];
list<Coords> newPath;
for (auto const& e : path.as_object())
{
unit->setPath(newPath);
log(unitName + L" new path set successfully");
}
else
{
log(unitName + L" not found, request will be discarded");
wstring WP = e.first;
double lat = path[WP][L"lat"].as_double();
double lng = path[WP][L"lng"].as_double();
log(unitName + L" set path destination " + WP + L" (" + to_wstring(lat) + L", " + to_wstring(lng) + L")");
Coords dest; dest.lat = lat; dest.lng = lng;
newPath.push_back(dest);
Unit* unit = unitsFactory->getUnit(ID);
if (unit != nullptr)
{
unit->setPath(newPath);
log(unitName + L" new path set successfully");
}
else
{
log(unitName + L" not found, request will be discarded");
}
}
}
}
@ -141,9 +138,10 @@ void Scheduler::handleRequest(wstring key, json::value value)
wstring type = value[L"type"].as_string();
double lat = value[L"location"][L"lat"].as_double();
double lng = value[L"location"][L"lng"].as_double();
log(L"Spawning " + coalition + L" air 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 SpawnAirCommand(coalition, type, loc));
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));
}
else if (key.compare(L"attackUnit") == 0)
{
@ -155,12 +153,10 @@ void Scheduler::handleRequest(wstring key, json::value value)
wstring unitName;
wstring targetName;
Coords loc;
if (unit != nullptr)
{
unitName = unit->getUnitName();
loc = unit->getActiveDestination();
}
else
{
@ -170,10 +166,6 @@ void Scheduler::handleRequest(wstring key, json::value value)
if (target != nullptr)
{
targetName = target->getUnitName();
if (loc == NULL)
{
loc = Coords(target->getLatitude(), target->getLongitude(), target->getAltitude());
}
}
else
{
@ -181,7 +173,25 @@ void Scheduler::handleRequest(wstring key, json::value value)
}
log(L"Unit " + unitName + L" attacking unit " + targetName);
command = dynamic_cast<Command*>(new AttackUnitCommand(unitName, targetName, loc));
unit->setTarget(targetID);
}
else if (key.compare(L"changeSpeed") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsFactory->getUnit(ID);
if (unit != nullptr)
{
unit->changeSpeed(value[L"change"].as_string());
}
}
else if (key.compare(L"changeAltitude") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsFactory->getUnit(ID);
if (unit != nullptr)
{
unit->changeAltitude(value[L"change"].as_string());
}
}
else
{

View File

@ -3,11 +3,14 @@
#include "logger.h"
#include "commands.h"
#include "scheduler.h"
#include "defines.h"
#include "unitsFactory.h"
#include <GeographicLib/Geodesic.hpp>
using namespace GeographicLib;
extern Scheduler* scheduler;
extern UnitsFactory* unitsFactory;
const Geodesic& geod = Geodesic::WGS84();
@ -15,7 +18,6 @@ Unit::Unit(json::value json, int ID) :
ID(ID)
{
log("Creating unit with ID: " + to_string(ID));
update(json);
}
Unit::~Unit()
@ -23,23 +25,21 @@ Unit::~Unit()
}
int Unit::getCategory()
{
if (type.has_number_field(L"level1"))
{
return type[L"level1"].as_number().to_int32();
}
else
{
return UnitCategory::NO_CATEGORY;
}
}
void Unit::update(json::value json)
{
/* Lock for thread safety */
lock_guard<mutex> guard(mutexLock);
/* Compute speed (loGetWorldObjects does not provide speed, we compute it for better performance instead of relying on many lua calls) */
if (oldPosition != NULL)
{
double dist = 0;
geod.Inverse(latitude, longitude, oldPosition.lat, oldPosition.lng, dist);
speed = speed * 0.95 + (dist / UPDATE_TIME_INTERVAL) * 0.05;
}
oldPosition = Coords(latitude, longitude, altitude);
/* Update all the internal fields from the input json file */
if (json.has_string_field(L"Name"))
name = json[L"Name"].as_string();
if (json.has_string_field(L"UnitName"))
@ -80,17 +80,81 @@ void Unit::update(json::value json)
void Unit::setPath(list<Coords> path)
{
activePath = path;
holding = false;
}
void Unit::setTarget(int newTargetID)
{
targetID = newTargetID;
resetActiveDestination();
}
wstring Unit::getTarget()
{
if (targetID == NULL)
{
return L"";
}
Unit* target = unitsFactory->getUnit(targetID);
if (target != nullptr)
{
if (target->alive)
{
return target->getUnitName();
}
else
{
targetID = NULL;
return L"";
}
}
else
{
targetID = NULL;
return L"";
}
}
wstring Unit::getCurrentTask()
{
if (activePath.size() == 0)
{
return L"Idle";
}
else
{
if (getTarget().empty())
{
if (looping)
{
return L"Looping";
}
else if (holding)
{
return L"Holding";
}
else
{
return L"Reaching destination";
}
}
else
{
return L"Attacking " + getTarget();
}
}
}
void Unit::AIloop()
{
/* Set the active destination to be always equal to the first point of the active path */
/* 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, getCategory()));
Command* command = dynamic_cast<Command*>(new MoveCommand(ID, unitName, activeDestination, getTargetSpeed(), getTargetAltitude(), getCategory(), getTarget()));
scheduler->appendCommand(command);
}
}
@ -102,52 +166,13 @@ void Unit::AIloop()
activeDestination = Coords(0); // Set the active path to NULL
}
}
}
/* Ground unit AI Loop */
if (getCategory() == UnitCategory::GROUND)
{
if (activeDestination != NULL)
{
double newDist = 0;
geod.Inverse(latitude, longitude, activeDestination.lat, activeDestination.lng, newDist);
if (newDist < GROUND_DEST_DIST_THR)
{
/* Destination reached */
activePath.pop_front();
log(unitName + L" destination reached");
}
}
}
/* Air unit AI Loop */
if (getCategory() == UnitCategory::AIR)
{
if (activeDestination != NULL)
{
double newDist = 0;
geod.Inverse(latitude, longitude, activeDestination.lat, activeDestination.lng, newDist);
if (newDist < AIR_DEST_DIST_THR)
{
/* Destination reached */
activePath.pop_front();
log(name + L" destination reached");
}
}
else
{
/* Air units must ALWAYS have a destination or they will RTB and may become uncontrollable */
Coords point1;
Coords point2;
Coords point3;
geod.Direct(latitude, longitude, 45, 18520, point1.lat, point1.lng);
geod.Direct(point1.lat, point1.lng, 135, 18520, point2.lat, point2.lng);
geod.Direct(point2.lat, point2.lng, 225, 18520, point3.lat, point3.lng);
activePath.push_back(point1);
activePath.push_back(point2);
activePath.push_back(point3);
activePath.push_back(Coords(latitude, longitude));
}
}
/* This function calls again the MoveCommand to reach the active destination. This is useful to change speed and altitude, for example */
void Unit::resetActiveDestination()
{
log(unitName + L" resetting active destination");
activeDestination = Coords(0);
}
json::value Unit::json()
@ -167,8 +192,11 @@ json::value Unit::json()
json[L"latitude"] = latitude;
json[L"longitude"] = longitude;
json[L"altitude"] = altitude;
json[L"speed"] = speed;
json[L"heading"] = heading;
json[L"flags"] = flags;
json[L"category"] = json::value::string(getCategory());
json[L"currentTask"] = json::value::string(getCurrentTask());
/* Send the active path as a json object */
if (activePath.size() > 0) {
@ -188,4 +216,223 @@ json::value Unit::json()
return json;
}
/* Air unit */
AirUnit::AirUnit(json::value json, int ID) : Unit(json, ID)
{
};
void AirUnit::AIloop()
{
/* Call the common AI loop */
Unit::AIloop();
/* Air unit AI Loop */
if (activeDestination != NULL)
{
double newDist = 0;
geod.Inverse(latitude, longitude, activeDestination.lat, activeDestination.lng, newDist);
if (newDist < AIR_DEST_DIST_THR)
{
/* Destination reached */
if (holding || looping)
{
activePath.push_back(activePath.front());
}
activePath.pop_front();
log(name + L" destination reached");
}
}
else
{
/* Air units must ALWAYS have a destination or they will RTB and may become uncontrollable */
Coords point1;
Coords point2;
Coords point3;
geod.Direct(latitude, longitude, 45, 10000, point1.lat, point1.lng);
geod.Direct(point1.lat, point1.lng, 135, 10000, point2.lat, point2.lng);
geod.Direct(point2.lat, point2.lng, 225, 10000, point3.lat, point3.lng);
activePath.push_back(point1);
activePath.push_back(point2);
activePath.push_back(point3);
activePath.push_back(Coords(latitude, longitude));
holding = true;
}
}
/* Aircraft */
Aircraft::Aircraft(json::value json, int ID) : AirUnit(json, ID)
{
log("New Aircraft created with ID: " + to_string(ID));
};
void Aircraft::changeSpeed(wstring change)
{
if (change.compare(L"stop") == 0)
{
/* Air units can't hold a position, so we can only set them to hold. At the moment, this will erase any other command. TODO: helicopters should be able to hover in place */
activePath.clear();
}
else if (change.compare(L"slow") == 0)
{
targetSpeed *= 0.9;
resetActiveDestination();
}
else if (change.compare(L"fast") == 0)
{
targetSpeed *= 1.1;
resetActiveDestination();
}
}
void Aircraft::changeAltitude(wstring change)
{
if (change.compare(L"descend") == 0)
{
targetAltitude *= 0.9;
}
else if (change.compare(L"climb") == 0)
{
targetAltitude *= 1.1;
}
resetActiveDestination();
}
/* Helicopter */
Helicopter::Helicopter(json::value json, int ID) : AirUnit(json, ID)
{
log("New Helicopter created with ID: " + to_string(ID));
};
void Helicopter::changeSpeed(wstring change)
{
if (change.compare(L"stop") == 0)
{
/* Air units can't hold a position, so we can only set them to hold. At the moment, this will erase any other command. TODO: helicopters should be able to hover in place */
activePath.clear();
}
else if (change.compare(L"slow") == 0)
{
targetSpeed *= 0.9;
resetActiveDestination();
}
else if (change.compare(L"fast") == 0)
{
targetSpeed *= 1.1;
resetActiveDestination();
}
}
void Helicopter::changeAltitude(wstring change)
{
if (change.compare(L"descend") == 0)
{
targetAltitude *= 0.9;
}
else if (change.compare(L"climb") == 0)
{
targetAltitude *= 1.1;
}
resetActiveDestination();
}
/* Ground unit */
GroundUnit::GroundUnit(json::value json, int ID) : Unit(json, ID)
{
log("New Ground Unit created with ID: " + to_string(ID));
};
void GroundUnit::AIloop()
{
/* Call the common AI loop */
Unit::AIloop();
/* Ground unit AI Loop */
if (activeDestination != NULL)
{
double newDist = 0;
geod.Inverse(latitude, longitude, activeDestination.lat, activeDestination.lng, newDist);
if (newDist < GROUND_DEST_DIST_THR)
{
/* Destination reached */
activePath.pop_front();
log(unitName + L" destination reached");
}
}
}
void GroundUnit::changeSpeed(wstring change)
{
if (change.compare(L"stop") == 0)
{
}
else if (change.compare(L"slow") == 0)
{
}
else if (change.compare(L"fast") == 0)
{
}
}
/* Navy Unit */
NavyUnit::NavyUnit(json::value json, int ID) : Unit(json, ID)
{
log("New Navy Unit created with ID: " + to_string(ID));
};
void NavyUnit::AIloop()
{
/* Call the common AI loop */
Unit::AIloop();
/* Navy unit AI Loop */
if (activeDestination != NULL)
{
double newDist = 0;
geod.Inverse(latitude, longitude, activeDestination.lat, activeDestination.lng, newDist);
if (newDist < GROUND_DEST_DIST_THR)
{
/* Destination reached */
activePath.pop_front();
log(unitName + L" destination reached");
}
}
}
void NavyUnit::changeSpeed(wstring change)
{
if (change.compare(L"stop") == 0)
{
}
else if (change.compare(L"slow") == 0)
{
}
else if (change.compare(L"fast") == 0)
{
}
}
/* Weapon */
Weapon::Weapon(json::value json, int ID) : Unit(json, ID)
{
};
/* Missile */
Missile::Missile(json::value json, int ID) : Weapon(json, ID)
{
log("New Missile created with ID: " + to_string(ID));
};
/* Bomb */
Bomb::Bomb(json::value json, int ID) : Weapon(json, ID)
{
log("New Bomb created with ID: " + to_string(ID));
};

View File

@ -81,5 +81,36 @@ void registerLuaFunctions(lua_State* L)
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");
}
}
}

View File

@ -4,6 +4,8 @@
#include "unit.h"
#include "utils.h"
UnitsFactory::UnitsFactory(lua_State* L)
{
LogInfo(L, "Units Factory constructor called successfully");
@ -34,9 +36,46 @@ void UnitsFactory::update(lua_State* L)
int ID = p.first;
if (units.count(ID) == 0)
{
units[ID] = new Unit(p.second, ID);
json::value type = static_cast<json::value>(p.second)[L"Type"];
if (type.has_number_field(L"level1"))
{
if (type[L"level1"].as_number().to_int32() == 1)
{
if (type[L"level2"].as_number().to_int32() == 1)
{
units[ID] = dynamic_cast<Unit*>(new Aircraft(p.second, ID));
}
else if (type[L"level2"].as_number().to_int32() == 2)
{
units[ID] = dynamic_cast<Unit*>(new Helicopter(p.second, ID));
}
}
else if (type[L"level1"].as_number().to_int32() == 2)
{
units[ID] = dynamic_cast<Unit*>(new GroundUnit(p.second, ID));
}
else if (type[L"level1"].as_number().to_int32() == 3)
{
units[ID] = dynamic_cast<Unit*>(new NavyUnit(p.second, ID));
}
else if (type[L"level1"].as_number().to_int32() == 4)
{
if (type[L"level2"].as_number().to_int32() == 4)
{
units[ID] = dynamic_cast<Unit*>(new Missile(p.second, ID));
}
else if (type[L"level2"].as_number().to_int32() == 5)
{
units[ID] = dynamic_cast<Unit*>(new Bomb(p.second, ID));
}
}
}
}
/* Update the unit if present*/
if (units.count(ID) != 0)
{
units[ID]->update(p.second);
}
units[ID]->update(p.second);
}
/* Set the units that are not present in the JSON as dead (probably have been destroyed) */

View File

@ -60,7 +60,7 @@
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>

View File

@ -47,7 +47,7 @@
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>

View File

@ -1,5 +0,0 @@
// pch.cpp: source file corresponding to the pre-compiled header
#include "pch.h"
// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.

View File

@ -56,8 +56,6 @@
}
.unitmarker-unitName-div {
width: 200px;
left: -70px;
top: -20px;
position: absolute;
text-align: center;
@ -68,8 +66,6 @@
}
.unitmarker-name-div {
width: 200px;
left: -70px;
bottom: -20px;
position: absolute;
text-align: center;
@ -91,6 +87,18 @@
-webkit-text-stroke: 1px;
}
.unitmarker-speed-div {
width: 100%;
left: 0px;
top: 0px;
position: absolute;
text-align: left;
font: 800 12px Arial;
white-space: nowrap;
-webkit-text-fill-color: white;
-webkit-text-stroke: 1px;
}
.unitmarker-container-table-dead .unitmarker-name-div{
opacity: 0;
}

View File

@ -2,7 +2,7 @@
.unit-info-panel {
background-color: #202831;
height: 100px;
width: 400px;
width: 800px;
border: solid white 1px;
font-size: 12px;
position: fixed;
@ -28,8 +28,19 @@
justify-content: space-evenly;
}
.control-panel {
background-color: #202831;
height: 40px;
width: 100%;
font-size: 12px;
display: flex;
align-items: center;
justify-content: left;
}
.panel-table {
text-shadow: 1px 1px #000, -1px -1px #000, 1px -1px #000, -1px 1px #000;
height: 100%;
}
.panel-title {
@ -40,16 +51,20 @@
.panel-label {
font-size: 12px;
color: #d3e9ff;
margin: 5px;
width: 100px;
}
.panel-content {
font-size: 12px;
color: white;
margin: 5px;
}
.panel-button {
height: 30px;
width: 30px;
margin: 5px;
color: #d3e9ff;
font-size: 25px;
display: flex;
@ -77,4 +92,32 @@
.panel-button-disabled:active {
color: #e1f0ff;
}
.panel-select {
-webkit-padding-start: 2px;
margin: 10px;
height: 25px;
color: black;
font-family: "Lucida Console", "Courier New", monospace;
vertical-align: middle;
}
.panel-select option {
margin: 40px;
color: black;
font-family: "Lucida Console", "Courier New", monospace;
height: 25px;
margin: 5px;
vertical-align: middle;
}
#unit-info-table
{
width: 400px;
}
#tactical-info-table
{
width: 400px;
}

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

BIN
www/img/units/missile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -18,12 +18,14 @@
<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/LeftPanel.js"></script>
<script src="js/TopPanel.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>
@ -36,7 +38,8 @@
<body>
<table id="content-table">
<tr id="header">
<td colspan>
<td>
<div class="control-panel" id="top-control-panel"></div>
</td>
</tr>
<tr>

86
www/js/ControlPanel.js Normal file
View File

@ -0,0 +1,86 @@
class ControlPanel
{
constructor(id)
{
this._panel = document.getElementById(id);
/* 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.addCallback(() => this._onHumanButton());
this._AIButton.addCallback(() => this._onAIButton());
this._weaponsButton.addCallback(() => this._onWeaponsButton());
this._deadAliveButton.addCallback(() => this._cycleDeadAlive());
this._human = "labels";
this._humanButton.setSubicon(this._labelsIcon);
this._AI = "marker";
this._weapons = "marker";
this._deadAlive = "both";
}
getSettings()
{
return {'human': this._human, 'AI': this._AI, 'weapons': this._weapons, 'deadAlive': this._deadAlive}
}
_onHumanButton()
{
this._human = this._cycleVisibility(this._humanButton, this._human, this._humanIcon);
}
_onAIButton()
{
this._AI = this._cycleVisibility(this._AIButton, this._AI, this._AIIcon);
}
_onWeaponsButton()
{
this._weapons = this._cycleVisibility(this._weaponsButton, this._weapons, this._weaponsIcon);
}
_cycleVisibility(button, variable, icon)
{
if (variable === "labels")
{
variable = "marker";
button.setIcon(icon);
button.setSlashed(false);
}
else if (variable === "marker")
{
variable = "none";
button.setSlashed(true);
}
else
{
variable = "labels";
button.setSubicon(this._labelsIcon);
button.setSlashed(false);
}
return variable;
}
_cycleDeadAlive()
{
if (this._deadAlive === "both")
{
this._deadAlive = "alive";
this._deadAliveButton.setSlashed(true);
}
else
{
this._deadAlive = "both";
this._deadAliveButton.setSlashed(false);
}
}
}

View File

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

View File

@ -184,17 +184,32 @@ class Map
this._selectionWheel = new SelectionWheel(e.originalEvent.x, e.originalEvent.y, options);
}
/* Show unit selection for ground units */
/* Show unit selection for air units */
_unitSelectAir(e)
{
this.removeSelectionWheel();
this.removeSelectionScroll();
var options = unitTypes.air;
options.sort();
this._selectionScroll = new SelectionScroll(e.originalEvent.x, e.originalEvent.y, options, (type) => {
this._selectionScroll = new SelectionScroll(e.originalEvent.x, e.originalEvent.y, options, (unitType) => {
this.removeSelectionWheel();
this.removeSelectionScroll();
spawnAirUnit(type, e.latlng, this._activeCoalition);
this._unitSelectPayload(unitType, e);
});
}
/* Show weapon selection for air units */
_unitSelectPayload(unitType, e)
{
this.removeSelectionWheel();
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);
});
}

View File

@ -2,25 +2,38 @@ class MissionData
{
constructor()
{
this.bullseye = undefined;
this._bullseye = undefined;
this._bullseyeMarker = undefined;
}
update(data)
{
this.bullseye = data.missionData.bullseye;
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());
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));
this._bullseyeMarker .setLatLng(new L.LatLng(this._bullseye.lat, this._bullseye.lng));
}
}
}

View File

@ -1,13 +1,15 @@
class PanelButton
{
constructor(parent, type)
constructor(parent, icon)
{
this._div= document.createElement("div");
this._div.innerHTML = `<i class="fa ${type}"></i>`
this._div = document.createElement("div");
this.setIcon(icon);
this.setSlashed(false);
this._div.classList.add("panel-button");
parent.appendChild(this._div);
this.setEnabled(false);
this.setEnabled(true);
this._div.onclick = () => this._onClick();
this._callbacks = [];
@ -24,7 +26,6 @@ class PanelButton
{
this._div.classList.add("panel-button-disabled");
}
}
addCallback(callback)
@ -37,6 +38,30 @@ class PanelButton
this._callbacks = [];
}
setIcon(icon)
{
this._baseIcon = `<i class="fa ${icon}"></i>`;
this._div.innerHTML = this._baseIcon;
}
setSubicon(subicon)
{
this._baseIcon = `<div style="display: flex;">${this._baseIcon}<i style="font-size: 10px;" class="fa ${subicon}"></i></div>`;
this._div.innerHTML = this._baseIcon;
}
setSlashed(slashed)
{
if (slashed)
{
this._div.innerHTML = `<div style="display: flex; justify-content: center;">${this._baseIcon}<i style="position:fixed;" class="fa fa-slash"></i></div>`;
}
else
{
this._div.innerHTML = this._baseIcon;
}
}
_onClick()
{
if (this._enabled)
@ -47,5 +72,4 @@ class PanelButton
}
}
}
}

View File

@ -1,10 +1,14 @@
class Unit
{
constructor(ID) {
constructor(ID, marker)
{
this.ID = ID;
this.marker = new L.Marker.UnitMarker([0, 0], {riseOnHover: true});
this.marker.addTo(map.getMap()).on('click', (e) => this.onClick(e));
this.marker.addTo(map.getMap()).on('contextmenu', (e) => this.onRightClick(e));
this.selectable = true;
// 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;
@ -12,6 +16,8 @@ class Unit
this._pathPolyline = new L.Polyline([], {color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1});
this._pathPolyline.addTo(map.getMap());
this._targetsPolylines = [];
}
update(response)
@ -27,21 +33,15 @@ class Unit
this.alive = response["alive"];
this.type = response["type"];
this.flags = response["flags"];
this.activePath = response["activePath"]
this.speed = response["speed"];
this.currentTask = response["currentTask"];
/* Only present if an active path is available */
if ("activePath" in response)
{
this.activePath = response["activePath"]
}
this.missionData = missionData.getUnitData(this.ID)
this.setSelected(this.getSelected() & this.alive)
this.drawMarker();
if (!this.alive)
{
this.setSelected(false);
}
if (this._selected && this.activePath != undefined)
if (this.getSelected() && this.activePath != undefined)
{
this.drawPath();
}
@ -49,32 +49,22 @@ class Unit
{
this.clearPath();
}
}
getCategory()
{
if (this.type.level1 == 1)
this.clearTargets();
this.missionData = missionData.getUnitData(this.ID);
if (this.missionData != undefined)
{
return 'air';
}
else if (this.type.level1 == 2)
{
return 'ground';
}
else if (this.type.level1 == 3)
{
return 'navy';
}
else
{
return undefined;
if (this.getSelected())
{
this.drawTargets();
}
}
}
setSelected(selected)
{
/* Only alive units can be selected */
if (this.alive || !selected)
// Only alive units can be selected. Some units are not selectable (weapons)
if ((this.alive || !selected) && this.selectable && this._selected != selected)
{
this._selected = selected;
this.marker.setSelected(selected);
@ -89,6 +79,7 @@ class Unit
addDestination(latlng)
{
// TODO move in dedicated file
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
@ -103,11 +94,11 @@ class Unit
{
var newPath = this.activePath;
newPath[(Object.keys(newPath).length + 1).toString()] = latlng;
command = {"ID": this.ID, "unitName": this.unitName, "path": newPath}
command = {"ID": this.ID, "path": newPath}
}
else
{
command = {"ID": this.ID, "unitName": this.unitName, "path": {"1": latlng}}
command = {"ID": this.ID, "path": {"1": latlng}}
}
var data = {"setPath": command}
@ -134,27 +125,36 @@ class Unit
unitsManager.onUnitRightClick(this.ID);
}
drawMarker()
drawMarker(settings)
{
var zIndex = this.marker.getZIndex();
var newLatLng = new L.LatLng(this.latitude, this.longitude);
this.marker.setLatLng(newLatLng);
this.marker.setAngle(this.heading);
this.marker.setZIndex(zIndex);
this.marker.setAlive(this.alive);
this.marker.setAltitude(this.altitude);
this.marker.setHuman(this.flags.Human);
this.marker.setCoalitionID(this.coalitionID);
this.marker.setUnitName(this.unitName);
this.marker.setName(this.name);
if (this.getCategory() == "air")
// Hide the marker if disabled
if ((settings === 'none' || (controlPanel.getSettings().deadAlive === "alive" && !this.alive)))
{
this.marker.setImage("img/units/air.png");
// Remove the marker if present
if (map.getMap().hasLayer(this.marker))
{
map.getMap().removeLayer(this.marker);
}
}
else if (this.getCategory() == "ground")
{
this.marker.setImage("img/units/ground.png")
else {
// Add the marker if not present
if (!map.getMap().hasLayer(this.marker))
{
this.marker.addTo(map.getMap());
}
// Set the marker vibility
this.marker.setLabelsVisibility((settings === 'labels' || this.getSelected()) && this.alive);
// Draw the marker
var zIndex = this.marker.getZIndex();
var newLatLng = new L.LatLng(this.latitude, this.longitude);
this.marker.setLatLng(newLatLng);
this.marker.setAngle(this.heading);
this.marker.setZIndex(zIndex);
this.marker.setAlive(this.alive);
this.marker.setAltitude(this.altitude);
this.marker.setSpeed(this.speed);
}
}
@ -163,21 +163,21 @@ class Unit
var _points = [];
_points.push(new L.LatLng(this.latitude, this.longitude));
/* Add markers if missing */
// Add markers if missing
while (this._pathMarkers.length < Object.keys(this.activePath).length)
{
var marker = L.marker([0, 0]).addTo(map.getMap());
this._pathMarkers.push(marker);
}
/* Remove markers if too many */
// Remove markers if too many
while (this._pathMarkers.length > Object.keys(this.activePath).length)
{
map.getMap().removeLayer(this._pathMarkers[this._pathMarkers.length - 1]);
this._pathMarkers.splice(this._pathMarkers.length - 1, 1)
}
/* Update the position of the existing markers (to avoid creating markers uselessly) */
// Update the position of the existing markers (to avoid creating markers uselessly)
for (let WP in this.activePath)
{
var destination = this.activePath[WP];
@ -197,8 +197,228 @@ class Unit
this._pathPolyline.setLatLngs([]);
}
drawTargets()
{
for (let typeIndex in this.missionData.targets)
{
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";
}
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)
}
}
}
clearTargets()
{
for (let index in this._targetsPolylines)
{
map.getMap().removeLayer(this._targetsPolylines[index])
}
}
attackUnit(targetID)
{
// Call DCS attackUnit function
attackUnit(this.ID, targetID);
}
changeSpeed(speedChange)
{
// 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 + " speed change request: " + speedChange);
}
};
var command = {"ID": this.ID, "change": speedChange}
var data = {"changeSpeed": command}
xhr.send(JSON.stringify(data));
}
changeAltitude(altitudeChange)
{
// 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 + " altitude change request: " + speedChange);
}
};
var command = {"ID": this.ID, "change": altitudeChange}
var data = {"changeAltitude": command}
xhr.send(JSON.stringify(data));
}
}
class AirUnit extends Unit
{
drawMarker()
{
if (this.flags.human)
{
super.drawMarker(controlPanel.getSettings().human);
}
else
{
super.drawMarker(controlPanel.getSettings().AI);
}
}
}
class Aircraft extends AirUnit
{
constructor(ID, data)
{
var marker = new L.Marker.UnitMarker.AirUnitMarker.AircraftMarker({
riseOnHover: true,
unitName: data.unitName,
name: data.name,
human: data.flags.Human,
coalitionID: data.coalitionID
});
super(ID, marker);
}
}
class Helicopter extends AirUnit
{
constructor(ID, data)
{
var marker = new L.Marker.UnitMarker.AirUnitMarker.HelicopterMarker({
riseOnHover: true,
unitName: data.unitName,
name: data.name,
human: data.flags.Human,
coalitionID: data.coalitionID
});
super(ID, marker);
}
}
class GroundUnit extends Unit
{
constructor(ID, data)
{
var marker = new L.Marker.UnitMarker.GroundMarker({
riseOnHover: true,
unitName: data.unitName,
name: data.name,
human: data.flags.Human,
coalitionID: data.coalitionID
});
super(ID, marker);
}
drawMarker()
{
super.drawMarker(controlPanel.getSettings().AI);
}
}
class NavyUnit extends Unit
{
constructor(ID, data)
{
var marker = new L.Marker.UnitMarker.NavyMarker({
riseOnHover: true,
unitName: data.unitName,
name: data.name,
human: data.flags.Human,
coalitionID: data.coalitionID
});
super(ID, marker);
}
drawMarker()
{
super.drawMarker(controlPanel.getSettings().AI);
}
}
class Weapon extends Unit
{
constructor(ID, data)
{
// Weapons can not be selected
self.selectable = false;
super(ID, data);
}
drawMarker()
{
super.drawMarker(controlPanel.getSettings().weapons);
}
onClick(e)
{
// Weapons can not be clicked
}
onRightClick(e)
{
// Weapons can not be clicked
}
}
class Missile extends Weapon
{
constructor(ID, data)
{
var marker = new L.Marker.UnitMarker.WeaponMarker.MissileMarker({
riseOnHover: true,
unitName: "",
name: data.name,
human: data.flags.Human,
coalitionID: data.coalitionID
});
super(ID, marker);
}
}
class Bomb extends Weapon
{
constructor(ID, data)
{
var marker = new L.Marker.UnitMarker.WeaponMarker.BombMarker({
riseOnHover: true,
unitName: "",
name: data.name,
human: data.flags.Human,
coalitionID: data.coalitionID
});
super(ID, marker);
}
}

View File

@ -1,4 +1,4 @@
class TopPanel
class UnitControlPanel
{
constructor(id)
{
@ -44,5 +44,4 @@ class TopPanel
this._descendButton.setEnabled(false);
this._climbButton.setEnabled(false);
}
}

View File

@ -1,4 +1,4 @@
class LeftPanel
class UnitInfoPanel
{
constructor(id)
{
@ -16,7 +16,8 @@ class LeftPanel
}
else
{
this._showUnitData();
this._panel.style.bottom = "-80px";
}
}
else
@ -30,8 +31,25 @@ class LeftPanel
{
if (selectedUnit !== undefined)
{
var loadout = "";
for (let index in selectedUnit.missionData.ammo)
{
if (selectedUnit.missionData != undefined)
{
var ammo = selectedUnit.missionData.ammo[index];
var displayName = ammo.desc.displayName;
var amount = ammo.count;
loadout += amount + "x" + displayName;
if (parseInt(index) < Object.keys(selectedUnit.missionData.ammo).length - 1)
{
loadout += ", ";
}
}
}
this._panel.innerHTML = `
<table class="panel-table">
<div style="display: flex">
<table class="panel-table" id="unit-info-table">
<tr>
<td colspan="4" class="panel-title">
UNIT INFO
@ -65,6 +83,20 @@ class LeftPanel
${Math.floor(selectedUnit.altitude / 0.3048) + "ft"}
</td>
</tr>
<tr>
<td class="panel-label">
Ground speed:
</td>
<td class="panel-content">
${Math.floor(selectedUnit.speed * 1.94384) + "kts"}
</td>
<td class="panel-label">
Fuel:
</td>
<td class="panel-content">
${Math.floor(selectedUnit.missionData.fuel * 100) + "%"}
</td>
</tr>
<tr>
<td class="panel-label">
Position:
@ -76,6 +108,37 @@ class LeftPanel
</td>
</tr>
</table>
<table class="panel-table" id="tactical-info-table">
<tr>
<td colspan="4" class="panel-title">
TACTICAL INFO
</td>
</tr>
<tr>
<td class="panel-label">
Current task:
</td>
<td class="panel-content" colspan="3">
${selectedUnit.currentTask}
</td>
</tr>
<tr>
<td class="panel-label">
Weapons:
</td>
<td class="panel-content" colspan="3">
${loadout}
</td>
</tr>
<tr>
</tr>
<tr>
</td>
</tr>
</table>
</div>
`;
}
else

View File

@ -1,119 +1,108 @@
L.Marker.UnitMarker = L.Marker.extend(
{
// Set the unit name and unit icon
setUnitName: function(unitName)
options: {
unitName: "No name",
name: "N/A",
human: false,
coalitionID: 2,
iconSrc: "img/units/undefined.png"
},
// Marker constructor
initialize: function(options) {
this._latlng = new L.LatLng(0, 0);
L.setOptions(this, options);
var icon = new L.DivIcon({
html: `<table class="unitmarker-container-table" id="container-table">
<tr>
<td>
<img class="unitmarker-selection-img" id="selection-img" src="img/selection.png">
<img class="unitmarker-icon-img" id="icon-img">
<div class="unitmarker-unitName-div" id="unitName"></div>
<div class="unitmarker-altitude-div" id="altitude-div"></div>
<div class="unitmarker-speed-div" id="speed-div"></div>
<div class="unitmarker-name-div" id="name"></div>
</td>
</tr>
</table>`,
className: 'unit-marker-icon'}); // Set the unit marker, className must be set to avoid white square
this.setIcon(icon);
},
// When the marker is added to the map, the source image and the style are set. This can not be done before.
onAdd: function (map)
{
// TODO: move in constructor and call only once (does not work in addInitHook for some reason)
L.Marker.prototype.onAdd.call(this, map);
this._icon.querySelector("#icon-img").src = this.options.iconSrc;
this._icon.style.outline = "transparent"; // Removes the rectangular outline
if (this.unitName !== unitName)
{
// Set the unit name in the marker
var unitNameDiv = this._icon.querySelector("#unitName");
if (unitNameDiv!== undefined)
{
if (this._human)
{
unitNameDiv.innerHTML = `<i class="fas fa-user"></i> ${unitName}`;
}
else
{
unitNameDiv.innerHTML = `${unitName}`;
}
}
}
this.unitName = unitName;
},
setName: function(name)
{
// TODO: move in constructor and call only once (does not work in addInitHook for some reason)
this._icon.style.outline = "transparent"; // Removes the rectangular outline
if (this.name !== name)
{
// Set the unit name in the marker
var nameDiv = this._icon.querySelector("#name");
if (nameDiv!== undefined)
{
nameDiv.innerHTML = name;
}
}
this.name = name;
},
setCoalitionID: function(coalitionID)
{
var img = this._icon.querySelector("#icon-img");
if (img!== undefined)
// Set the unit name in the marker
var unitNameDiv = this._icon.querySelector("#unitName");
if (this.options.human)
{
if (coalitionID == 2)
{
img.classList.add("unitmarker-icon-img-blue");
}
else
{
img.classList.add("unitmarker-icon-img-red");
}
unitNameDiv.innerHTML = `<i class="fas fa-user"></i> ${this.options.unitName}`;
}
},
setImage: function(icon)
{
// Set the unit icon
var img = this._icon.querySelector("#icon-img");
if (img !== undefined)
else
{
img.src = icon;
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;
nameDiv.style.left = (-(nameDiv.offsetWidth - this._icon.querySelector("#icon-img").height) / 2) + "px";
// Set the coalitionID
var img = this._icon.querySelector("#icon-img");
if (this.options.coalitionID == 2)
{
img.classList.add("unitmarker-icon-img-blue");
}
else
{
img.classList.add("unitmarker-icon-img-red");
}
},
// If the unit is not alive it is drawn with darker colours
setAlive: function(alive)
{
var table = this._icon.querySelector("#container-table");
if (table!== undefined)
if (alive)
{
if (alive)
{
table.classList.remove("unitmarker-container-table-dead");
}
else
{
table.classList.add("unitmarker-container-table-dead");
}
table.classList.remove("unitmarker-container-table-dead");
}
else
{
table.classList.add("unitmarker-container-table-dead");
}
this.alive = alive;
},
// Set heading
setAngle: function(angle)
{
if (this._angle !== angle){
var img = this._icon.querySelector("#icon-img");
if (img !== undefined)
{
img.style.transform = "rotate(" + angle + "rad)";
}
}
this._angle = angle;
var img = this._icon.querySelector("#icon-img");
img.style.transform = "rotate(" + angle + "rad)";
},
// Set altitude
setAltitude: function(altitude)
{
if (this._altitude !== altitude){
var div = this._icon.querySelector("#altitude-div");
if (div !== undefined)
{
div.innerHTML = Math.round(altitude / 0.3048 / 100) / 10;
}
}
this._altitude = altitude;
var div = this._icon.querySelector("#altitude-div");
div.innerHTML = Math.round(altitude / 0.3048 / 100) / 10;
},
// Set speed
setSpeed: function(speed)
{
var div = this._icon.querySelector("#speed-div");
div.innerHTML = Math.round(speed * 1.94384);
},
// Set hovered (mouse cursor is over the marker)
setHovered: function(hovered)
{
var img = this._icon.querySelector("#icon-img");
@ -130,10 +119,11 @@ L.Marker.UnitMarker = L.Marker.extend(
}
},
// Set selected
setSelected: function(selected)
{
var selectedImg = this._icon.querySelector("#selection-img");
if (selectedImg!== undefined)
if (selectedImg !== undefined)
{
if (selected)
{
@ -159,16 +149,23 @@ L.Marker.UnitMarker = L.Marker.extend(
}
},
setHuman: function(human)
setLabelsVisibility(visibility)
{
this._human = human;
this._icon.querySelector("#unitName").style.opacity = visibility ? "1": "0";
this._icon.querySelector("#unitName").innerHTML = visibility ? this.options.unitName : "";
//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";
this._icon.querySelector("#speed-div").style.opacity = visibility ? "1": "0";
},
// Set the icon zIndex
setZIndex: function(zIndex)
{
this._icon.style.zIndex = zIndex;
},
// Get the icon zIndex
getZIndex: function()
{
return this._icon.style.zIndex;
@ -176,31 +173,72 @@ L.Marker.UnitMarker = L.Marker.extend(
}
)
// By default markers can be hovered and clicked
L.Marker.UnitMarker.addInitHook(function()
{
var icon = new L.DivIcon({html: iconHtml, className: 'DCSUnit-marker-icon'}); // Set the unit marker, className must be set to avoid white square
this.setIcon(icon);
this.on('mouseover',function(e) {
this.on('mouseover', function(e) {
if (e.target.alive)
{
e.target.setHovered(true);
}
});
this.on('mouseout',function(e) {
this.on('mouseout', function(e) {
e.target.setHovered(false);
});
});
var iconHtml = `<table class="unitmarker-container-table" id="container-table">
<tr>
<td>
<img class="unitmarker-selection-img" id="selection-img" src="img/selection.png">
<img class="unitmarker-icon-img" id="icon-img">
<div class="unitmarker-unitName-div" id="unitName"></div>
<div class="unitmarker-altitude-div" id="altitude-div"></div>
<div class="unitmarker-name-div" id="name"></div>
</td>
</tr>
</table>`
/* Air Units ***********************************/
L.Marker.UnitMarker.AirUnitMarker = L.Marker.UnitMarker.extend({})
L.Marker.UnitMarker.AirUnitMarker.addInitHook(function(){});
// Aircraft
L.Marker.UnitMarker.AirUnitMarker.AircraftMarker = L.Marker.UnitMarker.AirUnitMarker.extend({});
L.Marker.UnitMarker.AirUnitMarker.AircraftMarker.addInitHook(function()
{
this.options.iconSrc = "img/units/aircraft.png";
});
// Helicopter
L.Marker.UnitMarker.AirUnitMarker.HelicopterMarker = L.Marker.UnitMarker.AirUnitMarker.extend({})
L.Marker.UnitMarker.AirUnitMarker.HelicopterMarker.addInitHook(function()
{
this.options.iconSrc = "img/units/helicopter.png";
});
/* Ground Units ***********************************/
L.Marker.UnitMarker.GroundMarker = L.Marker.UnitMarker.extend({});
L.Marker.UnitMarker.GroundMarker.addInitHook(function()
{
this.options.iconSrc = "img/units/ground.png";
});
/* Navy Units ***********************************/
L.Marker.UnitMarker.NavyMarker = L.Marker.UnitMarker.extend({})
L.Marker.UnitMarker.NavyMarker.addInitHook(function()
{
this.options.iconSrc = "img/units/navy.png";
});
/* Weapon Units ***********************************/
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) {});
});
// Missile
L.Marker.UnitMarker.WeaponMarker.MissileMarker = L.Marker.UnitMarker.WeaponMarker.extend({})
L.Marker.UnitMarker.WeaponMarker.MissileMarker.addInitHook(function()
{
this.options.iconSrc = "img/units/missile.png";
});
// Bomb
L.Marker.UnitMarker.WeaponMarker.BombMarker = L.Marker.UnitMarker.WeaponMarker.extend({})
L.Marker.UnitMarker.WeaponMarker.BombMarker.addInitHook(function()
{
this.options.iconSrc = "img/units/bomb.png";
});

View File

@ -5,14 +5,19 @@ class UnitsManager
this._units = {};
}
units()
addUnit(ID, data)
{
return this._units;
// The name of the unit category is exactly the same as the constructor name
var constructor = eval(data.category);
if (constructor != undefined)
{
this._units[ID] = new constructor(ID, data);
}
}
addUnit(ID)
getUnit(ID)
{
this._units[ID] = new Unit(ID)
return this._units[ID];
}
removeUnit(ID)
@ -35,7 +40,7 @@ class UnitsManager
// Create the unit if missing from the local array, then update the data. Drawing is handled by leaflet.
if (!(ID in this._units))
{
this.addUnit(parseInt(ID));
this.addUnit(parseInt(ID), data["units"][ID]);
}
this._units[ID].update(data["units"][ID]);
}
@ -46,12 +51,12 @@ class UnitsManager
if (this.getSelectedUnits().length > 0)
{
map.setState("UNIT_SELECTED");
topPanel.enableButtons(true);
unitControlPanel.enableButtons(true);
}
else
{
map.setState("IDLE");
topPanel.disableButtons();
unitControlPanel.disableButtons();
}
}
@ -92,38 +97,42 @@ class UnitsManager
addDestination(latlng)
{
for (let ID in this._units)
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
if (this._units[ID].getSelected())
{
this._units[ID].addDestination(latlng);
}
selectedUnits[idx].addDestination(latlng);
}
}
clearDestinations()
{
for (let ID in this._units)
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
if (this._units[ID].getSelected())
{
this._units[ID].clearDestinations();
}
selectedUnits[idx].clearDestinations();
}
}
selectedUnitsMove()
{
var asd = 1;
}
selectedUnitsChangeSpeed(speedChange)
{
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
selectedUnits[idx].changeSpeed(speedChange);
}
}
selectedUnitsChangeAltitude(altitudeChange)
{
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
selectedUnits[idx].changeAltitude(altitudeChange);
}
}
}

View File

@ -1,7 +1,8 @@
var missionData;
var controlPanel;
var unitsManager;
var leftPanel;
var topPanel;
var unitInfoPanel;
var unitControlPanel;
var map;
var RESTaddress = "http://localhost:30000/restdemo";
@ -11,8 +12,9 @@ function setup()
missionData = new MissionData();
unitsManager = new UnitsManager();
leftPanel = new LeftPanel("left-panel");
topPanel = new TopPanel("top-panel");
unitInfoPanel = new UnitInfoPanel("left-panel");
unitControlPanel = new UnitControlPanel("top-panel");
controlPanel = new ControlPanel("top-control-panel");
map = new Map();
@ -22,8 +24,8 @@ function setup()
function resize()
{
var topPanelHeight = document.getElementById("header").offsetHeight;
document.getElementById("map").style.height = `${window.innerHeight - topPanelHeight - 10}px`;
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`
}
@ -39,7 +41,7 @@ function update()
missionData.update(data);
unitsManager.update(data);
leftPanel.update(unitsManager.getSelectedUnits());
unitInfoPanel.update(unitsManager.getSelectedUnits());
};
xmlHttp.onerror = function () {

1
www/js/payloadNames.js Normal file

File diff suppressed because one or more lines are too long