Merge branch 'main' into 313-fix-getByRange-function
@ -1,2 +1,3 @@
|
||||
copy .\\node_modules\\leaflet\\dist\\leaflet.css .\\public\\stylesheets\\leaflet\\leaflet.css
|
||||
copy .\\node_modules\\leaflet.nauticscale\\dist\\leaflet.nauticscale.js .\\public\\javascripts\\leaflet.nauticscale.js
|
||||
copy .\\node_modules\\leaflet-path-drag\\dist\\L.Path.Drag.js .\\public\\javascripts\\L.Path.Drag.js
|
||||
863
client/demo.js
@ -1,641 +1,250 @@
|
||||
var enc = new TextEncoder();
|
||||
|
||||
const DEMO_UNIT_DATA = {
|
||||
["1"]:{
|
||||
baseData: {
|
||||
AI: false,
|
||||
name: "KC-135",
|
||||
unitName: "Olympus 1-1 aka Mr. Very long name",
|
||||
groupName: "Group 2",
|
||||
alive: true,
|
||||
category: "Aircraft",
|
||||
},
|
||||
flightData: {
|
||||
latitude: 37.20,
|
||||
longitude: -115.80,
|
||||
altitude: 2000,
|
||||
heading: 0.5,
|
||||
speed: 300
|
||||
},
|
||||
missionData: {
|
||||
fuel: 50,
|
||||
flags: {Human: false},
|
||||
ammo: [
|
||||
{
|
||||
count: 4,
|
||||
desc: {
|
||||
displayName: "AIM-120"
|
||||
}
|
||||
},
|
||||
{
|
||||
count: 2,
|
||||
desc: {
|
||||
displayName: "AIM-7"
|
||||
}
|
||||
}
|
||||
],
|
||||
targets: [],
|
||||
hasTask: true,
|
||||
coalition: "blue"
|
||||
},
|
||||
formationData: {
|
||||
formation: "Echelon",
|
||||
isLeader: false,
|
||||
isWingman: false,
|
||||
leaderID: null,
|
||||
wingmen: [],
|
||||
wingmenIDs: []
|
||||
},
|
||||
taskData: {
|
||||
currentTask: "Holding",
|
||||
currentState: "Idle",
|
||||
activePath: undefined,
|
||||
desiredSpeed: 400,
|
||||
desiredSpeedType: "CAS",
|
||||
desiredAltitude: 3000,
|
||||
desiredAltitudeType: "ASL",
|
||||
isTanker: false,
|
||||
|
||||
},
|
||||
optionsData: {
|
||||
ROE: "Designated",
|
||||
reactionToThreat: "Abort",
|
||||
}
|
||||
["1"]:{ alive: true, human: false, controlled: true, coalition: 2, country: 0, name: "KC-135", unitName: "Cool guy 1-1", groupName: "Cool group 1", state: 3, task: "Being cool!",
|
||||
hasTask: true, position: { lat: 37, lng: -116, alt: 1000 }, speed: 200, heading: 45, isTanker: true, isAWACS: false, onOff: true, followRoads: false, fuel: 50,
|
||||
desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0,
|
||||
formationOffset: { x: 0, y: 0, z: 0 },
|
||||
targetID: 2,
|
||||
targetPosition: { lat: 0, lng: 0, alt: 0 },
|
||||
ROE: 2,
|
||||
reactionToThreat: 1,
|
||||
emissionsCountermeasures: 1,
|
||||
TACAN: { isOn: false, XY: 'Y', callsign: 'TKR', channel: 40 },
|
||||
radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 },
|
||||
generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false },
|
||||
ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ],
|
||||
contacts: [],
|
||||
activePath: [ {lat: 38, lng: -115, alt: 0}, {lat: 38, lng: -114, alt: 0} ]
|
||||
},
|
||||
["2"]:{
|
||||
baseData: {
|
||||
AI: true,
|
||||
name: "KC-135",
|
||||
unitName: "Olympus 1-2",
|
||||
groupName: "Group 3",
|
||||
alive: true,
|
||||
category: "Aircraft",
|
||||
},
|
||||
flightData: {
|
||||
latitude: 37.2,
|
||||
longitude: -115.75,
|
||||
altitude: 2000,
|
||||
heading: 0.5,
|
||||
speed: 300
|
||||
},
|
||||
missionData: {
|
||||
fuel: 0.5,
|
||||
flags: {human: false},
|
||||
ammo: [],
|
||||
targets: [],
|
||||
hasTask: true,
|
||||
coalition: "red"
|
||||
},
|
||||
formationData: {
|
||||
formation: "Echelon",
|
||||
isLeader: false,
|
||||
isWingman: false,
|
||||
leaderID: null,
|
||||
wingmen: [],
|
||||
wingmenIDs: []
|
||||
},
|
||||
taskData: {
|
||||
currentTask: "Example task",
|
||||
activePath: undefined,
|
||||
desiredSpeed: 300,
|
||||
desiredAltitude: 3000
|
||||
},
|
||||
optionsData: {
|
||||
ROE: "Designated",
|
||||
reactionToThreat: "Abort",
|
||||
}
|
||||
},
|
||||
["3"]:{
|
||||
baseData: {
|
||||
AI: true,
|
||||
name: "M-60",
|
||||
unitName: "Olympus 1-3",
|
||||
groupName: "Group 4",
|
||||
alive: true,
|
||||
category: "GroundUnit",
|
||||
},
|
||||
flightData: {
|
||||
latitude: 37.175,
|
||||
longitude: -115.8,
|
||||
altitude: 2000,
|
||||
heading: 0.5,
|
||||
speed: 300
|
||||
},
|
||||
missionData: {
|
||||
fuel: 0.5,
|
||||
flags: {human: false},
|
||||
ammo: [],
|
||||
targets: [],
|
||||
hasTask: true,
|
||||
coalition: "blue"
|
||||
},
|
||||
formationData: {
|
||||
formation: "Echelon",
|
||||
isLeader: false,
|
||||
isWingman: false,
|
||||
leaderID: null,
|
||||
wingmen: [],
|
||||
wingmenIDs: []
|
||||
},
|
||||
taskData: {
|
||||
currentTask: "Example task",
|
||||
activePath: undefined,
|
||||
desiredSpeed: 400,
|
||||
desiredAltitude: 3000,
|
||||
onOff: false
|
||||
},
|
||||
optionsData: {
|
||||
ROE: "None",
|
||||
reactionToThreat: "None",
|
||||
}
|
||||
},
|
||||
["4"]:{
|
||||
baseData: {
|
||||
AI: true,
|
||||
name: "2S6 Tunguska",
|
||||
unitName: "Olympus 1-4",
|
||||
alive: true,
|
||||
category: "GroundUnit",
|
||||
},
|
||||
flightData: {
|
||||
latitude: 37.175,
|
||||
longitude: -115.75,
|
||||
altitude: 2000,
|
||||
heading: 0.5,
|
||||
speed: 300
|
||||
},
|
||||
missionData: {
|
||||
fuel: 0.5,
|
||||
flags: {human: false},
|
||||
ammo: [],
|
||||
targets: [],
|
||||
hasTask: true,
|
||||
coalition: "red"
|
||||
},
|
||||
formationData: {
|
||||
formation: "Echelon",
|
||||
isLeader: false,
|
||||
isWingman: false,
|
||||
leaderID: null,
|
||||
wingmen: [],
|
||||
wingmenIDs: []
|
||||
},
|
||||
taskData: {
|
||||
currentTask: "Example task",
|
||||
activePath: undefined,
|
||||
desiredSpeed: 400,
|
||||
desiredAltitude: 3000
|
||||
},
|
||||
optionsData: {
|
||||
ROE: "None",
|
||||
reactionToThreat: "None",
|
||||
}
|
||||
},
|
||||
["5"]:{
|
||||
baseData: {
|
||||
AI: true,
|
||||
name: "M-60",
|
||||
unitName: "Olympus 1-3",
|
||||
groupName: "Group 1",
|
||||
alive: true,
|
||||
category: "GroundUnit",
|
||||
},
|
||||
flightData: {
|
||||
latitude: 37.15,
|
||||
longitude: -115.8,
|
||||
altitude: 2000,
|
||||
heading: 0.5,
|
||||
speed: 300
|
||||
},
|
||||
missionData: {
|
||||
fuel: 0.5,
|
||||
flags: {human: false},
|
||||
ammo: [],
|
||||
targets: [],
|
||||
hasTask: true,
|
||||
coalition: "blue"
|
||||
},
|
||||
formationData: {
|
||||
formation: "Echelon",
|
||||
isLeader: false,
|
||||
isWingman: false,
|
||||
leaderID: null,
|
||||
wingmen: [],
|
||||
wingmenIDs: []
|
||||
},
|
||||
taskData: {
|
||||
currentTask: "Example task",
|
||||
activePath: undefined,
|
||||
desiredSpeed: 400,
|
||||
desiredAltitude: 3000
|
||||
},
|
||||
optionsData: {
|
||||
ROE: "None",
|
||||
reactionToThreat: "None",
|
||||
}
|
||||
},
|
||||
["6"]:{
|
||||
baseData: {
|
||||
AI: true,
|
||||
name: "M-60",
|
||||
unitName: "Olympus 1-4",
|
||||
groupName: "Group 1",
|
||||
alive: true,
|
||||
category: "GroundUnit",
|
||||
},
|
||||
flightData: {
|
||||
latitude: 37.15,
|
||||
longitude: -115.75,
|
||||
altitude: 2000,
|
||||
heading: 0.5,
|
||||
speed: 300
|
||||
},
|
||||
missionData: {
|
||||
fuel: 0.5,
|
||||
flags: {human: false},
|
||||
ammo: [],
|
||||
targets: [],
|
||||
hasTask: true,
|
||||
coalition: "red"
|
||||
},
|
||||
formationData: {
|
||||
formation: "Echelon",
|
||||
isLeader: false,
|
||||
isWingman: false,
|
||||
leaderID: null,
|
||||
wingmen: [],
|
||||
wingmenIDs: []
|
||||
},
|
||||
taskData: {
|
||||
currentTask: "Example task",
|
||||
activePath: undefined,
|
||||
desiredSpeed: 400,
|
||||
desiredAltitude: 3000
|
||||
},
|
||||
optionsData: {
|
||||
ROE: "None",
|
||||
reactionToThreat: "None",
|
||||
}
|
||||
},
|
||||
["7"]:{
|
||||
baseData: {
|
||||
AI: true,
|
||||
name: "CVN-75 Very long name",
|
||||
unitName: "Olympus 1-7",
|
||||
groupName: "Group 1",
|
||||
alive: true,
|
||||
category: "NavyUnit",
|
||||
},
|
||||
flightData: {
|
||||
latitude: 37.125,
|
||||
longitude: -115.8,
|
||||
altitude: 2000,
|
||||
heading: 0.5,
|
||||
speed: 300
|
||||
},
|
||||
missionData: {
|
||||
fuel: 0.5,
|
||||
flags: {human: false},
|
||||
ammo: [],
|
||||
targets: [],
|
||||
hasTask: true,
|
||||
coalition: "blue"
|
||||
},
|
||||
formationData: {
|
||||
formation: "Echelon",
|
||||
isLeader: false,
|
||||
isWingman: false,
|
||||
leaderID: null,
|
||||
wingmen: [],
|
||||
wingmenIDs: []
|
||||
},
|
||||
taskData: {
|
||||
currentTask: "Example task",
|
||||
activePath: undefined,
|
||||
desiredSpeed: 400,
|
||||
desiredAltitude: 3000
|
||||
},
|
||||
optionsData: {
|
||||
ROE: "None",
|
||||
reactionToThreat: "None",
|
||||
}
|
||||
},
|
||||
["8"]:{
|
||||
baseData: {
|
||||
AI: true,
|
||||
name: "CVN-75",
|
||||
unitName: "Olympus 1-8",
|
||||
groupName: "Group 1",
|
||||
alive: true,
|
||||
category: "NavyUnit",
|
||||
},
|
||||
flightData: {
|
||||
latitude: 37.125,
|
||||
longitude: -115.75,
|
||||
altitude: 2000,
|
||||
heading: 0.5,
|
||||
speed: 300
|
||||
},
|
||||
missionData: {
|
||||
fuel: 0.5,
|
||||
flags: {human: false},
|
||||
ammo: [],
|
||||
targets: [],
|
||||
hasTask: true,
|
||||
coalition: "red"
|
||||
},
|
||||
formationData: {
|
||||
formation: "Echelon",
|
||||
isLeader: false,
|
||||
isWingman: false,
|
||||
leaderID: null,
|
||||
wingmen: [],
|
||||
wingmenIDs: []
|
||||
},
|
||||
taskData: {
|
||||
currentTask: "Example task",
|
||||
activePath: undefined,
|
||||
desiredSpeed: 400,
|
||||
desiredAltitude: 3000
|
||||
},
|
||||
optionsData: {
|
||||
ROE: "None",
|
||||
reactionToThreat: "None",
|
||||
}
|
||||
},
|
||||
["9"]:{
|
||||
baseData: {
|
||||
AI: true,
|
||||
name: "CVN-75",
|
||||
unitName: "Olympus 1-9",
|
||||
groupName: "Group 1",
|
||||
alive: true,
|
||||
category: "Aircraft",
|
||||
},
|
||||
flightData: {
|
||||
latitude: 37.10,
|
||||
longitude: -115.75,
|
||||
altitude: 2000,
|
||||
heading: 0.5,
|
||||
speed: 300
|
||||
},
|
||||
missionData: {
|
||||
fuel: 0.5,
|
||||
flags: {human: false},
|
||||
ammo: [],
|
||||
targets: [],
|
||||
hasTask: true,
|
||||
coalition: "red"
|
||||
},
|
||||
formationData: {
|
||||
formation: "Echelon",
|
||||
isLeader: false,
|
||||
isWingman: false,
|
||||
leaderID: null,
|
||||
wingmen: [],
|
||||
wingmenIDs: []
|
||||
},
|
||||
taskData: {
|
||||
currentTask: "Example task",
|
||||
activePath: undefined,
|
||||
desiredSpeed: 400,
|
||||
desiredAltitude: 3000
|
||||
},
|
||||
optionsData: {
|
||||
ROE: "None",
|
||||
reactionToThreat: "None",
|
||||
}
|
||||
},
|
||||
["10"]:{
|
||||
baseData: {
|
||||
AI: true,
|
||||
name: "CVN-75",
|
||||
unitName: "Olympus 1-10",
|
||||
groupName: "Group 1",
|
||||
alive: true,
|
||||
category: "Aircraft",
|
||||
},
|
||||
flightData: {
|
||||
latitude: 37.10,
|
||||
longitude: -115.8,
|
||||
altitude: 2000,
|
||||
heading: 0.5,
|
||||
speed: 300
|
||||
},
|
||||
missionData: {
|
||||
fuel: 0.5,
|
||||
flags: {human: false},
|
||||
ammo: [],
|
||||
targets: [],
|
||||
hasTask: true,
|
||||
coalition: "blue"
|
||||
},
|
||||
formationData: {
|
||||
formation: "Echelon",
|
||||
isLeader: false,
|
||||
isWingman: false,
|
||||
leaderID: null,
|
||||
wingmen: [],
|
||||
wingmenIDs: []
|
||||
},
|
||||
taskData: {
|
||||
currentTask: "Example task",
|
||||
activePath: undefined,
|
||||
desiredSpeed: 400,
|
||||
desiredAltitude: 3000
|
||||
},
|
||||
optionsData: {
|
||||
ROE: "None",
|
||||
reactionToThreat: "None",
|
||||
}
|
||||
},
|
||||
["11"]:{
|
||||
baseData: {
|
||||
AI: true,
|
||||
name: "CVN-75",
|
||||
unitName: "Olympus 1-11",
|
||||
groupName: "Group 1",
|
||||
alive: true,
|
||||
category: "Missile",
|
||||
},
|
||||
flightData: {
|
||||
latitude: 37.075,
|
||||
longitude: -115.80,
|
||||
altitude: 2000,
|
||||
heading: 0.5,
|
||||
speed: 300
|
||||
},
|
||||
missionData: {
|
||||
fuel: 0.5,
|
||||
flags: {human: false},
|
||||
ammo: [],
|
||||
targets: [],
|
||||
hasTask: true,
|
||||
coalition: "blue"
|
||||
},
|
||||
formationData: {
|
||||
formation: "Echelon",
|
||||
isLeader: false,
|
||||
isWingman: false,
|
||||
leaderID: null,
|
||||
wingmen: [],
|
||||
wingmenIDs: []
|
||||
},
|
||||
taskData: {
|
||||
currentTask: "Example task",
|
||||
activePath: undefined,
|
||||
desiredSpeed: 400,
|
||||
desiredAltitude: 3000
|
||||
},
|
||||
optionsData: {
|
||||
ROE: "None",
|
||||
reactionToThreat: "None",
|
||||
}
|
||||
},
|
||||
["12"]:{
|
||||
baseData: {
|
||||
AI: true,
|
||||
name: "CVN-75",
|
||||
unitName: "Olympus 1-12",
|
||||
groupName: "Group 1",
|
||||
alive: true,
|
||||
category: "Missile",
|
||||
},
|
||||
flightData: {
|
||||
latitude: 37.075,
|
||||
longitude: -115.75,
|
||||
altitude: 2000,
|
||||
heading: 0.6,
|
||||
speed: 300
|
||||
},
|
||||
missionData: {
|
||||
fuel: 0.5,
|
||||
flags: {human: false},
|
||||
ammo: [],
|
||||
targets: [],
|
||||
hasTask: true,
|
||||
coalition: "red"
|
||||
},
|
||||
formationData: {
|
||||
formation: "Echelon",
|
||||
isLeader: false,
|
||||
isWingman: false,
|
||||
leaderID: null,
|
||||
wingmen: [],
|
||||
wingmenIDs: []
|
||||
},
|
||||
taskData: {
|
||||
currentTask: "Example task",
|
||||
activePath: undefined,
|
||||
desiredSpeed: 400,
|
||||
desiredAltitude: 3000
|
||||
},
|
||||
optionsData: {
|
||||
ROE: "None",
|
||||
reactionToThreat: "None",
|
||||
}
|
||||
},
|
||||
["13"]:{
|
||||
baseData: {
|
||||
AI: true,
|
||||
name: "CVN-75",
|
||||
unitName: "Olympus 1-11",
|
||||
groupName: "Group 1",
|
||||
alive: true,
|
||||
category: "Bomb",
|
||||
},
|
||||
flightData: {
|
||||
latitude: 37.05,
|
||||
longitude: -115.8,
|
||||
altitude: 2000,
|
||||
heading: 0.5,
|
||||
speed: 300
|
||||
},
|
||||
missionData: {
|
||||
fuel: 0.5,
|
||||
flags: {human: false},
|
||||
ammo: [],
|
||||
targets: [],
|
||||
hasTask: true,
|
||||
coalition: "blue"
|
||||
},
|
||||
formationData: {
|
||||
formation: "Echelon",
|
||||
isLeader: false,
|
||||
isWingman: false,
|
||||
leaderID: null,
|
||||
wingmen: [],
|
||||
wingmenIDs: []
|
||||
},
|
||||
taskData: {
|
||||
currentTask: "Example task",
|
||||
activePath: undefined,
|
||||
desiredSpeed: 400,
|
||||
desiredAltitude: 3000
|
||||
},
|
||||
optionsData: {
|
||||
ROE: "None",
|
||||
reactionToThreat: "None",
|
||||
}
|
||||
},
|
||||
["14"]:{
|
||||
baseData: {
|
||||
AI: true,
|
||||
name: "CVN-75",
|
||||
unitName: "Olympus 1-12",
|
||||
groupName: "Group 1",
|
||||
alive: true,
|
||||
category: "Bomb",
|
||||
},
|
||||
flightData: {
|
||||
latitude: 37.05,
|
||||
longitude: -115.75,
|
||||
altitude: 2000,
|
||||
heading: 0.6,
|
||||
speed: 300
|
||||
},
|
||||
missionData: {
|
||||
fuel: 0.5,
|
||||
flags: {human: false},
|
||||
ammo: [],
|
||||
targets: [],
|
||||
hasTask: true,
|
||||
coalition: "red"
|
||||
},
|
||||
formationData: {
|
||||
formation: "Echelon",
|
||||
isLeader: false,
|
||||
isWingman: false,
|
||||
leaderID: null,
|
||||
wingmen: [],
|
||||
wingmenIDs: []
|
||||
},
|
||||
taskData: {
|
||||
currentTask: "Example task",
|
||||
activePath: undefined,
|
||||
desiredSpeed: 400,
|
||||
desiredAltitude: 3000
|
||||
},
|
||||
optionsData: {
|
||||
ROE: "None",
|
||||
reactionToThreat: "None",
|
||||
}
|
||||
["2"]:{ alive: true, human: false, controlled: false, coalition: 1, country: 0, name: "KC-135", unitName: "Cool guy 1-2", groupName: "Cool group 2", state: 1, task: "Being cool",
|
||||
hasTask: false, position: { lat: 36.9, lng: -116, alt: 1000 }, speed: 200, heading: 0, isTanker: false, isAWACS: false, onOff: true, followRoads: false, fuel: 50,
|
||||
desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0,
|
||||
formationOffset: { x: 0, y: 0, z: 0 },
|
||||
targetID: 0,
|
||||
targetPosition: { lat: 38, lng: -117, alt: 1000 },
|
||||
ROE: 2,
|
||||
reactionToThreat: 1,
|
||||
emissionsCountermeasures: 1,
|
||||
TACAN: { isOn: false, XY: 'Y', callsign: 'TKR', channel: 40 },
|
||||
radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 },
|
||||
generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false },
|
||||
ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ],
|
||||
contacts: [{ID: 1, detectionMethod: 4}],
|
||||
activePath: [ {lat: 38, lng: -115, alt: 0}, {lat: 38, lng: -114, alt: 0} ]
|
||||
}
|
||||
}
|
||||
|
||||
class DemoDataGenerator {
|
||||
constructor(unitsNumber)
|
||||
constructor()
|
||||
{
|
||||
this.demoUnits = this.generateRandomUnitsDemoData(unitsNumber);
|
||||
|
||||
}
|
||||
|
||||
units(req, res){
|
||||
var ret = this.demoUnits;
|
||||
for (let ID in this.demoUnits["units"]){
|
||||
this.demoUnits["units"][ID].flightData.latitude += 0.00001;
|
||||
var array = new Uint8Array();
|
||||
var time = Date.now();
|
||||
array = this.concat(array, this.uint64ToByteArray(BigInt(time)));
|
||||
for (let idx in DEMO_UNIT_DATA) {
|
||||
const unit = DEMO_UNIT_DATA[idx];
|
||||
array = this.concat(array, this.uint32ToByteArray(idx));
|
||||
array = this.appendString(array, "Aircraft", 1);
|
||||
array = this.appendUint8(array, unit.alive, 2);
|
||||
array = this.appendUint8(array, unit.human, 3);
|
||||
array = this.appendUint8(array, unit.controlled, 4);
|
||||
array = this.appendUint16(array, unit.coalition, 5);
|
||||
array = this.appendUint8(array, unit.country, 6);
|
||||
array = this.appendString(array, unit.name, 7);
|
||||
array = this.appendString(array, unit.unitName, 8);
|
||||
array = this.appendString(array, unit.groupName, 9);
|
||||
array = this.appendUint8(array, unit.state, 10);
|
||||
array = this.appendString(array, unit.task, 11);
|
||||
array = this.appendUint8(array, unit.hasTask, 12);
|
||||
array = this.appendCoordinates(array, unit.position, 13);
|
||||
array = this.appendDouble(array, unit.speed, 14);
|
||||
array = this.appendDouble(array, unit.heading, 15);
|
||||
array = this.appendUint8(array, unit.isTanker, 16);
|
||||
array = this.appendUint8(array, unit.isAWACS, 17);
|
||||
array = this.appendUint8(array, unit.onOff, 18);
|
||||
array = this.appendUint8(array, unit.followRoads, 19);
|
||||
array = this.appendUint16(array, unit.fuel, 20);
|
||||
array = this.appendDouble(array, unit.desiredSpeed, 21);
|
||||
array = this.appendUint8(array, unit.desiredSpeedType, 22);
|
||||
array = this.appendDouble(array, unit.desiredAltitude, 23);
|
||||
array = this.appendUint8(array, unit.desiredAltitudeType, 24);
|
||||
array = this.appendUint32(array, unit.leaderID, 25);
|
||||
array = this.appendOffset(array, unit.formationOffset, 26);
|
||||
array = this.appendUint32(array, unit.targetID, 27);
|
||||
array = this.appendCoordinates(array, unit.targetPosition, 28);
|
||||
array = this.appendUint8(array, unit.ROE, 29);
|
||||
array = this.appendUint8(array, unit.reactionToThreat, 30);
|
||||
array = this.appendUint8(array, unit.emissionsCountermeasures, 31);
|
||||
array = this.appendTACAN(array, unit.TACAN, 32);
|
||||
array = this.appendRadio(array, unit.radio, 33);
|
||||
array = this.appendRadio(array, unit.generalSettings, 34);
|
||||
array = this.appendAmmo(array, unit.ammo, 35);
|
||||
array = this.appendContacts(array, unit.contacts, 36);
|
||||
array = this.appendActivePath(array, unit.activePath, 37);
|
||||
array = this.concat(array, this.uint8ToByteArray(255));
|
||||
}
|
||||
ret.time = Date.now();
|
||||
res.send(JSON.stringify(ret));
|
||||
res.end(Buffer.from(array, 'binary'));
|
||||
};
|
||||
|
||||
concat(array1, array2) {
|
||||
var mergedArray = new Uint8Array(array1.length + array2.length);
|
||||
mergedArray.set(array1);
|
||||
mergedArray.set(array2, array1.length);
|
||||
return mergedArray;
|
||||
}
|
||||
|
||||
uint8ToByteArray(number) {
|
||||
var buffer = new ArrayBuffer(1);
|
||||
var longNum = new Uint8Array(buffer);
|
||||
longNum[0] = number;
|
||||
return Array.from(new Uint8Array(buffer));
|
||||
}
|
||||
|
||||
uint16ToByteArray(number) {
|
||||
var buffer = new ArrayBuffer(2);
|
||||
var longNum = new Uint16Array(buffer);
|
||||
longNum[0] = number;
|
||||
return Array.from(new Uint8Array(buffer));
|
||||
}
|
||||
|
||||
uint32ToByteArray(number) {
|
||||
var buffer = new ArrayBuffer(4);
|
||||
var longNum = new Uint32Array(buffer);
|
||||
longNum[0] = number;
|
||||
return Array.from(new Uint8Array(buffer));
|
||||
}
|
||||
|
||||
uint64ToByteArray(number) {
|
||||
var buffer = new ArrayBuffer(8);
|
||||
var longNum = new BigUint64Array(buffer);
|
||||
longNum[0] = number;
|
||||
return Array.from(new Uint8Array(buffer));
|
||||
}
|
||||
|
||||
doubleToByteArray(number) {
|
||||
var buffer = new ArrayBuffer(8);
|
||||
var longNum = new Float64Array(buffer);
|
||||
longNum[0] = number;
|
||||
return Array.from(new Uint8Array(buffer));
|
||||
}
|
||||
|
||||
appendUint8(array, number, datumIndex) {
|
||||
array = this.concat(array, this.uint8ToByteArray(datumIndex));
|
||||
array = this.concat(array, this.uint8ToByteArray(number));
|
||||
return array;
|
||||
}
|
||||
|
||||
appendUint16(array, number, datumIndex) {
|
||||
array = this.concat(array, this.uint8ToByteArray(datumIndex));
|
||||
array = this.concat(array, this.uint16ToByteArray(number));
|
||||
return array;
|
||||
}
|
||||
|
||||
appendUint32(array, number, datumIndex) {
|
||||
array = this.concat(array, this.uint8ToByteArray(datumIndex));
|
||||
array = this.concat(array, this.uint32ToByteArray(number));
|
||||
return array;
|
||||
}
|
||||
|
||||
appendDouble(array, number, datumIndex) {
|
||||
array = this.concat(array, this.uint8ToByteArray(datumIndex));
|
||||
array = this.concat(array, this.doubleToByteArray(number));
|
||||
return array;
|
||||
}
|
||||
|
||||
appendCoordinates(array, coordinates, datumIndex) {
|
||||
array = this.concat(array, this.uint8ToByteArray(datumIndex));
|
||||
array = this.concat(array, this.doubleToByteArray(coordinates.lat));
|
||||
array = this.concat(array, this.doubleToByteArray(coordinates.lng));
|
||||
array = this.concat(array, this.doubleToByteArray(coordinates.alt));
|
||||
return array;
|
||||
}
|
||||
|
||||
appendOffset(array, offset, datumIndex) {
|
||||
array = this.concat(array, this.uint8ToByteArray(datumIndex));
|
||||
array = this.concat(array, this.doubleToByteArray(offset.x));
|
||||
array = this.concat(array, this.doubleToByteArray(offset.y));
|
||||
array = this.concat(array, this.doubleToByteArray(offset.z));
|
||||
return array;
|
||||
}
|
||||
|
||||
appendString(array, string, datumIndex) {
|
||||
array = this.concat(array, this.uint8ToByteArray(datumIndex));
|
||||
array = this.concat(array, this.uint16ToByteArray(string.length));
|
||||
array = this.concat(array, enc.encode(string));
|
||||
return array;
|
||||
}
|
||||
|
||||
padString(string, length) {
|
||||
while (string.length < length)
|
||||
string += " ";
|
||||
return string.substring(0, length);
|
||||
}
|
||||
|
||||
appendTACAN(array, TACAN, datumIndex) {
|
||||
array = this.concat(array, this.uint8ToByteArray(datumIndex));
|
||||
array = this.concat(array, this.uint8ToByteArray(TACAN.isOn));
|
||||
array = this.concat(array, this.uint8ToByteArray(TACAN.channel));
|
||||
array = this.concat(array, enc.encode(TACAN.XY));
|
||||
array = this.concat(array, enc.encode(this.padString(TACAN.callsign, 4)));
|
||||
return array;
|
||||
}
|
||||
|
||||
appendRadio(array, radio, datumIndex) {
|
||||
array = this.concat(array, this.uint8ToByteArray(datumIndex));
|
||||
array = this.concat(array, this.uint32ToByteArray(radio.frequency));
|
||||
array = this.concat(array, this.uint8ToByteArray(radio.callsign));
|
||||
array = this.concat(array, this.uint8ToByteArray(radio.callsignNumber));
|
||||
return array;
|
||||
}
|
||||
|
||||
appendGeneralSettings(array, generalSettings, datumIndex) {
|
||||
array = this.concat(array, this.uint8ToByteArray(datumIndex));
|
||||
array = this.concat(array, this.uint8ToByteArray(generalSettings.prohibitAA));
|
||||
array = this.concat(array, this.uint8ToByteArray(generalSettings.prohibitAfterburner));
|
||||
array = this.concat(array, this.uint8ToByteArray(generalSettings.prohibitAG));
|
||||
array = this.concat(array, this.uint8ToByteArray(generalSettings.prohibitAirWpn));
|
||||
array = this.concat(array, this.uint8ToByteArray(generalSettings.prohibitJettison));
|
||||
return array;
|
||||
}
|
||||
|
||||
appendAmmo(array, ammo, datumIndex) {
|
||||
array = this.concat(array, this.uint8ToByteArray(datumIndex));
|
||||
array = this.concat(array, this.uint16ToByteArray(ammo.length));
|
||||
ammo.forEach((element) => {
|
||||
array = this.concat(array, this.uint16ToByteArray(element.quantity));
|
||||
array = this.concat(array, enc.encode(this.padString(element.name, 33)));
|
||||
array = this.concat(array, this.uint8ToByteArray(element.guidance));
|
||||
array = this.concat(array, this.uint8ToByteArray(element.category));
|
||||
array = this.concat(array, this.uint8ToByteArray(element.missileCategory));
|
||||
})
|
||||
return array;
|
||||
}
|
||||
|
||||
appendContacts(array, contacts, datumIndex) {
|
||||
array = this.concat(array, this.uint8ToByteArray(datumIndex));
|
||||
array = this.concat(array, this.uint16ToByteArray(contacts.length));
|
||||
contacts.forEach((element) => {
|
||||
array = this.concat(array, this.uint32ToByteArray(element.ID));
|
||||
array = this.concat(array, this.uint8ToByteArray(element.detectionMethod));
|
||||
})
|
||||
return array;
|
||||
}
|
||||
|
||||
appendActivePath(array, activePath, datumIndex) {
|
||||
array = this.concat(array, this.uint8ToByteArray(datumIndex));
|
||||
array = this.concat(array, this.uint16ToByteArray(activePath.length));
|
||||
activePath.forEach((element) => {
|
||||
array = this.concat(array, this.doubleToByteArray(element.lat));
|
||||
array = this.concat(array, this.doubleToByteArray(element.lng));
|
||||
array = this.concat(array, this.doubleToByteArray(element.alt));
|
||||
})
|
||||
return array;
|
||||
}
|
||||
|
||||
logs(req, res){
|
||||
var ret = {logs: {}};
|
||||
@ -696,10 +305,6 @@ class DemoDataGenerator {
|
||||
res.send(JSON.stringify(ret));
|
||||
}
|
||||
|
||||
generateRandomUnitsDemoData(unitsNumber)
|
||||
{
|
||||
return {"units": DEMO_UNIT_DATA};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DemoDataGenerator;
|
||||
3373
client/package-lock.json
generated
@ -10,6 +10,7 @@
|
||||
"watch": "watchify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]"
|
||||
},
|
||||
"dependencies": {
|
||||
"@turf/turf": "^6.5.0",
|
||||
"@types/formatcoords": "^1.1.0",
|
||||
"@types/geojson": "^7946.0.10",
|
||||
"@types/leaflet": "^1.9.0",
|
||||
@ -21,6 +22,7 @@
|
||||
"formatcoords": "^1.1.3",
|
||||
"leaflet": "^1.9.3",
|
||||
"leaflet-control-mini-map": "^0.4.0",
|
||||
"leaflet-path-drag": "*",
|
||||
"leaflet.nauticscale": "^1.1.0",
|
||||
"morgan": "~1.9.1",
|
||||
"save": "^2.9.0"
|
||||
|
||||
6
client/public/javascripts/L.Path.Drag.js
Normal file
@ -311,4 +311,8 @@
|
||||
|
||||
[data-object|="unit-aircraft"][data-is-dead] .unit-summary .unit-callsign {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ol-temporary-marker {
|
||||
opacity: 0.5;
|
||||
}
|
||||
@ -38,6 +38,11 @@ body {
|
||||
cursor: none !important;
|
||||
}
|
||||
|
||||
.hidden-cursor * {
|
||||
cursor: none !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
@ -408,7 +413,6 @@ nav.ol-panel> :last-child {
|
||||
|
||||
.ol-panel .ol-group-button-toggle {
|
||||
align-items: center;
|
||||
column-gap: 15px;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
white-space: nowrap;
|
||||
@ -421,7 +425,7 @@ nav.ol-panel> :last-child {
|
||||
border: 0;
|
||||
display: flex;
|
||||
justify-items: left;
|
||||
text-indent: 5px;
|
||||
text-indent: 2px;
|
||||
}
|
||||
|
||||
.ol-panel .ol-group-button-toggle button::before {
|
||||
@ -624,39 +628,39 @@ nav.ol-panel> :last-child {
|
||||
width: 28px;
|
||||
}
|
||||
|
||||
#unit-visibility-control {
|
||||
.ol-navbar-buttons-group {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#unit-visibility-control button {
|
||||
.ol-navbar-buttons-group button {
|
||||
border: none;
|
||||
height: 32px;
|
||||
padding: 0px;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
#unit-visibility-control button svg {
|
||||
.ol-navbar-buttons-group button svg {
|
||||
height: 16px;
|
||||
pointer-events: none;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
#unit-visibility-control button {
|
||||
.ol-navbar-buttons-group button {
|
||||
background-color: white;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
#unit-visibility-control button.off {
|
||||
.ol-navbar-buttons-group button.off {
|
||||
background-color: transparent;
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
#unit-visibility-control button.off svg * {
|
||||
.ol-navbar-buttons-group button.off svg * {
|
||||
fill: white !important;
|
||||
stroke: white !important;
|
||||
}
|
||||
|
||||
#unit-visibility-control button svg * {
|
||||
.ol-navbar-buttons-group button svg * {
|
||||
fill: var(--background-steel) !important;
|
||||
stroke: var(--background-steel) !important;
|
||||
}
|
||||
@ -667,10 +671,9 @@ nav.ol-panel> :last-child {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#atc-navbar-control button {
|
||||
background: #ffffff20;
|
||||
border-radius: var(--border-radius-sm);
|
||||
padding: 4px;
|
||||
#atc-navbar-control button svg {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
#roe-buttons-container button,
|
||||
@ -877,6 +880,33 @@ nav.ol-panel> :last-child {
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.ol-draw-icon {
|
||||
background-image: url("/resources/theme/images/markers/draw.svg");
|
||||
height: 24px;
|
||||
pointer-events: none;
|
||||
width: 24px;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.ol-coalitionarea-handle-icon,
|
||||
.ol-coalitionarea-middle-handle-icon {
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.ol-coalitionarea-handle-icon {
|
||||
background-color: #FFFFFFEE;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.ol-coalitionarea-middle-handle-icon {
|
||||
background-color: #FFFFFFAA;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
dl.ol-data-grid {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
@ -1134,4 +1164,87 @@ input[type=number]::-webkit-outer-spin-button {
|
||||
|
||||
.ol-switch[data-value="undefined"]>.ol-switch-fill::after {
|
||||
transform: translateX(calc((var(--width) - var(--height)) * 0.5));
|
||||
}
|
||||
|
||||
.ol-contexmenu-panel {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.ol-coalition-switch[data-value="false"]>.ol-switch-fill {
|
||||
background-color: var(--primary-blue);
|
||||
}
|
||||
|
||||
.ol-coalition-switch[data-value="true"]>.ol-switch-fill {
|
||||
background-color: var(--primary-red);
|
||||
}
|
||||
|
||||
.ol-coalition-switch[data-value="undefined"]>.ol-switch-fill {
|
||||
background-color: var(--primary-neutral);
|
||||
}
|
||||
|
||||
.ol-context-menu>div:nth-child(2) {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.ol-context-menu>ul {
|
||||
max-height: 200px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ol-context-menu .ol-panel {
|
||||
border-radius: var(--border-radius-sm);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ol-context-menu ul {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.ol-context-menu>div:nth-child(n+3) {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
row-gap: 5px;
|
||||
}
|
||||
|
||||
.ol-context-menu .ol-select-container {
|
||||
align-self: stretch;
|
||||
flex: 0 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.ol-contexmenu-button {
|
||||
border: none;
|
||||
border-radius: 0px;
|
||||
height: 48px;
|
||||
margin-bottom: -10px;
|
||||
margin-top: -10px;
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.ol-contexmenu-button:last-of-type {
|
||||
border-bottom-right-radius: var(--border-radius-sm);
|
||||
border-top-right-radius: var(--border-radius-sm);
|
||||
}
|
||||
|
||||
[data-coalition="blue"].ol-contexmenu-button:hover,
|
||||
[data-coalition="blue"].ol-contexmenu-button.is-open {
|
||||
background-color: var(--primary-blue)
|
||||
}
|
||||
|
||||
[data-coalition="red"].ol-contexmenu-button:hover,
|
||||
[data-coalition="red"].ol-contexmenu-button.is-open {
|
||||
background-color: var(--primary-red)
|
||||
}
|
||||
|
||||
[data-coalition="neutral"].ol-contexmenu-button:hover,
|
||||
[data-coalition="neutral"].ol-contexmenu-button.is-open {
|
||||
background-color: var(--primary-neutral)
|
||||
}
|
||||
@ -35,61 +35,13 @@
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
#coalition-switch[data-value="false"]>.ol-switch-fill {
|
||||
background-color: var(--primary-blue);
|
||||
}
|
||||
|
||||
#coalition-switch[data-value="true"]>.ol-switch-fill {
|
||||
background-color: var(--primary-red);
|
||||
}
|
||||
|
||||
#coalition-switch[data-value="undefined"]>.ol-switch-fill {
|
||||
background-color: var(--primary-neutral);
|
||||
}
|
||||
|
||||
#map-contextmenu>div:nth-child(2) {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
#map-contextmenu>ul {
|
||||
max-height: 200px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#map-contextmenu .ol-panel {
|
||||
border-radius: var(--border-radius-sm);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#map-contextmenu ul {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#map-contextmenu>div:nth-child(n+3) {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
row-gap: 5px;
|
||||
}
|
||||
|
||||
#map-contextmenu .ol-select-container {
|
||||
align-self: stretch;
|
||||
flex: 0 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#aircraft-spawn-menu .ol-select.is-open .ol-select-options {
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
#aircraft-spawn-menu>button,
|
||||
#ground-unit-spawn-menu>button {
|
||||
#ground-unit-spawn-menu>button,
|
||||
#iads-menu>button {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
@ -99,7 +51,7 @@
|
||||
background-size: 48px;
|
||||
}
|
||||
|
||||
#ground-unit-spawn-button {
|
||||
#ground-ol-contexmenu-button {
|
||||
background-image: url("/resources/theme/images/buttons/spawn/ground.svg");
|
||||
background-size: 48px;
|
||||
}
|
||||
@ -114,71 +66,54 @@
|
||||
background-size: 48px;
|
||||
}
|
||||
|
||||
.unit-spawn-button {
|
||||
border: none;
|
||||
border-radius: 0px;
|
||||
height: 48px;
|
||||
margin-bottom: -10px;
|
||||
margin-top: -10px;
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.unit-spawn-button:last-of-type {
|
||||
border-bottom-right-radius: var(--border-radius-sm);
|
||||
border-top-right-radius: var(--border-radius-sm);
|
||||
}
|
||||
|
||||
[data-active-coalition="blue"].unit-spawn-button:hover,
|
||||
[data-active-coalition="blue"].unit-spawn-button.is-open,
|
||||
[data-active-coalition="blue"]#active-coalition-label,
|
||||
[data-active-coalition="blue"].deploy-unit-button,
|
||||
[data-active-coalition="blue"]#spawn-airbase-aircraft-button {
|
||||
[data-coalition="blue"]#active-coalition-label,
|
||||
[data-coalition="blue"].deploy-unit-button,
|
||||
[data-coalition="blue"]#spawn-airbase-aircraft-button,
|
||||
[data-coalition="blue"].create-iads-button {
|
||||
background-color: var(--primary-blue)
|
||||
}
|
||||
|
||||
[data-active-coalition="red"].unit-spawn-button:hover,
|
||||
[data-active-coalition="red"].unit-spawn-button.is-open,
|
||||
[data-active-coalition="red"]#active-coalition-label,
|
||||
[data-active-coalition="red"].deploy-unit-button,
|
||||
[data-active-coalition="red"]#spawn-airbase-aircraft-button {
|
||||
[data-coalition="red"]#active-coalition-label,
|
||||
[data-coalition="red"].deploy-unit-button,
|
||||
[data-coalition="red"]#spawn-airbase-aircraft-button,
|
||||
[data-coalition="red"].create-iads-button {
|
||||
background-color: var(--primary-red)
|
||||
}
|
||||
|
||||
[data-active-coalition="neutral"].unit-spawn-button:hover,
|
||||
[data-active-coalition="neutral"].unit-spawn-button.is-open,
|
||||
[data-active-coalition="neutral"]#active-coalition-label,
|
||||
[data-active-coalition="neutral"].deploy-unit-button,
|
||||
[data-active-coalition="neutral"]#spawn-airbase-aircraft-button {
|
||||
[data-coalition="neutral"]#active-coalition-label,
|
||||
[data-coalition="neutral"].deploy-unit-button,
|
||||
[data-coalition="neutral"]#spawn-airbase-aircraft-button,
|
||||
[data-coalition="neutral"].create-iads-button {
|
||||
background-color: var(--primary-neutral)
|
||||
}
|
||||
|
||||
[data-active-coalition="blue"].deploy-unit-button:disabled {
|
||||
[data-coalition="blue"].deploy-unit-button:disabled {
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--primary-blue);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
[data-active-coalition="red"].deploy-unit-button:disabled {
|
||||
[data-coalition="red"].deploy-unit-button:disabled {
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--primary-red);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
[data-active-coalition="neutral"].deploy-unit-button:disabled {
|
||||
[data-coalition="neutral"].deploy-unit-button:disabled {
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--primary-neutral);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
[data-active-coalition="blue"]#active-coalition-label::after {
|
||||
[data-coalition="blue"]#active-coalition-label::after {
|
||||
content: "Create blue unit";
|
||||
}
|
||||
|
||||
[data-active-coalition="red"]#active-coalition-label::after {
|
||||
[data-coalition="red"]#active-coalition-label::after {
|
||||
content: "Create red unit";
|
||||
}
|
||||
|
||||
[data-active-coalition="neutral"]#active-coalition-label::after {
|
||||
[data-coalition="neutral"]#active-coalition-label::after {
|
||||
content: "Create neutral unit";
|
||||
}
|
||||
|
||||
@ -407,3 +342,65 @@
|
||||
width: 180px;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
/* Coalition area context menu */
|
||||
#coalition-area-contextmenu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: fit-content;
|
||||
position: absolute;
|
||||
row-gap: 5px;
|
||||
width: 250px;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
#coalition-area-switch {
|
||||
margin-right: 10px;
|
||||
height: 25px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
#iads-button {
|
||||
background-image: url("/resources/theme/images/buttons/spawn/sam.svg");
|
||||
background-size: 48px;
|
||||
}
|
||||
|
||||
#cap-button {
|
||||
background-image: url("/resources/theme/images/buttons/spawn/aircraft.svg");
|
||||
background-size: 48px;
|
||||
}
|
||||
|
||||
#coalitionarea-back-button {
|
||||
background-image: url("/resources/theme/images/buttons/other/back.svg");
|
||||
background-size: 48px;
|
||||
}
|
||||
|
||||
#coalitionarea-delete-button {
|
||||
background-image: url("/resources/theme/images/buttons/other/delete.svg");
|
||||
background-size: 48px;
|
||||
}
|
||||
|
||||
#coalition-area-contextmenu .ol-checkbox {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
#iads-menu .ol-select-options>* {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
#iads-menu .ol-select-options>*:first-child {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
#iads-menu .ol-select-options>*:last-child {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
#iads-menu .ol-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#iads-menu {
|
||||
row-gap: 10px;
|
||||
}
|
||||
|
||||
41
client/public/themes/olympus/images/buttons/other/back.svg
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 32 32"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="back.svg"
|
||||
width="32"
|
||||
height="32"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="26.15625"
|
||||
inkscape:cx="20.587814"
|
||||
inkscape:cy="20.109916"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
<path
|
||||
d="M 24.337929,21.064066 H 7.766909 c -0.572865,0 -1.035689,0.462823 -1.035689,1.035689 0,0.572864 0.462824,1.035688 1.035689,1.035688 h 16.57102 c 0.572865,0 1.035689,-0.462824 1.035689,-1.035688 0,-0.572866 -0.462824,-1.035689 -1.035689,-1.035689 z M 20.024324,15.15093 c -0.404566,-0.404566 -1.061581,-0.404566 -1.466147,0 l -1.336685,1.339923 V 9.6714892 c 0,-0.572865 -0.462823,-1.035688 -1.035689,-1.035688 -0.572866,0 -1.035688,0.462823 -1.035688,1.035688 V 16.490853 L 13.810192,15.15093 c -0.404566,-0.404566 -1.061581,-0.404566 -1.466147,0 -0.404565,0.404566 -0.404565,1.061581 0,1.466147 l 3.107066,3.107066 c 0.404565,0.404566 1.061582,0.404566 1.466147,0 l 3.107066,-3.107066 c 0.404567,-0.404566 0.404567,-1.061581 0,-1.466147 z"
|
||||
id="path2"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke-width:0.0323653"
|
||||
sodipodi:nodetypes="ssssssssccssscssccccs" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
40
client/public/themes/olympus/images/buttons/other/delete.svg
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 32 32"
|
||||
version="1.1"
|
||||
id="svg1940"
|
||||
sodipodi:docname="delete.svg"
|
||||
width="32"
|
||||
height="32"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1944" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1942"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="13.078125"
|
||||
inkscape:cx="27.794504"
|
||||
inkscape:cy="19.192354"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1940" />
|
||||
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
<path
|
||||
d="m 14.670468,9.3574565 -0.569725,0.8515865 h 4.347895 L 17.878913,9.3574565 C 17.833935,9.2914885 17.758972,9.2495089 17.678011,9.2495089 h -2.809639 c -0.08096,0 -0.155924,0.038981 -0.200903,0.1079476 z m 4.407864,-0.7976134 1.100466,1.6491999 h 0.413801 1.439301 0.239884 c 0.398807,0 0.719652,0.320845 0.719652,0.719652 0,0.398807 -0.320845,0.719651 -0.719652,0.719651 H 22.0319 v 9.115583 c 0,1.325358 -1.073479,2.398837 -2.398837,2.398837 h -6.716745 c -1.325357,0 -2.398838,-1.073479 -2.398838,-2.398837 v -9.115583 h -0.239884 c -0.3988062,0 -0.7196507,-0.320844 -0.7196507,-0.719651 0,-0.398807 0.3208445,-0.719652 0.7196507,-0.719652 h 0.239884 1.439303 0.413799 l 1.100467,-1.6521984 c 0.311849,-0.4647748 0.836595,-0.7466382 1.397323,-0.7466382 h 2.809639 c 0.560728,0 1.085474,0.2818634 1.397322,0.7466382 z m -7.121549,3.0885029 v 9.115583 c 0,0.530743 0.428792,0.959535 0.959535,0.959535 h 6.716745 c 0.530743,0 0.959536,-0.428792 0.959536,-0.959535 v -9.115583 z m 2.398837,1.919071 v 6.236977 c 0,0.263872 -0.215895,0.479767 -0.479767,0.479767 -0.263872,0 -0.479767,-0.215895 -0.479767,-0.479767 v -6.236977 c 0,-0.263873 0.215895,-0.479768 0.479767,-0.479768 0.263872,0 0.479767,0.215895 0.479767,0.479768 z m 2.398838,0 v 6.236977 c 0,0.263872 -0.215896,0.479767 -0.479767,0.479767 -0.263873,0 -0.479768,-0.215895 -0.479768,-0.479767 v -6.236977 c 0,-0.263873 0.215895,-0.479768 0.479768,-0.479768 0.263871,0 0.479767,0.215895 0.479767,0.479768 z m 2.398838,0 v 6.236977 c 0,0.263872 -0.215896,0.479767 -0.479768,0.479767 -0.263871,0 -0.479767,-0.215895 -0.479767,-0.479767 v -6.236977 c 0,-0.263873 0.215896,-0.479768 0.479767,-0.479768 0.263872,0 0.479768,0.215895 0.479768,0.479768 z"
|
||||
id="path1938"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.0299854;stroke-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
91
client/public/themes/olympus/images/buttons/spawn/sam.svg
Normal file
|
After Width: | Height: | Size: 10 KiB |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M96 151.4V360.6c9.7 5.6 17.8 13.7 23.4 23.4H328.6c0-.1 .1-.2 .1-.3l-4.5-7.9-32-56 0 0c-1.4 .1-2.8 .1-4.2 .1c-35.3 0-64-28.7-64-64s28.7-64 64-64c1.4 0 2.8 0 4.2 .1l0 0 32-56 4.5-7.9-.1-.3H119.4c-5.6 9.7-13.7 17.8-23.4 23.4zM384.3 352c35.2 .2 63.7 28.7 63.7 64c0 35.3-28.7 64-64 64c-23.7 0-44.4-12.9-55.4-32H119.4c-11.1 19.1-31.7 32-55.4 32c-35.3 0-64-28.7-64-64c0-23.7 12.9-44.4 32-55.4V151.4C12.9 140.4 0 119.7 0 96C0 60.7 28.7 32 64 32c23.7 0 44.4 12.9 55.4 32H328.6c11.1-19.1 31.7-32 55.4-32c35.3 0 64 28.7 64 64c0 35.3-28.5 63.8-63.7 64l-4.5 7.9-32 56-2.3 4c4.2 8.5 6.5 18 6.5 28.1s-2.3 19.6-6.5 28.1l2.3 4 32 56 4.5 7.9z"/></svg>
|
||||
|
After Width: | Height: | Size: 872 B |
44
client/public/themes/olympus/images/buttons/tools/ground.svg
Normal file
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 512 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="ground.svg"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.1559539"
|
||||
inkscape:cx="432.97576"
|
||||
inkscape:cy="301.91516"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
<path
|
||||
d="m 130.61192,122.94993 c 4.83398,-2.4171 10.52734,-2.4171 15.36132,0 l 85.9375,42.97085 c 8.48633,4.24336 11.92383,14.55637 7.68066,23.04312 -3.00781,6.01593 -9.07714,9.5073 -15.36132,9.5073 v 42.97085 c 0,9.50729 -7.68066,17.18833 -17.1875,17.18833 h -2.63184 l 17.1875,103.13004 266.68729,0 c 9.50683,0 17.1875,7.68105 17.1875,17.18835 0,9.5073 -7.68067,17.18833 -17.1875,17.18833 l -280.97441,0 H 206.72031 69.86485 69.27404 17.98008 c -9.50683,0 -17.1875,-7.68103 -17.1875,-17.18833 0,-9.5073 7.68067,-17.18835 17.1875,-17.18835 h 37.00684 l 17.1875,-103.13004 h -2.63184 c -9.50684,0 -17.1875,-7.68104 -17.1875,-17.18833 V 198.4712 c -6.28418,0 -12.35351,-3.49137 -15.36132,-9.5073 -4.24317,-8.48675 -0.80567,-18.79976 7.68066,-23.04312 z m 39.10155,238.81049 -31.42089,-26.21222 -31.4209,26.21222 z m -62.68066,-103.13004 -2.52441,15.20096 33.78418,28.19961 33.78418,-28.19961 -2.52441,-15.20096 z m -7.46582,44.68969 -6.01561,35.98808 24.59961,-20.51857 z m 58.86718,15.46951 24.59961,20.46487 -6.01561,-35.98809 z M 95.32383,189.87702 c -4.72656,0 -8.59374,3.86738 -8.59374,8.59418 0,4.72679 3.86718,8.59417 8.59374,8.59417 h 85.9375 c 4.72656,0 8.59374,-3.86738 8.59374,-8.59417 0,-4.7268 -3.86718,-8.59418 -8.59374,-8.59418 z"
|
||||
id="path2"
|
||||
style="stroke-width:0.537122"
|
||||
sodipodi:nodetypes="cccscssccsssccccsssccsscsccccccccccccccccccccsssssss" />
|
||||
<path
|
||||
d="m 398.88439,290.60444 -56.18308,-59.35032 c -4.82498,-5.06298 -11.50756,-7.93072 -18.4952,-7.84559 l -20.1384,0.17717 c -4.88076,0.0647 -7.83178,5.39609 -5.3158,9.5508 l 35.56198,58.63664 -43.41565,0.78856 -16.07612,-19.15941 c -2.39827,-2.87404 -5.9964,-4.54245 -9.73595,-4.48875 l -14.13363,0.13667 c -4.09068,0.0573 -7.05613,3.89607 -6.017,7.85435 l 11.10611,42.68377 c 0.92991,3.55548 3.36429,6.5345 6.64574,8.13577 l 48.58659,23.70886 c 1.76422,0.86089 3.69975,1.28114 5.63108,1.26246 l 119.77611,-1.14062 c 20.41896,-0.21499 40.00846,-8.13034 54.85055,-22.163 7.69088,-7.25597 5.74152,-20.00247 -3.78528,-24.65128 l -20.25324,-9.883 c -7.12743,-3.47798 -15.01076,-5.22788 -22.94599,-5.08091 z"
|
||||
id="path1903"
|
||||
sodipodi:nodetypes="ccccccccccccsscccsscc"
|
||||
style="stroke-width:0.392612" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M362.7 19.3L314.3 67.7 444.3 197.7l48.4-48.4c25-25 25-65.5 0-90.5L453.3 19.3c-25-25-65.5-25-90.5 0zm-71 71L58.6 323.5c-10.4 10.4-18 23.3-22.2 37.4L1 481.2C-1.5 489.7 .8 498.8 7 505s15.3 8.5 23.7 6.1l120.3-35.4c14.1-4.2 27-11.8 37.4-22.2L421.7 220.3 291.7 90.3z"/></svg>
|
||||
|
After Width: | Height: | Size: 508 B |
44
client/public/themes/olympus/images/buttons/tools/tower.svg
Normal file
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 512 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="tower.svg"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.40869141"
|
||||
inkscape:cx="532.18638"
|
||||
inkscape:cy="298.51374"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
<path
|
||||
d="m 130.61192,122.94993 c 4.83398,-2.4171 10.52734,-2.4171 15.36132,0 l 85.9375,42.97085 c 8.48633,4.24336 11.92383,14.55637 7.68066,23.04312 -3.00781,6.01593 -9.07714,9.5073 -15.36132,9.5073 v 42.97085 c 0,9.50729 -7.68066,17.18833 -17.1875,17.18833 h -2.63184 l 17.1875,103.13004 266.68729,0 c 9.50683,0 17.1875,7.68105 17.1875,17.18835 0,9.5073 -7.68067,17.18833 -17.1875,17.18833 l -280.97441,0 H 206.72031 69.86485 69.27404 17.98008 c -9.50683,0 -17.1875,-7.68103 -17.1875,-17.18833 0,-9.5073 7.68067,-17.18835 17.1875,-17.18835 h 37.00684 l 17.1875,-103.13004 h -2.63184 c -9.50684,0 -17.1875,-7.68104 -17.1875,-17.18833 V 198.4712 c -6.28418,0 -12.35351,-3.49137 -15.36132,-9.5073 -4.24317,-8.48675 -0.80567,-18.79976 7.68066,-23.04312 z m 39.10155,238.81049 -31.42089,-26.21222 -31.4209,26.21222 z m -62.68066,-103.13004 -2.52441,15.20096 33.78418,28.19961 33.78418,-28.19961 -2.52441,-15.20096 z m -7.46582,44.68969 -6.01561,35.98808 24.59961,-20.51857 z m 58.86718,15.46951 24.59961,20.46487 -6.01561,-35.98809 z M 95.32383,189.87702 c -4.72656,0 -8.59374,3.86738 -8.59374,8.59418 0,4.72679 3.86718,8.59417 8.59374,8.59417 h 85.9375 c 4.72656,0 8.59374,-3.86738 8.59374,-8.59417 0,-4.7268 -3.86718,-8.59418 -8.59374,-8.59418 z"
|
||||
id="path2"
|
||||
style="stroke-width:0.537122"
|
||||
sodipodi:nodetypes="cccscssccsssccccsssccsscsccccccccccccccccccccsssssss" />
|
||||
<path
|
||||
d="m 387.14987,87.347348 -58.62532,-21.98825 c -5.0233,-1.86494 -10.58805,-1.59423 -15.37072,0.81215 l -13.80658,6.88825 c -3.33884,1.68446 -3.57948,6.34681 -0.45119,8.36215 l 44.18707,28.425312 -29.6285,15.13009 -17.50638,-7.79064 c -2.61694,-1.17311 -5.65498,-1.11295 -8.21176,0.18048 l -9.68566,4.84283 c -2.79741,1.41374 -3.5494,5.05338 -1.50398,7.42968 l 21.98825,25.65798 c 1.83486,2.13566 4.51196,3.36892 7.30937,3.36892 h 41.41973 c 1.50399,0 2.97789,-0.36095 4.3014,-1.02271 l 82.08748,-41.0287 c 13.98706,-7.00856 24.81575,-19.040442 30.32033,-33.689252 2.85757,-7.58008 -2.76733,-15.7016 -10.88885,-15.7016 h -17.26574 c -6.0761,0 -12.09204,1.44382 -17.50638,4.21116 z"
|
||||
id="path1903"
|
||||
sodipodi:nodetypes="ccccccccccccsscccsscc"
|
||||
style="stroke-width:0.300797" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
38
client/public/themes/olympus/images/markers/draw.svg
Normal file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 512 512"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="draw.svg"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.81738281"
|
||||
inkscape:cx="159.65591"
|
||||
inkscape:cy="402.50418"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
|
||||
<path
|
||||
d="m 352.44544,42.011059 -43.76337,43.76337 117.54624,117.546241 43.76337,-43.76337 c 22.60505,-22.60504 22.60505,-59.22522 0,-81.830267 L 434.36613,42.011059 c -22.60505,-22.605047 -59.22523,-22.605047 -81.83027,0 z M 288.2471,106.20939 77.477646,317.06927 c -9.4037,9.4037 -16.275634,21.0679 -20.073282,33.81715 L 25.395618,459.6619 c -2.260505,7.68572 -0.18084,15.91396 5.425211,21.52001 5.606052,5.60605 13.834289,7.68572 21.429585,5.51563 L 161.0259,454.68879 c 12.74925,-3.79764 24.41345,-10.66958 33.81715,-20.07328 l 210.9503,-210.85987 z"
|
||||
id="path2"
|
||||
style="stroke:#ffffff;stroke-width:45.7526;stroke-dasharray:none;stroke-opacity:1;fill:#247be2;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
2
client/src/@types/server.d.ts
vendored
@ -1,5 +1,5 @@
|
||||
interface UnitsData {
|
||||
units: {[key: string]: UnitData},
|
||||
units: string,
|
||||
sessionHash: string
|
||||
}
|
||||
|
||||
|
||||
109
client/src/@types/unit.d.ts
vendored
@ -1,60 +1,23 @@
|
||||
interface UpdateData {
|
||||
[key: string]: any
|
||||
import { LatLng } from "leaflet"
|
||||
|
||||
interface UnitIconOptions {
|
||||
showState: boolean,
|
||||
showVvi: boolean,
|
||||
showHotgroup: boolean,
|
||||
showUnitIcon: boolean,
|
||||
showShortLabel: boolean,
|
||||
showFuel: boolean,
|
||||
showAmmo: boolean,
|
||||
showSummary: boolean,
|
||||
rotateToHeading: boolean
|
||||
}
|
||||
|
||||
interface BaseData {
|
||||
controlled: boolean;
|
||||
name: string;
|
||||
unitName: string;
|
||||
groupName: string;
|
||||
alive: boolean;
|
||||
category: string;
|
||||
}
|
||||
|
||||
interface FlightData {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
altitude: number;
|
||||
heading: number;
|
||||
speed: number;
|
||||
}
|
||||
|
||||
interface MissionData {
|
||||
fuel: number;
|
||||
flags: any;
|
||||
ammo: any;
|
||||
contacts: any;
|
||||
hasTask: boolean;
|
||||
coalition: string;
|
||||
}
|
||||
|
||||
interface FormationData {
|
||||
leaderID: number;
|
||||
}
|
||||
|
||||
interface TaskData {
|
||||
currentState: string;
|
||||
currentTask: string;
|
||||
activePath: any;
|
||||
desiredSpeed: number;
|
||||
desiredSpeedType: string;
|
||||
desiredAltitude: number;
|
||||
desiredAltitudeType: string;
|
||||
targetLocation: any;
|
||||
isTanker: boolean;
|
||||
isAWACS: boolean;
|
||||
onOff: boolean;
|
||||
followRoads: boolean;
|
||||
targetID: number;
|
||||
}
|
||||
|
||||
interface OptionsData {
|
||||
ROE: string;
|
||||
reactionToThreat: string;
|
||||
emissionsCountermeasures: string;
|
||||
TACAN: TACAN;
|
||||
radio: Radio;
|
||||
generalSettings: GeneralSettings;
|
||||
interface GeneralSettings {
|
||||
prohibitJettison: boolean;
|
||||
prohibitAA: boolean;
|
||||
prohibitAG: boolean;
|
||||
prohibitAfterburner: boolean;
|
||||
prohibitAirWpn: boolean;
|
||||
}
|
||||
|
||||
interface TACAN {
|
||||
@ -70,31 +33,21 @@ interface Radio {
|
||||
callsignNumber: number;
|
||||
}
|
||||
|
||||
interface GeneralSettings {
|
||||
prohibitJettison: boolean;
|
||||
prohibitAA: boolean;
|
||||
prohibitAG: boolean;
|
||||
prohibitAfterburner: boolean;
|
||||
prohibitAirWpn: boolean;
|
||||
interface Ammo {
|
||||
quantity: number,
|
||||
name: string,
|
||||
guidance: number,
|
||||
category: number,
|
||||
missileCategory: number
|
||||
}
|
||||
|
||||
interface UnitIconOptions {
|
||||
showState: boolean,
|
||||
showVvi: boolean,
|
||||
showHotgroup: boolean,
|
||||
showUnitIcon: boolean,
|
||||
showShortLabel: boolean,
|
||||
showFuel: boolean,
|
||||
showAmmo: boolean,
|
||||
showSummary: boolean,
|
||||
rotateToHeading: boolean
|
||||
interface Contact {
|
||||
ID: number,
|
||||
detectionMethod: number
|
||||
}
|
||||
|
||||
interface UnitData {
|
||||
baseData: BaseData;
|
||||
flightData: FlightData;
|
||||
missionData: MissionData;
|
||||
formationData: FormationData;
|
||||
taskData: TaskData;
|
||||
optionsData: OptionsData;
|
||||
interface Offset {
|
||||
x: number,
|
||||
y: number,
|
||||
z: number
|
||||
}
|
||||
1
client/src/@types/unitdatabase.d.ts
vendored
@ -15,6 +15,7 @@ interface LoadoutBlueprint {
|
||||
interface UnitBlueprint {
|
||||
name: string;
|
||||
era?: string[];
|
||||
type?: string;
|
||||
label: string;
|
||||
shortLabel: string;
|
||||
range?: string;
|
||||
|
||||
@ -115,11 +115,11 @@ export abstract class ATCBoard {
|
||||
|
||||
addFlight( unit:Unit ) {
|
||||
|
||||
const baseData = unit.getBaseData();
|
||||
const baseData = unit.getData();
|
||||
|
||||
const unitCanBeAdded = () => {
|
||||
|
||||
if ( baseData.category !== "Aircraft" ) {
|
||||
if ( unit.getCategory() !== "Aircraft" ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -345,7 +345,7 @@ export abstract class ATCBoard {
|
||||
const results = Object.keys( units ).reduce( ( acc:Unit[], unitId:any ) => {
|
||||
|
||||
const unit = units[ unitId ];
|
||||
const baseData = unit.getBaseData();
|
||||
const baseData = unit.getData();
|
||||
|
||||
if ( !unitIdsBeingMonitored.includes( parseInt( unitId ) ) && baseData.unitName.toLowerCase().indexOf( searchString ) > -1 ) {
|
||||
acc.push( unit );
|
||||
@ -359,7 +359,7 @@ export abstract class ATCBoard {
|
||||
|
||||
results.forEach( unit => {
|
||||
|
||||
const baseData = unit.getBaseData();
|
||||
const baseData = unit.getData();
|
||||
|
||||
const a = document.createElement( "a" );
|
||||
a.innerText = baseData.unitName;
|
||||
|
||||
@ -34,13 +34,13 @@ export class ATCBoardTower extends ATCBoard {
|
||||
return;
|
||||
}
|
||||
|
||||
const flightData:FlightData = {
|
||||
const flightData = {
|
||||
latitude: -1,
|
||||
longitude: -1,
|
||||
altitude: -1,
|
||||
heading: -1,
|
||||
speed: -1,
|
||||
...( selectableUnits.hasOwnProperty( flight.unitId ) ? selectableUnits[flight.unitId].getFlightData() : {} )
|
||||
...( selectableUnits.hasOwnProperty( flight.unitId ) ? selectableUnits[flight.unitId].getData() : {} )
|
||||
};
|
||||
|
||||
if ( !strip ) {
|
||||
|
||||
@ -12,8 +12,8 @@ export class UnitDataTable extends Panel {
|
||||
var units = getUnitsManager().getUnits();
|
||||
|
||||
const unitsArray = Object.values(units).sort((a: Unit, b: Unit) => {
|
||||
const aVal = a.getBaseData().unitName?.toLowerCase();
|
||||
const bVal = b.getBaseData().unitName?.toLowerCase();
|
||||
const aVal = a.getUnitName()?.toLowerCase();
|
||||
const bVal = b.getUnitName()?.toLowerCase();
|
||||
|
||||
if (aVal > bVal) {
|
||||
return 1;
|
||||
@ -48,7 +48,7 @@ export class UnitDataTable extends Panel {
|
||||
|
||||
for (const unit of unitsArray) {
|
||||
|
||||
const dataset = [unit.getBaseData().unitName, unit.getBaseData().name, unit.getBaseData().category, (unit.getBaseData().controlled) ? "AI" : "Human"];
|
||||
const dataset = [unit.getUnitName(), unit.getName(), unit.getCategory(), (unit.getControlled()) ? "AI" : "Human"];
|
||||
|
||||
addRow(el, dataset);
|
||||
}
|
||||
|
||||
@ -1,12 +1,31 @@
|
||||
import { LatLng, LatLngBounds, TileLayer, tileLayer } from "leaflet";
|
||||
|
||||
export const ROEs: string[] = ["Hold", "Return", "Designated", "Free"];
|
||||
export const reactionsToThreat: string[] = ["None", "Manoeuvre", "Passive", "Evade"];
|
||||
export const emissionsCountermeasures: string[] = ["Silent", "Attack", "Defend", "Free"];
|
||||
export const states: string[] = ["none", "idle", "reach-destination", "attack", "follow", "land", "refuel", "AWACS", "tanker", "bomb-point", "carpet-bomb", "bomb-building", "fire-at-area"];
|
||||
export const ROEs: string[] = ["free", "designated", "return", "hold"];
|
||||
export const reactionsToThreat: string[] = ["none", "manoeuvre", "passive", "evade"];
|
||||
export const emissionsCountermeasures: string[] = ["silent", "attack", "defend", "free"];
|
||||
|
||||
export const ROEDescriptions: string[] = ["Hold (Never fire)", "Return (Only fire if fired upon)", "Designated (Attack the designated target only)", "Free (Attack anyone)"];
|
||||
export const reactionsToThreatDescriptions: string[] = ["None (No reaction)", "Manoeuvre (no countermeasures)", "Passive (Countermeasures only, no manoeuvre)", "Evade (Countermeasures and manoeuvers)"];
|
||||
export const emissionsCountermeasuresDescriptions: string[] = ["Silent (Radar OFF, no ECM)", "Attack (Radar only for targeting, ECM only if locked)", "Defend (Radar for searching, ECM if locked)", "Always on (Radar and ECM always on)"];
|
||||
export const ROEDescriptions: string[] = [
|
||||
"Free (Attack anyone)",
|
||||
"Designated (Attack the designated target only)",
|
||||
"",
|
||||
"Return (Only fire if fired upon)",
|
||||
"Hold (Never fire)"
|
||||
];
|
||||
|
||||
export const reactionsToThreatDescriptions: string[] = [
|
||||
"None (No reaction)",
|
||||
"Manoeuvre (no countermeasures)",
|
||||
"Passive (Countermeasures only, no manoeuvre)",
|
||||
"Evade (Countermeasures and manoeuvers)"
|
||||
];
|
||||
|
||||
export const emissionsCountermeasuresDescriptions: string[] = [
|
||||
"Silent (Radar OFF, no ECM)",
|
||||
"Attack (Radar only for targeting, ECM only if locked)",
|
||||
"Defend (Radar for searching, ECM if locked)",
|
||||
"Always on (Radar and ECM always on)"
|
||||
];
|
||||
|
||||
export const minSpeedValues: { [key: string]: number } = { Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0 };
|
||||
export const maxSpeedValues: { [key: string]: number } = { Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60 };
|
||||
@ -99,4 +118,59 @@ export const layers = {
|
||||
maxZoom: 20,
|
||||
attribution: '<a href="https://github.com/cyclosm/cyclosm-cartocss-style/releases" title="CyclOSM - Open Bicycle render">CyclOSM</a> | Map data: © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Map constants */
|
||||
export const IDLE = "Idle";
|
||||
export const MOVE_UNIT = "Move unit";
|
||||
export const BOMBING = "Bombing";
|
||||
export const CARPET_BOMBING = "Carpet bombing";
|
||||
export const FIRE_AT_AREA = "Fire at area";
|
||||
export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area";
|
||||
export const COALITIONAREA_INTERACT = "Interact with Coalition Areas"
|
||||
export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"];
|
||||
export const visibilityControlsTootlips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"];
|
||||
|
||||
export const IADSRoles: {[key: string]: number}= {"AAA": 0.8, "MANPADS": 0.3, "SAM Sites": 0.1, "Radar": 0.05};
|
||||
|
||||
export enum DataIndexes {
|
||||
startOfData = 0,
|
||||
category,
|
||||
alive,
|
||||
human,
|
||||
controlled,
|
||||
coalition,
|
||||
country,
|
||||
name,
|
||||
unitName,
|
||||
groupName,
|
||||
state,
|
||||
task,
|
||||
hasTask,
|
||||
position,
|
||||
speed,
|
||||
heading,
|
||||
isTanker,
|
||||
isAWACS,
|
||||
onOff,
|
||||
followRoads,
|
||||
fuel,
|
||||
desiredSpeed,
|
||||
desiredSpeedType,
|
||||
desiredAltitude,
|
||||
desiredAltitudeType,
|
||||
leaderID,
|
||||
formationOffset,
|
||||
targetID,
|
||||
targetPosition,
|
||||
ROE,
|
||||
reactionToThreat,
|
||||
emissionsCountermeasures,
|
||||
TACAN,
|
||||
radio,
|
||||
generalSettings,
|
||||
ammo,
|
||||
contacts,
|
||||
activePath,
|
||||
endOfData = 255
|
||||
};
|
||||
112
client/src/controls/coalitionareacontextmenu.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import { getMap, getUnitsManager } from "..";
|
||||
import { IADSRoles } from "../constants/constants";
|
||||
import { CoalitionArea } from "../map/coalitionarea";
|
||||
import { ContextMenu } from "./contextmenu";
|
||||
import { Dropdown } from "./dropdown";
|
||||
import { Slider } from "./slider";
|
||||
import { Switch } from "./switch";
|
||||
|
||||
export class CoalitionAreaContextMenu extends ContextMenu {
|
||||
#coalitionSwitch: Switch;
|
||||
#coalitionArea: CoalitionArea | null = null;
|
||||
#iadsDensitySlider: Slider;
|
||||
#iadsRoleDropdown: Dropdown;
|
||||
|
||||
//#iadsPeriodDropdown: Dropdown;
|
||||
|
||||
constructor(id: string) {
|
||||
super(id);
|
||||
|
||||
this.#coalitionSwitch = new Switch("coalition-area-switch", (value: boolean) => this.#onSwitchClick(value));
|
||||
this.#coalitionSwitch.setValue(false);
|
||||
this.#iadsRoleDropdown = new Dropdown("iads-units-role-options", () => { });
|
||||
//this.#iadsPeriodDropdown = new Dropdown("iads-period-options", () => {});
|
||||
this.#iadsDensitySlider = new Slider("iads-density-slider", 5, 100, "%", (value: number) => { });
|
||||
this.#iadsDensitySlider.setIncrement(5);
|
||||
this.#iadsDensitySlider.setValue(50);
|
||||
this.#iadsDensitySlider.setActive(true);
|
||||
|
||||
document.addEventListener("coalitionAreaContextMenuShow", (e: any) => {
|
||||
if (this.getVisibleSubMenu() !== e.detail.type)
|
||||
this.showSubMenu(e.detail.type);
|
||||
else
|
||||
this.hideSubMenus();
|
||||
});
|
||||
|
||||
document.addEventListener("coalitionAreaDelete", (e: any) => {
|
||||
if (this.#coalitionArea)
|
||||
getMap().deleteCoalitionArea(this.#coalitionArea);
|
||||
getMap().hideCoalitionAreaContextMenu();
|
||||
});
|
||||
|
||||
document.addEventListener("contextMenuCreateIads", (e: any) => {
|
||||
const values: { [key: string]: boolean } = {};
|
||||
const element = this.#iadsRoleDropdown.getOptionElements();
|
||||
for (let idx = 0; idx < element.length; idx++) {
|
||||
const option = element.item(idx) as HTMLElement;
|
||||
const key = option.querySelector("span")?.innerText;
|
||||
const value = option.querySelector("input")?.checked;
|
||||
if (key !== undefined && value !== undefined)
|
||||
values[key] = value;
|
||||
}
|
||||
|
||||
const area = this.getCoalitionArea();
|
||||
if (area)
|
||||
getUnitsManager().createIADS(area, values, this.#iadsDensitySlider.getValue());
|
||||
})
|
||||
|
||||
/* Create the checkboxes to select the unit roles */
|
||||
this.#iadsRoleDropdown.setOptionsElements(Object.keys(IADSRoles).map((unitRole: string) => {
|
||||
var div = document.createElement("div");
|
||||
div.classList.add("ol-checkbox");
|
||||
var label = document.createElement("label");
|
||||
label.title = `Add ${unitRole}s to the IADS`;
|
||||
var input = document.createElement("input");
|
||||
input.type = "checkbox";
|
||||
input.checked = true;
|
||||
var span = document.createElement("span");
|
||||
span.innerText = unitRole;
|
||||
label.appendChild(input);
|
||||
label.appendChild(span);
|
||||
div.appendChild(label);
|
||||
return div as HTMLElement;
|
||||
}));
|
||||
|
||||
this.hide();
|
||||
}
|
||||
|
||||
showSubMenu(type: string) {
|
||||
this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", type !== "iads");
|
||||
this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", type === "iads");
|
||||
this.clip();
|
||||
|
||||
this.setVisibleSubMenu(type);
|
||||
}
|
||||
|
||||
hideSubMenus() {
|
||||
this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", true);
|
||||
this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", false);
|
||||
this.clip();
|
||||
|
||||
this.setVisibleSubMenu(null);
|
||||
}
|
||||
|
||||
getCoalitionArea() {
|
||||
return this.#coalitionArea;
|
||||
}
|
||||
|
||||
setCoalitionArea(coalitionArea: CoalitionArea) {
|
||||
this.#coalitionArea = coalitionArea;
|
||||
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => {
|
||||
element.setAttribute("data-coalition", this.getCoalitionArea()?.getCoalition())
|
||||
});
|
||||
this.#coalitionSwitch.setValue(this.getCoalitionArea()?.getCoalition() === "red");
|
||||
}
|
||||
|
||||
#onSwitchClick(value: boolean) {
|
||||
this.getCoalitionArea()?.setCoalition(value ? "red" : "blue");
|
||||
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => {
|
||||
element.setAttribute("data-coalition", this.getCoalitionArea()?.getCoalition())
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@ export class ContextMenu {
|
||||
#latlng: LatLng = new LatLng(0, 0);
|
||||
#x: number = 0;
|
||||
#y: number = 0;
|
||||
#visibleSubMenu: string | null = null;
|
||||
|
||||
constructor(id: string) {
|
||||
this.#container = document.getElementById(id);
|
||||
@ -52,4 +53,12 @@ export class ContextMenu {
|
||||
this.#container.style.top = window.innerHeight - this.#container.offsetHeight - 10 + "px";
|
||||
}
|
||||
}
|
||||
|
||||
setVisibleSubMenu(menu: string | null) {
|
||||
this.#visibleSubMenu = menu;
|
||||
}
|
||||
|
||||
getVisibleSubMenu() {
|
||||
return this.#visibleSubMenu;
|
||||
}
|
||||
}
|
||||
@ -50,6 +50,15 @@ export class Dropdown {
|
||||
}));
|
||||
}
|
||||
|
||||
setOptionsElements(optionsElements: HTMLElement[]) {
|
||||
this.#optionsList = [];
|
||||
this.#options.replaceChildren(...optionsElements);
|
||||
}
|
||||
|
||||
getOptionElements() {
|
||||
return this.#options.children;
|
||||
}
|
||||
|
||||
selectText(text: string) {
|
||||
const index = [].slice.call(this.#options.children).findIndex((opt: Element) => opt.querySelector("button")?.innerText === text);
|
||||
if (index > -1) {
|
||||
|
||||
@ -11,12 +11,13 @@ import { ftToM } from "../other/utils";
|
||||
|
||||
export interface SpawnOptions {
|
||||
role: string;
|
||||
type: string;
|
||||
name: string;
|
||||
latlng: LatLng;
|
||||
coalition: string;
|
||||
loadout: string | null;
|
||||
airbaseName: string | null;
|
||||
altitude: number | null;
|
||||
loadout?: string | null;
|
||||
airbaseName?: string | null;
|
||||
altitude?: number | null;
|
||||
immediate?: boolean;
|
||||
}
|
||||
|
||||
export class MapContextMenu extends ContextMenu {
|
||||
@ -27,12 +28,12 @@ export class MapContextMenu extends ContextMenu {
|
||||
#aircrafSpawnAltitudeSlider: Slider;
|
||||
#groundUnitRoleDropdown: Dropdown;
|
||||
#groundUnitTypeDropdown: Dropdown;
|
||||
#spawnOptions: SpawnOptions = { role: "", type: "", latlng: new LatLng(0, 0), loadout: null, coalition: "blue", airbaseName: null, altitude: ftToM(20000) };
|
||||
|
||||
#spawnOptions: SpawnOptions = { role: "", name: "", latlng: new LatLng(0, 0), loadout: null, coalition: "blue", airbaseName: null, altitude: ftToM(20000) };
|
||||
|
||||
constructor(id: string) {
|
||||
super(id);
|
||||
|
||||
this.#coalitionSwitch = new Switch("coalition-switch", this.#onSwitchClick);
|
||||
this.#coalitionSwitch = new Switch("coalition-switch", (value: boolean) => this.#onSwitchClick(value));
|
||||
this.#coalitionSwitch.setValue(false);
|
||||
this.#coalitionSwitch.getContainer()?.addEventListener("contextmenu", (e) => this.#onSwitchRightClick(e));
|
||||
this.#aircraftRoleDropdown = new Dropdown("aircraft-role-options", (role: string) => this.#setAircraftRole(role));
|
||||
@ -45,15 +46,18 @@ export class MapContextMenu extends ContextMenu {
|
||||
this.#groundUnitRoleDropdown = new Dropdown("ground-unit-role-options", (role: string) => this.#setGroundUnitRole(role));
|
||||
this.#groundUnitTypeDropdown = new Dropdown("ground-unit-type-options", (type: string) => this.#setGroundUnitType(type));
|
||||
|
||||
document.addEventListener("contextMenuShow", (e: any) => {
|
||||
this.showSubMenu(e.detail.type);
|
||||
document.addEventListener("mapContextMenuShow", (e: any) => {
|
||||
if (this.getVisibleSubMenu() !== e.detail.type)
|
||||
this.showSubMenu(e.detail.type);
|
||||
else
|
||||
this.hideSubMenus();
|
||||
});
|
||||
|
||||
document.addEventListener("contextMenuDeployAircraft", () => {
|
||||
this.hide();
|
||||
this.#spawnOptions.coalition = getActiveCoalition();
|
||||
if (this.#spawnOptions) {
|
||||
getMap().addTemporaryMarker(this.#spawnOptions.latlng);
|
||||
getMap().addTemporaryMarker(this.#spawnOptions);
|
||||
spawnAircraft(this.#spawnOptions);
|
||||
}
|
||||
});
|
||||
@ -62,7 +66,7 @@ export class MapContextMenu extends ContextMenu {
|
||||
this.hide();
|
||||
this.#spawnOptions.coalition = getActiveCoalition();
|
||||
if (this.#spawnOptions) {
|
||||
getMap().addTemporaryMarker(this.#spawnOptions.latlng);
|
||||
getMap().addTemporaryMarker(this.#spawnOptions);
|
||||
spawnGroundUnit(this.#spawnOptions);
|
||||
}
|
||||
});
|
||||
@ -92,7 +96,7 @@ export class MapContextMenu extends ContextMenu {
|
||||
this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", type !== "aircraft");
|
||||
this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", type === "aircraft");
|
||||
this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.classList.toggle("hide", type !== "ground-unit");
|
||||
this.getContainer()?.querySelector("#ground-unit-spawn-button")?.classList.toggle("is-open", type === "ground-unit");
|
||||
this.getContainer()?.querySelector("#ground-ol-contexmenu-button")?.classList.toggle("is-open", type === "ground-unit");
|
||||
this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", type !== "smoke");
|
||||
this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", type === "smoke");
|
||||
this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", type !== "explosion");
|
||||
@ -103,8 +107,30 @@ export class MapContextMenu extends ContextMenu {
|
||||
this.#resetGroundUnitRole();
|
||||
this.#resetGroundUnitType();
|
||||
this.clip();
|
||||
|
||||
this.setVisibleSubMenu(type);
|
||||
}
|
||||
|
||||
hideSubMenus() {
|
||||
this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", true);
|
||||
this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", false);
|
||||
this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.classList.toggle("hide", true);
|
||||
this.getContainer()?.querySelector("#ground-ol-contexmenu-button")?.classList.toggle("is-open", false);
|
||||
this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", true);
|
||||
this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", false);
|
||||
this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", true);
|
||||
this.getContainer()?.querySelector("#explosion-spawn-button")?.classList.toggle("is-open", false);
|
||||
|
||||
this.#resetAircraftRole();
|
||||
this.#resetAircraftType();
|
||||
this.#resetGroundUnitRole();
|
||||
this.#resetGroundUnitType();
|
||||
this.clip();
|
||||
|
||||
this.setVisibleSubMenu(null);
|
||||
}
|
||||
|
||||
|
||||
showUpperBar() {
|
||||
this.getContainer()?.querySelector("#upper-bar")?.classList.toggle("hide", false);
|
||||
}
|
||||
@ -123,6 +149,7 @@ export class MapContextMenu extends ContextMenu {
|
||||
|
||||
#onSwitchClick(value: boolean) {
|
||||
value? setActiveCoalition("red"): setActiveCoalition("blue");
|
||||
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
|
||||
}
|
||||
|
||||
#onSwitchRightClick(e: any) {
|
||||
@ -152,7 +179,7 @@ export class MapContextMenu extends ContextMenu {
|
||||
this.#resetAircraftType();
|
||||
var type = aircraftDatabase.getByLabel(label)?.name || null;
|
||||
if (type != null) {
|
||||
this.#spawnOptions.type = type;
|
||||
this.#spawnOptions.name = type;
|
||||
this.#aircraftLoadoutDropdown.setOptions(aircraftDatabase.getLoadoutNamesByRole(type, this.#spawnOptions.role));
|
||||
this.#aircraftLoadoutDropdown.selectValue(0);
|
||||
var image = (<HTMLImageElement>this.getContainer()?.querySelector("#unit-image"));
|
||||
@ -171,7 +198,7 @@ export class MapContextMenu extends ContextMenu {
|
||||
}
|
||||
|
||||
#setAircraftLoadout(loadoutName: string) {
|
||||
var loadout = aircraftDatabase.getLoadoutByName(this.#spawnOptions.type, loadoutName);
|
||||
var loadout = aircraftDatabase.getLoadoutByName(this.#spawnOptions.name, loadoutName);
|
||||
if (loadout) {
|
||||
this.#spawnOptions.loadout = loadout.code;
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
|
||||
@ -214,7 +241,7 @@ export class MapContextMenu extends ContextMenu {
|
||||
this.#resetGroundUnitType();
|
||||
var type = groundUnitsDatabase.getByLabel(label)?.name || null;
|
||||
if (type != null) {
|
||||
this.#spawnOptions.type = type;
|
||||
this.#spawnOptions.name = type;
|
||||
(<HTMLButtonElement>this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
|
||||
}
|
||||
this.clip();
|
||||
|
||||
@ -223,6 +223,10 @@ export function getUnitsManager() {
|
||||
return unitsManager;
|
||||
}
|
||||
|
||||
export function getMissionHandler() {
|
||||
return missionHandler;
|
||||
}
|
||||
|
||||
export function getUnitInfoPanel() {
|
||||
return unitInfoPanel;
|
||||
}
|
||||
@ -249,7 +253,6 @@ export function getHotgroupPanel() {
|
||||
|
||||
export function setActiveCoalition(newActiveCoalition: string) {
|
||||
activeCoalition = newActiveCoalition;
|
||||
document.querySelectorAll('[data-active-coalition]').forEach((element: any) => { element.setAttribute("data-active-coalition", activeCoalition) });
|
||||
}
|
||||
|
||||
export function getActiveCoalition() {
|
||||
|
||||
@ -46,7 +46,7 @@ export var BoxSelect = Handler.extend({
|
||||
|
||||
_onMouseDown: function (e: any) {
|
||||
if ((e.which == 1 && e.button == 0 && e.shiftKey)) {
|
||||
|
||||
this._map.fire('selectionstart');
|
||||
// Clear the deferred resetState if it hasn't executed yet, otherwise it
|
||||
// will interrupt the interaction and orphan a box element in the container.
|
||||
this._clearDeferredResetState();
|
||||
|
||||
171
client/src/map/coalitionarea.ts
Normal file
@ -0,0 +1,171 @@
|
||||
import { DomUtil, LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions } from "leaflet";
|
||||
import { getMap } from "..";
|
||||
import { CoalitionAreaHandle } from "./coalitionareahandle";
|
||||
import { CoalitionAreaMiddleHandle } from "./coalitionareamiddlehandle";
|
||||
|
||||
export class CoalitionArea extends Polygon {
|
||||
#coalition: string = "blue";
|
||||
#selected: boolean = true;
|
||||
#editing: boolean = true;
|
||||
#handles: CoalitionAreaHandle[] = [];
|
||||
#middleHandles: CoalitionAreaMiddleHandle[] = [];
|
||||
#activeIndex: number = 0;
|
||||
|
||||
constructor(latlngs: LatLngExpression[] | LatLngExpression[][] | LatLngExpression[][][], options?: PolylineOptions) {
|
||||
if (options === undefined)
|
||||
options = {};
|
||||
|
||||
options.bubblingMouseEvents = false;
|
||||
super(latlngs, options);
|
||||
this.#setColors();
|
||||
this.#registerCallbacks();
|
||||
|
||||
}
|
||||
|
||||
setCoalition(coalition: string) {
|
||||
this.#coalition = coalition;
|
||||
this.#setColors();
|
||||
}
|
||||
|
||||
getCoalition() {
|
||||
return this.#coalition;
|
||||
}
|
||||
|
||||
setSelected(selected: boolean) {
|
||||
this.#selected = selected;
|
||||
this.#setColors();
|
||||
this.#setHandles();
|
||||
if (!this.getSelected() && this.getEditing()) {
|
||||
/* Remove the vertex we were working on */
|
||||
var latlngs = this.getLatLngs()[0] as LatLng[];
|
||||
latlngs.splice(this.#activeIndex, 1);
|
||||
this.setLatLngs(latlngs);
|
||||
this.setEditing(false);
|
||||
}
|
||||
}
|
||||
|
||||
getSelected() {
|
||||
return this.#selected;
|
||||
}
|
||||
|
||||
setEditing(editing: boolean) {
|
||||
this.#editing = editing;
|
||||
this.#setHandles();
|
||||
var latlngs = this.getLatLngs()[0] as LatLng[];
|
||||
|
||||
/* Remove areas with less than 2 vertexes */
|
||||
if (latlngs.length <= 2)
|
||||
getMap().deleteCoalitionArea(this);
|
||||
}
|
||||
|
||||
getEditing() {
|
||||
return this.#editing;
|
||||
}
|
||||
|
||||
setInteractive(interactive: boolean) {
|
||||
this.setOpacity(interactive? 1: 0.5);
|
||||
this.options.interactive = interactive;
|
||||
|
||||
if (interactive)
|
||||
DomUtil.addClass(this.getElement() as HTMLElement, 'leaflet-interactive');
|
||||
else
|
||||
DomUtil.removeClass(this.getElement() as HTMLElement, 'leaflet-interactive');
|
||||
}
|
||||
|
||||
addTemporaryLatLng(latlng: LatLng) {
|
||||
this.#activeIndex++;
|
||||
var latlngs = this.getLatLngs()[0] as LatLng[];
|
||||
latlngs.splice(this.#activeIndex, 0, latlng);
|
||||
this.setLatLngs(latlngs);
|
||||
this.#setHandles();
|
||||
}
|
||||
|
||||
moveActiveVertex(latlng: LatLng) {
|
||||
var latlngs = this.getLatLngs()[0] as LatLng[];
|
||||
latlngs[this.#activeIndex] = latlng;
|
||||
this.setLatLngs(latlngs);
|
||||
this.#setHandles();
|
||||
}
|
||||
|
||||
setOpacity(opacity: number) {
|
||||
this.setStyle({opacity: opacity, fillOpacity: opacity * 0.25});
|
||||
}
|
||||
|
||||
#setColors() {
|
||||
const coalitionColor = this.getCoalition() === "blue" ? "#247be2" : "#ff5858";
|
||||
this.setStyle({ color: this.getSelected() ? "white" : coalitionColor, fillColor: coalitionColor });
|
||||
}
|
||||
|
||||
#setHandles() {
|
||||
this.#handles.forEach((handle: CoalitionAreaHandle) => handle.removeFrom(getMap()));
|
||||
this.#handles = [];
|
||||
if (this.getSelected()) {
|
||||
var latlngs = this.getLatLngs()[0] as LatLng[];
|
||||
latlngs.forEach((latlng: LatLng, idx: number) => {
|
||||
/* Add the polygon vertex handle (for moving the vertex) */
|
||||
const handle = new CoalitionAreaHandle(latlng);
|
||||
handle.addTo(getMap());
|
||||
handle.on("drag", (e: any) => {
|
||||
var latlngs = this.getLatLngs()[0] as LatLng[];
|
||||
latlngs[idx] = e.target.getLatLng();
|
||||
this.setLatLngs(latlngs);
|
||||
this.#setMiddleHandles();
|
||||
});
|
||||
this.#handles.push(handle);
|
||||
});
|
||||
}
|
||||
this.#setMiddleHandles();
|
||||
}
|
||||
|
||||
#setMiddleHandles() {
|
||||
this.#middleHandles.forEach((handle: CoalitionAreaMiddleHandle) => handle.removeFrom(getMap()));
|
||||
this.#middleHandles = [];
|
||||
var latlngs = this.getLatLngs()[0] as LatLng[];
|
||||
if (this.getSelected() && latlngs.length >= 2) {
|
||||
var lastLatLng: LatLng | null = null;
|
||||
latlngs.concat([latlngs[0]]).forEach((latlng: LatLng, idx: number) => {
|
||||
/* Add the polygon middle point handle (for adding new vertexes) */
|
||||
if (lastLatLng != null) {
|
||||
const handle1Point = getMap().latLngToLayerPoint(latlng);
|
||||
const handle2Point = getMap().latLngToLayerPoint(lastLatLng);
|
||||
const middlePoint = new Point((handle1Point.x + handle2Point.x) / 2, (handle1Point.y + handle2Point.y) / 2);
|
||||
const middleLatLng = getMap().layerPointToLatLng(middlePoint);
|
||||
|
||||
const middleHandle = new CoalitionAreaMiddleHandle(middleLatLng);
|
||||
middleHandle.addTo(getMap());
|
||||
middleHandle.on("click", (e: any) => {
|
||||
this.#activeIndex = idx - 1;
|
||||
this.addTemporaryLatLng(middleLatLng);
|
||||
});
|
||||
this.#middleHandles.push(middleHandle);
|
||||
}
|
||||
lastLatLng = latlng;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#registerCallbacks() {
|
||||
this.on("click", (e: any) => {
|
||||
getMap().deselectAllCoalitionAreas();
|
||||
if (!this.getSelected()) {
|
||||
this.setSelected(true);
|
||||
}
|
||||
});
|
||||
|
||||
this.on("contextmenu", (e: any) => {
|
||||
if (!this.getEditing()) {
|
||||
getMap().deselectAllCoalitionAreas();
|
||||
this.setSelected(true);
|
||||
getMap().showCoalitionAreaContextMenu(e, this);
|
||||
}
|
||||
else
|
||||
this.setEditing(false);
|
||||
});
|
||||
}
|
||||
|
||||
onRemove(map: Map): this {
|
||||
super.onRemove(map);
|
||||
this.#handles.concat(this.#middleHandles).forEach((handle: CoalitionAreaHandle | CoalitionAreaMiddleHandle) => handle.removeFrom(getMap()));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
19
client/src/map/coalitionareahandle.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { DivIcon, LatLng } from "leaflet";
|
||||
import { CustomMarker } from "./custommarker";
|
||||
|
||||
export class CoalitionAreaHandle extends CustomMarker {
|
||||
constructor(latlng: LatLng) {
|
||||
super(latlng, {interactive: true, draggable: true});
|
||||
}
|
||||
|
||||
createIcon() {
|
||||
this.setIcon(new DivIcon({
|
||||
iconSize: [24, 24],
|
||||
iconAnchor: [12, 12],
|
||||
className: "leaflet-coalitionarea-handle-marker",
|
||||
}));
|
||||
var el = document.createElement("div");
|
||||
el.classList.add("ol-coalitionarea-handle-icon");
|
||||
this.getElement()?.appendChild(el);
|
||||
}
|
||||
}
|
||||
19
client/src/map/coalitionareamiddlehandle.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { DivIcon, LatLng } from "leaflet";
|
||||
import { CustomMarker } from "./custommarker";
|
||||
|
||||
export class CoalitionAreaMiddleHandle extends CustomMarker {
|
||||
constructor(latlng: LatLng) {
|
||||
super(latlng, {interactive: true, draggable: false});
|
||||
}
|
||||
|
||||
createIcon() {
|
||||
this.setIcon(new DivIcon({
|
||||
iconSize: [16, 16],
|
||||
iconAnchor: [8, 8],
|
||||
className: "leaflet-coalitionarea-middle-handle-marker",
|
||||
}));
|
||||
var el = document.createElement("div");
|
||||
el.classList.add("ol-coalitionarea-middle-handle-icon");
|
||||
this.getElement()?.appendChild(el);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,12 @@
|
||||
import { DivIcon } from "leaflet";
|
||||
import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
|
||||
import { CustomMarker } from "./custommarker";
|
||||
|
||||
export class DestinationPreviewMarker extends CustomMarker {
|
||||
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
|
||||
super(latlng, options);
|
||||
this.setZIndexOffset(9999);
|
||||
}
|
||||
|
||||
createIcon() {
|
||||
this.setIcon(new DivIcon({
|
||||
iconSize: [52, 52],
|
||||
|
||||
20
client/src/map/drawingcursor.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { DivIcon, LatLng } from "leaflet";
|
||||
import { CustomMarker } from "./custommarker";
|
||||
|
||||
export class DrawingCursor extends CustomMarker {
|
||||
constructor() {
|
||||
super(new LatLng(0, 0), {interactive: false})
|
||||
this.setZIndexOffset(9999);
|
||||
}
|
||||
|
||||
createIcon() {
|
||||
this.setIcon(new DivIcon({
|
||||
iconSize: [24, 24],
|
||||
iconAnchor: [0, 24],
|
||||
className: "leaflet-draw-marker",
|
||||
}));
|
||||
var el = document.createElement("div");
|
||||
el.classList.add("ol-draw-icon");
|
||||
this.getElement()?.appendChild(el);
|
||||
}
|
||||
}
|
||||
@ -12,22 +12,17 @@ import { DestinationPreviewMarker } from "./destinationpreviewmarker";
|
||||
import { TemporaryUnitMarker } from "./temporaryunitmarker";
|
||||
import { ClickableMiniMap } from "./clickableminimap";
|
||||
import { SVGInjector } from '@tanem/svg-injector'
|
||||
import { layers as mapLayers, mapBounds, minimapBoundaries } from "../constants/constants";
|
||||
import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTootlips, FIRE_AT_AREA, MOVE_UNIT, CARPET_BOMBING, BOMBING, COALITIONAREA_INTERACT } from "../constants/constants";
|
||||
import { TargetMarker } from "./targetmarker";
|
||||
import { CoalitionArea } from "./coalitionarea";
|
||||
import { CoalitionAreaContextMenu } from "../controls/coalitionareacontextmenu";
|
||||
import { DrawingCursor } from "./drawingcursor";
|
||||
|
||||
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
|
||||
|
||||
// TODO would be nice to convert to ts
|
||||
require("../../public/javascripts/leaflet.nauticscale.js")
|
||||
|
||||
/* Map constants */
|
||||
export const IDLE = "Idle";
|
||||
export const MOVE_UNIT = "Move unit";
|
||||
export const BOMBING = "Bombing";
|
||||
export const CARPET_BOMBING = "Carpet bombing";
|
||||
export const FIRE_AT_AREA = "Fire at area";
|
||||
export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"];
|
||||
export const visibilityControlsTootlips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"];
|
||||
require("../../public/javascripts/L.Path.Drag.js")
|
||||
|
||||
export class Map extends L.Map {
|
||||
#ID: string;
|
||||
@ -42,27 +37,34 @@ export class Map extends L.Map {
|
||||
#panUp: boolean = false;
|
||||
#panDown: boolean = false;
|
||||
#lastMousePosition: L.Point = new L.Point(0, 0);
|
||||
#shiftKey: boolean = false;
|
||||
#ctrlKey: boolean = false;
|
||||
#centerUnit: Unit | null = null;
|
||||
#miniMap: ClickableMiniMap | null = null;
|
||||
#miniMapLayerGroup: L.LayerGroup;
|
||||
#temporaryMarkers: TemporaryUnitMarker[] = [];
|
||||
#destinationPreviewMarkers: DestinationPreviewMarker[] = [];
|
||||
#targetMarker: TargetMarker;
|
||||
#selecting: boolean = false;
|
||||
|
||||
#destinationGroupRotation: number = 0;
|
||||
#computeDestinationRotation: boolean = false;
|
||||
#destinationRotationCenter: L.LatLng | null = null;
|
||||
#coalitionAreas: CoalitionArea[] = [];
|
||||
|
||||
#targetCursor: TargetMarker = new TargetMarker(new L.LatLng(0, 0), { interactive: false });
|
||||
#destinationPreviewCursors: DestinationPreviewMarker[] = [];
|
||||
#drawingCursor: DrawingCursor = new DrawingCursor();
|
||||
|
||||
#mapContextMenu: MapContextMenu = new MapContextMenu("map-contextmenu");
|
||||
#unitContextMenu: UnitContextMenu = new UnitContextMenu("unit-contextmenu");
|
||||
#airbaseContextMenu: AirbaseContextMenu = new AirbaseContextMenu("airbase-contextmenu");
|
||||
#coalitionAreaContextMenu: CoalitionAreaContextMenu = new CoalitionAreaContextMenu("coalition-area-contextmenu");
|
||||
|
||||
#mapSourceDropdown: Dropdown;
|
||||
#optionButtons: { [key: string]: HTMLButtonElement[] } = {}
|
||||
|
||||
constructor(ID: string) {
|
||||
/* Init the leaflet map */
|
||||
|
||||
//@ts-ignore
|
||||
//@ts-ignore Needed because the boxSelect option is non-standard
|
||||
super(ID, { doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true, zoomAnimation: true, maxBoundsViscosity: 1.0, minZoom: 7, keyboard: true, keyboardPanDelta: 0 });
|
||||
this.setView([37.23, -115.8], 10);
|
||||
|
||||
@ -92,18 +94,19 @@ export class Map extends L.Map {
|
||||
this.on("zoomstart", (e: any) => this.#onZoom(e));
|
||||
this.on("drag", (e: any) => this.centerOnUnit(null));
|
||||
this.on("contextmenu", (e: any) => this.#onContextMenu(e));
|
||||
this.on('selectionstart', (e: any) => this.#onSelectionStart(e));
|
||||
this.on('selectionend', (e: any) => this.#onSelectionEnd(e));
|
||||
this.on('mousedown', (e: any) => this.#onMouseDown(e));
|
||||
this.on('mouseup', (e: any) => this.#onMouseUp(e));
|
||||
this.on('mousemove', (e: any) => this.#onMouseMove(e));
|
||||
this.on('keydown', (e: any) => this.#updateDestinationPreview(e));
|
||||
this.on('keyup', (e: any) => this.#updateDestinationPreview(e));
|
||||
this.on('keydown', (e: any) => this.#onKeyDown(e));
|
||||
this.on('keyup', (e: any) => this.#onKeyUp(e));
|
||||
|
||||
/* Event listeners */
|
||||
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
|
||||
const el = ev.detail._element;
|
||||
el?.classList.toggle("off");
|
||||
getUnitsManager().setHiddenType(ev.detail.coalition, (el?.currentTarget as HTMLElement)?.classList.contains("off"));
|
||||
getUnitsManager().setHiddenType(ev.detail.coalition, !el?.classList.contains("off"));
|
||||
Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
|
||||
});
|
||||
|
||||
@ -114,6 +117,29 @@ export class Map extends L.Map {
|
||||
Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
|
||||
});
|
||||
|
||||
document.addEventListener("toggleCoalitionAreaInteraction", (ev: CustomEventInit) => {
|
||||
const el = ev.detail._element;
|
||||
/* Add listener to set the button to off if the state changes */
|
||||
document.addEventListener("mapStateChanged", () => el?.classList.toggle("off", !(this.getState() === COALITIONAREA_INTERACT)));
|
||||
if (this.getState() !== COALITIONAREA_INTERACT)
|
||||
this.setState(COALITIONAREA_INTERACT);
|
||||
else
|
||||
this.setState(IDLE);
|
||||
});
|
||||
|
||||
document.addEventListener("toggleCoalitionAreaDraw", (ev: CustomEventInit) => {
|
||||
const el = ev.detail._element;
|
||||
/* Add listener to set the button to off if the state changes */
|
||||
document.addEventListener("mapStateChanged", () => el?.classList.toggle("off", !(this.getState() === COALITIONAREA_DRAW_POLYGON)));
|
||||
this.deselectAllCoalitionAreas();
|
||||
if (ev.detail?.type == "polygon") {
|
||||
if (this.getState() !== COALITIONAREA_DRAW_POLYGON)
|
||||
this.setState(COALITIONAREA_DRAW_POLYGON);
|
||||
else
|
||||
this.setState(IDLE);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("unitUpdated", (ev: CustomEvent) => {
|
||||
if (this.#centerUnit != null && ev.detail == this.#centerUnit)
|
||||
this.#panToUnit(this.#centerUnit);
|
||||
@ -122,7 +148,7 @@ export class Map extends L.Map {
|
||||
/* Pan interval */
|
||||
this.#panInterval = window.setInterval(() => {
|
||||
if (this.#panLeft || this.#panDown || this.#panRight || this.#panLeft)
|
||||
this.panBy(new L.Point(((this.#panLeft? -1 : 0) + (this.#panRight ? 1 : 0)) * this.#deafultPanDelta,
|
||||
this.panBy(new L.Point(((this.#panLeft ? -1 : 0) + (this.#panRight ? 1 : 0)) * this.#deafultPanDelta,
|
||||
((this.#panUp ? -1 : 0) + (this.#panDown ? 1 : 0)) * this.#deafultPanDelta));
|
||||
}, 20);
|
||||
|
||||
@ -131,16 +157,13 @@ export class Map extends L.Map {
|
||||
return this.#createOptionButton(option, `visibility/${option.toLowerCase()}.svg`, visibilityControlsTootlips[index], "toggleUnitVisibility", `{"type": "${option}"}`);
|
||||
});
|
||||
document.querySelector("#unit-visibility-control")?.append(...this.#optionButtons["visibility"]);
|
||||
|
||||
/* Markers */
|
||||
this.#targetMarker = new TargetMarker(new L.LatLng(0, 0), {interactive: false});
|
||||
}
|
||||
|
||||
setLayer(layerName: string) {
|
||||
if (this.#layer != null)
|
||||
if (this.#layer != null)
|
||||
this.removeLayer(this.#layer)
|
||||
|
||||
if (layerName in mapLayers){
|
||||
|
||||
if (layerName in mapLayers) {
|
||||
const layerData = mapLayers[layerName as keyof typeof mapLayers];
|
||||
var options: L.TileLayerOptions = {
|
||||
attribution: layerData.attribution,
|
||||
@ -160,21 +183,27 @@ export class Map extends L.Map {
|
||||
/* State machine */
|
||||
setState(state: string) {
|
||||
this.#state = state;
|
||||
if (this.#state === IDLE) {
|
||||
this.#resetDestinationMarkers();
|
||||
this.#resetTargetMarker();
|
||||
this.#showCursor();
|
||||
this.#updateCursor();
|
||||
|
||||
/* Operations to perform if you are NOT in a state */
|
||||
if (this.#state !== COALITIONAREA_INTERACT) {
|
||||
this.#coalitionAreas.forEach((coalitionArea: CoalitionArea) => {
|
||||
coalitionArea.setInteractive(false);
|
||||
});
|
||||
}
|
||||
else if (this.#state === MOVE_UNIT) {
|
||||
this.#resetTargetMarker();
|
||||
this.#createDestinationMarkers();
|
||||
if (this.#destinationPreviewMarkers.length > 0)
|
||||
this.#hideCursor();
|
||||
if (this.#state !== COALITIONAREA_DRAW_POLYGON) {
|
||||
this.#deselectCoalitionAreas();
|
||||
}
|
||||
else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) {
|
||||
this.#resetDestinationMarkers();
|
||||
this.#createTargetMarker();
|
||||
this.#hideCursor();
|
||||
|
||||
/* Operations to perform if you ARE in a state */
|
||||
if (this.#state === COALITIONAREA_INTERACT) {
|
||||
this.#coalitionAreas.forEach((coalitionArea: CoalitionArea) => {
|
||||
coalitionArea.setInteractive(true);
|
||||
});
|
||||
}
|
||||
else if (this.#state === COALITIONAREA_DRAW_POLYGON) {
|
||||
this.#coalitionAreas.push(new CoalitionArea([]));
|
||||
this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this);
|
||||
}
|
||||
document.dispatchEvent(new CustomEvent("mapStateChanged"));
|
||||
}
|
||||
@ -183,11 +212,23 @@ export class Map extends L.Map {
|
||||
return this.#state;
|
||||
}
|
||||
|
||||
deselectAllCoalitionAreas() {
|
||||
this.#coalitionAreas.forEach((coalitionArea: CoalitionArea) => coalitionArea.setSelected(false));
|
||||
}
|
||||
|
||||
deleteCoalitionArea(coalitionArea: CoalitionArea) {
|
||||
if (this.#coalitionAreas.includes(coalitionArea))
|
||||
this.#coalitionAreas.splice(this.#coalitionAreas.indexOf(coalitionArea), 1);
|
||||
if (this.hasLayer(coalitionArea))
|
||||
this.removeLayer(coalitionArea);
|
||||
}
|
||||
|
||||
/* Context Menus */
|
||||
hideAllContextMenus() {
|
||||
this.hideMapContextMenu();
|
||||
this.hideUnitContextMenu();
|
||||
this.hideAirbaseContextMenu();
|
||||
this.hideCoalitionAreaContextMenu();
|
||||
}
|
||||
|
||||
showMapContextMenu(e: any) {
|
||||
@ -238,6 +279,22 @@ export class Map extends L.Map {
|
||||
this.#airbaseContextMenu.hide();
|
||||
}
|
||||
|
||||
showCoalitionAreaContextMenu(e: any, coalitionArea: CoalitionArea) {
|
||||
this.hideAllContextMenus();
|
||||
var x = e.originalEvent.x;
|
||||
var y = e.originalEvent.y;
|
||||
this.#coalitionAreaContextMenu.show(x, y, e.latlng);
|
||||
this.#coalitionAreaContextMenu.setCoalitionArea(coalitionArea);
|
||||
}
|
||||
|
||||
getCoalitionAreaContextMenu() {
|
||||
return this.#coalitionAreaContextMenu;
|
||||
}
|
||||
|
||||
hideCoalitionAreaContextMenu() {
|
||||
this.#coalitionAreaContextMenu.hide();
|
||||
}
|
||||
|
||||
/* Mouse coordinates */
|
||||
getMousePosition() {
|
||||
return this.#lastMousePosition;
|
||||
@ -333,36 +390,49 @@ export class Map extends L.Map {
|
||||
}
|
||||
}
|
||||
|
||||
addTemporaryMarker(latlng: L.LatLng) {
|
||||
var marker = new TemporaryUnitMarker(latlng);
|
||||
addTemporaryMarker(spawnOptions: SpawnOptions) {
|
||||
var marker = new TemporaryUnitMarker(spawnOptions);
|
||||
marker.addTo(this);
|
||||
this.#temporaryMarkers.push(marker);
|
||||
}
|
||||
|
||||
removeTemporaryMarker(latlng: L.LatLng) {
|
||||
var d: number | null = null;
|
||||
// TODO something more refined than this
|
||||
var dist: number | null = null;
|
||||
var closest: L.Marker | null = null;
|
||||
var i: number = 0;
|
||||
this.#temporaryMarkers.forEach((marker: L.Marker, idx: number) => {
|
||||
var t = latlng.distanceTo(marker.getLatLng());
|
||||
if (d == null || t < d) {
|
||||
d = t;
|
||||
if (dist == null || t < dist) {
|
||||
dist = t;
|
||||
closest = marker;
|
||||
i = idx;
|
||||
}
|
||||
});
|
||||
if (closest) {
|
||||
if (closest && dist != null && dist < 100) {
|
||||
this.removeLayer(closest);
|
||||
delete this.#temporaryMarkers[i];
|
||||
this.#temporaryMarkers.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedCoalitionArea() {
|
||||
return this.#coalitionAreas.find((area: CoalitionArea) => { return area.getSelected() });
|
||||
}
|
||||
|
||||
/* Event handlers */
|
||||
#onClick(e: any) {
|
||||
if (!this.#preventLeftClick) {
|
||||
this.hideAllContextMenus();
|
||||
if (this.#state === IDLE) {
|
||||
|
||||
this.deselectAllCoalitionAreas();
|
||||
}
|
||||
else if (this.#state === COALITIONAREA_DRAW_POLYGON) {
|
||||
if (this.getSelectedCoalitionArea()?.getEditing()) {
|
||||
this.getSelectedCoalitionArea()?.addTemporaryLatLng(e.latlng);
|
||||
}
|
||||
else {
|
||||
this.deselectAllCoalitionAreas();
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.setState(IDLE);
|
||||
@ -372,7 +442,7 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
#onDoubleClick(e: any) {
|
||||
|
||||
this.deselectAllCoalitionAreas();
|
||||
}
|
||||
|
||||
#onContextMenu(e: any) {
|
||||
@ -386,50 +456,49 @@ export class Map extends L.Map {
|
||||
if (!e.originalEvent.ctrlKey) {
|
||||
getUnitsManager().selectedUnitsClearDestinations();
|
||||
}
|
||||
getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, e.originalEvent.shiftKey, this.#destinationGroupRotation)
|
||||
this.#destinationGroupRotation = 0;
|
||||
getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, this.#shiftKey, this.#destinationGroupRotation)
|
||||
this.#destinationGroupRotation = 0;
|
||||
this.#destinationRotationCenter = null;
|
||||
this.#computeDestinationRotation = false;
|
||||
}
|
||||
else if (this.#state === BOMBING) {
|
||||
getUnitsManager().getSelectedUnits().length > 0? this.setState(MOVE_UNIT): this.setState(IDLE);
|
||||
getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates());
|
||||
getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
|
||||
getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates());
|
||||
}
|
||||
else if (this.#state === CARPET_BOMBING) {
|
||||
getUnitsManager().getSelectedUnits().length > 0? this.setState(MOVE_UNIT): this.setState(IDLE);
|
||||
getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates());
|
||||
getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
|
||||
getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates());
|
||||
}
|
||||
else if (this.#state === FIRE_AT_AREA) {
|
||||
getUnitsManager().getSelectedUnits().length > 0? this.setState(MOVE_UNIT): this.setState(IDLE);
|
||||
getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates());
|
||||
getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
|
||||
getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates());
|
||||
}
|
||||
else {
|
||||
this.setState(IDLE);
|
||||
}
|
||||
}
|
||||
|
||||
#executeAction(e: any, action: string) {
|
||||
if (action === "bomb")
|
||||
getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates());
|
||||
else if (action === "carpet-bomb")
|
||||
getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates());
|
||||
else if (action === "building-bomb")
|
||||
getUnitsManager().selectedUnitsBombBuilding(this.getMouseCoordinates());
|
||||
else if (action === "fire-at-area")
|
||||
getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates());
|
||||
#onSelectionStart(e: any) {
|
||||
this.#selecting = true;
|
||||
this.#updateCursor();
|
||||
}
|
||||
|
||||
#onSelectionEnd(e: any) {
|
||||
this.#selecting = false;
|
||||
clearTimeout(this.#leftClickTimer);
|
||||
this.#preventLeftClick = true;
|
||||
this.#leftClickTimer = window.setTimeout(() => {
|
||||
this.#preventLeftClick = false;
|
||||
}, 200);
|
||||
getUnitsManager().selectFromBounds(e.selectionBounds);
|
||||
this.#updateCursor();
|
||||
}
|
||||
|
||||
#onMouseDown(e: any) {
|
||||
this.hideAllContextMenus();
|
||||
|
||||
if (this.#state == MOVE_UNIT) {
|
||||
this.#destinationGroupRotation = 0;
|
||||
this.#destinationGroupRotation = 0;
|
||||
this.#destinationRotationCenter = null;
|
||||
this.#computeDestinationRotation = false;
|
||||
if (e.originalEvent.button == 2) {
|
||||
@ -446,14 +515,36 @@ export class Map extends L.Map {
|
||||
this.#lastMousePosition.x = e.originalEvent.x;
|
||||
this.#lastMousePosition.y = e.originalEvent.y;
|
||||
|
||||
if (this.#state === MOVE_UNIT){
|
||||
this.#updateCursor();
|
||||
|
||||
if (this.#state === MOVE_UNIT) {
|
||||
/* Update the position of the destination cursors depeding on mouse rotation */
|
||||
if (this.#computeDestinationRotation && this.#destinationRotationCenter != null)
|
||||
this.#destinationGroupRotation = -bearing(this.#destinationRotationCenter.lat, this.#destinationRotationCenter.lng, this.getMouseCoordinates().lat, this.getMouseCoordinates().lng);
|
||||
this.#updateDestinationPreview(e);
|
||||
this.#updateDestinationCursors();
|
||||
}
|
||||
else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) {
|
||||
this.#targetMarker.setLatLng(this.getMouseCoordinates());
|
||||
this.#targetCursor.setLatLng(this.getMouseCoordinates());
|
||||
}
|
||||
else if (this.#state === COALITIONAREA_DRAW_POLYGON) {
|
||||
this.#drawingCursor.setLatLng(e.latlng);
|
||||
/* Update the polygon being drawn with the current position of the mouse cursor */
|
||||
this.getSelectedCoalitionArea()?.moveActiveVertex(e.latlng);
|
||||
}
|
||||
}
|
||||
|
||||
#onKeyDown(e: any) {
|
||||
this.#shiftKey = e.originalEvent.shiftKey;
|
||||
this.#ctrlKey = e.originalEvent.ctrlKey;
|
||||
this.#updateCursor();
|
||||
this.#updateDestinationCursors();
|
||||
}
|
||||
|
||||
#onKeyUp(e: any) {
|
||||
this.#shiftKey = e.originalEvent.shiftKey;
|
||||
this.#ctrlKey = e.originalEvent.ctrlKey;
|
||||
this.#updateCursor();
|
||||
this.#updateDestinationCursors();
|
||||
}
|
||||
|
||||
#onZoom(e: any) {
|
||||
@ -462,7 +553,7 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
#panToUnit(unit: Unit) {
|
||||
var unitPosition = new L.LatLng(unit.getFlightData().latitude, unit.getFlightData().longitude);
|
||||
var unitPosition = new L.LatLng(unit.getPosition().lat, unit.getPosition().lng);
|
||||
this.setView(unitPosition, this.getZoom(), { animate: false });
|
||||
}
|
||||
|
||||
@ -471,13 +562,6 @@ export class Map extends L.Map {
|
||||
return minimapBoundaries;
|
||||
}
|
||||
|
||||
#updateDestinationPreview(e: any) {
|
||||
Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : this.getMouseCoordinates(), this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => {
|
||||
if (idx < this.#destinationPreviewMarkers.length)
|
||||
this.#destinationPreviewMarkers[idx].setLatLng(e.originalEvent.shiftKey ? latlng : this.getMouseCoordinates());
|
||||
})
|
||||
}
|
||||
|
||||
#createOptionButton(value: string, url: string, title: string, callback: string, argument: string) {
|
||||
var button = document.createElement("button");
|
||||
const img = document.createElement("img");
|
||||
@ -491,47 +575,115 @@ export class Map extends L.Map {
|
||||
return button;
|
||||
}
|
||||
|
||||
#createDestinationMarkers() {
|
||||
this.#resetDestinationMarkers();
|
||||
#deselectCoalitionAreas() {
|
||||
this.getSelectedCoalitionArea()?.setSelected(false);
|
||||
}
|
||||
|
||||
if (getUnitsManager().getSelectedUnits({ excludeHumans: true }).length > 0) {
|
||||
/* Create the unit destination preview markers */
|
||||
this.#destinationPreviewMarkers = getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).map((unit: Unit) => {
|
||||
var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), {interactive: false});
|
||||
/* Cursors */
|
||||
#showDefaultCursor() {
|
||||
document.getElementById(this.#ID)?.classList.remove("hidden-cursor");
|
||||
}
|
||||
|
||||
#hideDefaultCursor() {
|
||||
document.getElementById(this.#ID)?.classList.add("hidden-cursor");
|
||||
}
|
||||
|
||||
#showDestinationCursors() {
|
||||
const singleCursor = !this.#shiftKey;
|
||||
const selectedUnitsCount = getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length;
|
||||
if (selectedUnitsCount > 0) {
|
||||
if (singleCursor && this.#destinationPreviewCursors.length != 1) {
|
||||
this.#hideDestinationCursors();
|
||||
var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false });
|
||||
marker.addTo(this);
|
||||
return marker;
|
||||
})
|
||||
this.#destinationPreviewCursors = [marker];
|
||||
}
|
||||
else if (!singleCursor) {
|
||||
while (this.#destinationPreviewCursors.length > selectedUnitsCount) {
|
||||
this.removeLayer(this.#destinationPreviewCursors[0]);
|
||||
this.#destinationPreviewCursors.splice(0, 1);
|
||||
}
|
||||
|
||||
while (this.#destinationPreviewCursors.length < selectedUnitsCount) {
|
||||
var cursor = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false });
|
||||
cursor.addTo(this);
|
||||
this.#destinationPreviewCursors.push(cursor);
|
||||
}
|
||||
|
||||
this.#updateDestinationCursors();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#resetDestinationMarkers() {
|
||||
/* Remove all the destination preview markers */
|
||||
this.#destinationPreviewMarkers.forEach((marker: L.Marker) => {
|
||||
#updateDestinationCursors() {
|
||||
const groupLatLng = this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : this.getMouseCoordinates();
|
||||
if (this.#destinationPreviewCursors.length == 1)
|
||||
this.#destinationPreviewCursors[0].setLatLng(this.getMouseCoordinates());
|
||||
else {
|
||||
Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(groupLatLng, this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => {
|
||||
if (idx < this.#destinationPreviewCursors.length)
|
||||
this.#destinationPreviewCursors[idx].setLatLng(this.#shiftKey? latlng : this.getMouseCoordinates());
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
#hideDestinationCursors() {
|
||||
/* Remove all the destination cursors */
|
||||
this.#destinationPreviewCursors.forEach((marker: L.Marker) => {
|
||||
this.removeLayer(marker);
|
||||
})
|
||||
this.#destinationPreviewMarkers = [];
|
||||
this.#destinationPreviewCursors = [];
|
||||
|
||||
/* Reset the variables used to compute the rotation of the group cursors */
|
||||
this.#destinationGroupRotation = 0;
|
||||
this.#computeDestinationRotation = false;
|
||||
this.#destinationRotationCenter = null;
|
||||
}
|
||||
|
||||
#createTargetMarker(){
|
||||
this.#resetTargetMarker();
|
||||
this.#targetMarker.addTo(this);
|
||||
#showTargetCursor() {
|
||||
this.#hideTargetCursor();
|
||||
this.#targetCursor.addTo(this);
|
||||
}
|
||||
|
||||
#resetTargetMarker() {
|
||||
this.#targetMarker.setLatLng(new L.LatLng(0, 0));
|
||||
this.removeLayer(this.#targetMarker);
|
||||
#hideTargetCursor() {
|
||||
this.#targetCursor.setLatLng(new L.LatLng(0, 0));
|
||||
this.removeLayer(this.#targetCursor);
|
||||
}
|
||||
|
||||
#showCursor() {
|
||||
document.getElementById(this.#ID)?.classList.remove("hidden-cursor");
|
||||
#showDrawingCursor() {
|
||||
this.#hideDefaultCursor();
|
||||
if (!this.hasLayer(this.#drawingCursor))
|
||||
this.#drawingCursor.addTo(this);
|
||||
}
|
||||
|
||||
#hideCursor() {
|
||||
document.getElementById(this.#ID)?.classList.add("hidden-cursor");
|
||||
#hideDrawingCursor() {
|
||||
this.#drawingCursor.setLatLng(new L.LatLng(0, 0));
|
||||
if (this.hasLayer(this.#drawingCursor))
|
||||
this.#drawingCursor.removeFrom(this);
|
||||
}
|
||||
}
|
||||
|
||||
#updateCursor() {
|
||||
/* If the ctrl key is being pressed or we are performing an area selection, show the default cursor */
|
||||
if (this.#ctrlKey || this.#selecting) {
|
||||
/* Hide all non default cursors */
|
||||
this.#hideDestinationCursors();
|
||||
this.#hideTargetCursor();
|
||||
this.#hideDrawingCursor();
|
||||
|
||||
this.#showDefaultCursor();
|
||||
} else {
|
||||
/* Hide all the unnecessary cursors depending on the active state */
|
||||
if (this.#state !== IDLE && this.#state !== COALITIONAREA_INTERACT) this.#hideDefaultCursor();
|
||||
if (this.#state !== MOVE_UNIT) this.#hideDestinationCursors();
|
||||
if (![BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) this.#hideTargetCursor();
|
||||
if (this.#state !== COALITIONAREA_DRAW_POLYGON) this.#hideDrawingCursor();
|
||||
|
||||
/* Show the active cursor depending on the active state */
|
||||
if (this.#state === IDLE || this.#state === COALITIONAREA_INTERACT) this.#showDefaultCursor();
|
||||
else if (this.#state === MOVE_UNIT) this.#showDestinationCursors();
|
||||
else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) this.#showTargetCursor();
|
||||
else if (this.#state === COALITIONAREA_DRAW_POLYGON) this.#showDrawingCursor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
import { DivIcon } from "leaflet";
|
||||
import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
|
||||
import { CustomMarker } from "./custommarker";
|
||||
|
||||
export class TargetMarker extends CustomMarker {
|
||||
#interactive: boolean = false;
|
||||
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
|
||||
super(latlng, options);
|
||||
this.setZIndexOffset(9999);
|
||||
}
|
||||
|
||||
createIcon() {
|
||||
this.setIcon(new DivIcon({
|
||||
@ -12,7 +15,6 @@ export class TargetMarker extends CustomMarker {
|
||||
}));
|
||||
var el = document.createElement("div");
|
||||
el.classList.add("ol-target-icon");
|
||||
el.classList.toggle("ol-target-icon-interactive", this.#interactive)
|
||||
this.getElement()?.appendChild(el);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,52 @@
|
||||
import { Icon } from "leaflet";
|
||||
import { CustomMarker } from "./custommarker";
|
||||
import { SpawnOptions } from "../controls/mapcontextmenu";
|
||||
import { DivIcon } from "leaflet";
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { getMarkerCategoryByName, getUnitDatabaseByCategory } from "../other/utils";
|
||||
|
||||
export class TemporaryUnitMarker extends CustomMarker {
|
||||
#spawnOptions: SpawnOptions;
|
||||
|
||||
constructor(spawnOptions: SpawnOptions) {
|
||||
super(spawnOptions.latlng, {interactive: false});
|
||||
this.#spawnOptions = spawnOptions;
|
||||
}
|
||||
|
||||
createIcon() {
|
||||
var icon = new Icon({
|
||||
iconUrl: '/resources/theme/images/markers/temporary-icon.png',
|
||||
iconSize: [52, 52],
|
||||
iconAnchor: [26, 26]
|
||||
const category = getMarkerCategoryByName(this.#spawnOptions.name);
|
||||
|
||||
/* Set the icon */
|
||||
var icon = new DivIcon({
|
||||
className: 'leaflet-unit-icon',
|
||||
iconAnchor: [25, 25],
|
||||
iconSize: [50, 50],
|
||||
});
|
||||
this.setIcon(icon);
|
||||
|
||||
var el = document.createElement("div");
|
||||
el.classList.add("unit");
|
||||
el.setAttribute("data-object", `unit-${category}`);
|
||||
el.setAttribute("data-coalition", this.#spawnOptions.coalition);
|
||||
|
||||
// Main icon
|
||||
var unitIcon = document.createElement("div");
|
||||
unitIcon.classList.add("unit-icon");
|
||||
var img = document.createElement("img");
|
||||
img.src = `/resources/theme/images/units/${category}.svg`;
|
||||
img.onload = () => SVGInjector(img);
|
||||
unitIcon.appendChild(img);
|
||||
unitIcon.toggleAttribute("data-rotate-to-heading", false);
|
||||
el.append(unitIcon);
|
||||
|
||||
// Short label
|
||||
if (category == "aircraft" || category == "helicopter") {
|
||||
var shortLabel = document.createElement("div");
|
||||
shortLabel.classList.add("unit-short-label");
|
||||
shortLabel.innerText = getUnitDatabaseByCategory(category)?.getByName(this.#spawnOptions.name)?.shortLabel || "";
|
||||
el.append(shortLabel);
|
||||
}
|
||||
|
||||
this.getElement()?.appendChild(el);
|
||||
this.getElement()?.classList.add("ol-temporary-marker");
|
||||
}
|
||||
}
|
||||
@ -3,50 +3,41 @@ import { getInfoPopup, getMap } from "..";
|
||||
import { Airbase } from "./airbase";
|
||||
import { Bullseye } from "./bullseye";
|
||||
|
||||
export class MissionHandler
|
||||
{
|
||||
#bullseyes : {[name: string]: Bullseye} = {};
|
||||
#airbases : {[name: string]: Airbase} = {};
|
||||
#theatre : string = "";
|
||||
export class MissionHandler {
|
||||
#bullseyes: { [name: string]: Bullseye } = {};
|
||||
#airbases: { [name: string]: Airbase } = {};
|
||||
#theatre: string = "";
|
||||
|
||||
#airbaseData : { [name: string]: object } = {};
|
||||
#airbaseData: { [name: string]: object } = {};
|
||||
|
||||
// Time
|
||||
#date : any;
|
||||
#elapsedTime : any;
|
||||
#startTime : any;
|
||||
#time : any;
|
||||
#date: any;
|
||||
#elapsedTime: any;
|
||||
#startTime: any;
|
||||
#time: any;
|
||||
|
||||
#updateTime : any;
|
||||
#updateTime: any;
|
||||
|
||||
constructor()
|
||||
{
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
update(data: BullseyesData | AirbasesData | any)
|
||||
{
|
||||
if ("bullseyes" in data)
|
||||
{
|
||||
for (let idx in data.bullseyes)
|
||||
{
|
||||
update(data: BullseyesData | AirbasesData | any) {
|
||||
if ("bullseyes" in data) {
|
||||
for (let idx in data.bullseyes) {
|
||||
const bullseye = data.bullseyes[idx];
|
||||
if (!(idx in this.#bullseyes))
|
||||
this.#bullseyes[idx] = new Bullseye([0, 0]).addTo(getMap());
|
||||
|
||||
if (bullseye.latitude && bullseye.longitude && bullseye.coalition)
|
||||
{
|
||||
this.#bullseyes[idx].setLatLng(new LatLng(bullseye.latitude, bullseye.longitude));
|
||||
|
||||
if (bullseye.latitude && bullseye.longitude && bullseye.coalition) {
|
||||
this.#bullseyes[idx].setLatLng(new LatLng(bullseye.latitude, bullseye.longitude));
|
||||
this.#bullseyes[idx].setCoalition(bullseye.coalition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ("mission" in data)
|
||||
{
|
||||
if (data.mission != null && data.mission.theatre != this.#theatre)
|
||||
{
|
||||
if ("mission" in data) {
|
||||
if (data.mission != null && data.mission.theatre != this.#theatre) {
|
||||
this.#theatre = data.mission.theatre;
|
||||
getMap().setTheatre(this.#theatre);
|
||||
|
||||
@ -54,31 +45,18 @@ export class MissionHandler
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ("airbases" in data)
|
||||
{
|
||||
/*
|
||||
console.log( Object.values( data.airbases ).sort( ( a:any, b:any ) => {
|
||||
const aVal = a.callsign.toLowerCase();
|
||||
const bVal = b.callsign.toLowerCase();
|
||||
|
||||
return aVal > bVal ? 1 : -1;
|
||||
}) );
|
||||
//*/
|
||||
for (let idx in data.airbases)
|
||||
{
|
||||
if ("airbases" in data) {
|
||||
for (let idx in data.airbases) {
|
||||
var airbase = data.airbases[idx]
|
||||
if (this.#airbases[idx] === undefined && airbase.callsign != '')
|
||||
{
|
||||
if (this.#airbases[idx] === undefined && airbase.callsign != '') {
|
||||
this.#airbases[idx] = new Airbase({
|
||||
position: new LatLng(airbase.latitude, airbase.longitude),
|
||||
position: new LatLng(airbase.latitude, airbase.longitude),
|
||||
name: airbase.callsign
|
||||
}).addTo(getMap());
|
||||
this.#airbases[idx].on('contextmenu', (e) => this.#onAirbaseClick(e));
|
||||
}
|
||||
|
||||
if (this.#airbases[idx] != undefined && airbase.latitude && airbase.longitude && airbase.coalition)
|
||||
{
|
||||
if (this.#airbases[idx] != undefined && airbase.latitude && airbase.longitude && airbase.coalition) {
|
||||
this.#airbases[idx].setLatLng(new LatLng(airbase.latitude, airbase.longitude));
|
||||
this.#airbases[idx].setCoalition(airbase.coalition);
|
||||
}
|
||||
@ -87,83 +65,68 @@ export class MissionHandler
|
||||
}
|
||||
}
|
||||
|
||||
if ("mission" in data && data.mission != null)
|
||||
{
|
||||
if (data.mission != null && data.mission.theatre != this.#theatre)
|
||||
{
|
||||
if ("mission" in data && data.mission != null) {
|
||||
if (data.mission != null && data.mission.theatre != this.#theatre) {
|
||||
this.#theatre = data.mission.theatre;
|
||||
getMap().setTheatre(this.#theatre);
|
||||
|
||||
getInfoPopup().setText("Map set to " + this.#theatre);
|
||||
}
|
||||
|
||||
if ( "date" in data.mission ) {
|
||||
if ("date" in data.mission)
|
||||
this.#date = data.mission.date;
|
||||
}
|
||||
|
||||
if ( "elapsedTime" in data.mission ) {
|
||||
if ("elapsedTime" in data.mission)
|
||||
this.#elapsedTime = data.mission.elapsedTime;
|
||||
}
|
||||
|
||||
if ( "startTime" in data.mission ) {
|
||||
if ("startTime" in data.mission)
|
||||
this.#startTime = data.mission.startTime;
|
||||
}
|
||||
|
||||
if ( "time" in data.mission ) {
|
||||
if ("time" in data.mission)
|
||||
this.#time = data.mission.time;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if ( "time" in data ) {
|
||||
if ("time" in data)
|
||||
this.#updateTime = data.time;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
getBullseyes()
|
||||
{
|
||||
getBullseyes() {
|
||||
return this.#bullseyes;
|
||||
}
|
||||
|
||||
getAirbases() {
|
||||
return this.#airbases;
|
||||
}
|
||||
|
||||
getDate() {
|
||||
return this.#date;
|
||||
}
|
||||
|
||||
|
||||
getNowDate() {
|
||||
|
||||
const date = this.getDate();
|
||||
const time = this.getTime();
|
||||
|
||||
if ( !date ) {
|
||||
if (!date) {
|
||||
return new Date();
|
||||
}
|
||||
|
||||
let year = date.Year;
|
||||
|
||||
let year = date.Year;
|
||||
let month = date.Month - 1;
|
||||
|
||||
if ( month < 0 ) {
|
||||
if (month < 0) {
|
||||
month = 11;
|
||||
year--;
|
||||
}
|
||||
|
||||
return new Date( year, month, date.Day, time.h, time.m, time.s );
|
||||
return new Date(year, month, date.Day, time.h, time.m, time.s);
|
||||
}
|
||||
|
||||
|
||||
getTime() {
|
||||
return this.#time;
|
||||
}
|
||||
|
||||
|
||||
getUpdateTime() {
|
||||
return this.#updateTime;
|
||||
}
|
||||
|
||||
#onAirbaseClick(e: any)
|
||||
{
|
||||
#onAirbaseClick(e: any) {
|
||||
getMap().showAirbaseContextMenu(e, e.sourceTarget);
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,12 @@
|
||||
import { LatLng, Point, Polygon } from "leaflet";
|
||||
import * as turf from "@turf/turf";
|
||||
import { UnitDatabase } from "../units/unitdatabase";
|
||||
import { aircraftDatabase } from "../units/aircraftdatabase";
|
||||
import { helicopterDatabase } from "../units/helicopterdatabase";
|
||||
import { groundUnitsDatabase } from "../units/groundunitsdatabase";
|
||||
import { Buffer } from "buffer";
|
||||
import { ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants";
|
||||
|
||||
export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) {
|
||||
const φ1 = deg2rad(lat1); // φ, λ in radians
|
||||
const φ2 = deg2rad(lat2);
|
||||
@ -184,4 +193,101 @@ export function mToNm(m: number) {
|
||||
|
||||
export function nmToFt(nm: number) {
|
||||
return nm * 6076.12;
|
||||
}
|
||||
|
||||
export function randomPointInPoly(polygon: Polygon): LatLng {
|
||||
var bounds = polygon.getBounds();
|
||||
var x_min = bounds.getEast();
|
||||
var x_max = bounds.getWest();
|
||||
var y_min = bounds.getSouth();
|
||||
var y_max = bounds.getNorth();
|
||||
|
||||
var lat = y_min + (Math.random() * (y_max - y_min));
|
||||
var lng = x_min + (Math.random() * (x_max - x_min));
|
||||
|
||||
var poly = polygon.toGeoJSON();
|
||||
var inside = turf.inside(turf.point([lng, lat]), poly);
|
||||
|
||||
if (inside) {
|
||||
return new LatLng(lat, lng);
|
||||
} else {
|
||||
return randomPointInPoly(polygon);
|
||||
}
|
||||
}
|
||||
|
||||
export function polygonArea(polygon: Polygon) {
|
||||
var poly = polygon.toGeoJSON();
|
||||
return turf.area(poly);
|
||||
}
|
||||
|
||||
export function randomUnitBlueprintByRole(unitDatabse: UnitDatabase, role: string) {
|
||||
const unitBlueprints = unitDatabse.getByRole(role);
|
||||
var index = Math.floor(Math.random() * unitBlueprints.length);
|
||||
return unitBlueprints[index];
|
||||
}
|
||||
|
||||
export function getMarkerCategoryByName(name: string) {
|
||||
if (aircraftDatabase.getByName(name) != null)
|
||||
return "aircraft";
|
||||
else if (helicopterDatabase.getByName(name) != null)
|
||||
return "helicopter";
|
||||
else if (groundUnitsDatabase.getByName(name) != null){
|
||||
// TODO this is very messy
|
||||
var role = groundUnitsDatabase.getByName(name)?.loadouts[0].roles[0];
|
||||
return (role?.includes("SAM")) ? "groundunit-sam" : "groundunit-other";
|
||||
}
|
||||
else
|
||||
return ""; // TODO add other unit types
|
||||
}
|
||||
|
||||
export function getUnitDatabaseByCategory(category: string) {
|
||||
if (category == "aircraft")
|
||||
return aircraftDatabase;
|
||||
else if (category == "helicopter")
|
||||
return helicopterDatabase;
|
||||
else if (category.includes("groundunit"))
|
||||
return groundUnitsDatabase;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
export function base64ToBytes(base64: string) {
|
||||
return Buffer.from(base64, 'base64').buffer;
|
||||
}
|
||||
|
||||
export function enumToState(state: number) {
|
||||
if (state < states.length)
|
||||
return states[state];
|
||||
else
|
||||
return states[0];
|
||||
}
|
||||
|
||||
export function enumToROE(ROE: number) {
|
||||
if (ROE < ROEs.length)
|
||||
return ROEs[ROE];
|
||||
else
|
||||
return ROEs[0];
|
||||
}
|
||||
|
||||
export function enumToReactionToThreat(reactionToThreat: number) {
|
||||
if (reactionToThreat < reactionsToThreat.length)
|
||||
return reactionsToThreat[reactionToThreat];
|
||||
else
|
||||
return reactionsToThreat[0];
|
||||
}
|
||||
|
||||
export function enumToEmissioNCountermeasure(emissionCountermeasure: number) {
|
||||
if (emissionCountermeasure < emissionsCountermeasures.length)
|
||||
return emissionsCountermeasures[emissionCountermeasure];
|
||||
else
|
||||
return emissionsCountermeasures[0];
|
||||
}
|
||||
|
||||
export function enumToCoalition(coalitionID: number) {
|
||||
switch (coalitionID){
|
||||
case 0: return "neutral";
|
||||
case 1: return "red";
|
||||
case 2: return "blue";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@ -36,7 +36,7 @@ export class MouseInfoPanel extends Panel {
|
||||
var selectedUnitPosition = null;
|
||||
var selectedUnits = getUnitsManager().getSelectedUnits();
|
||||
if (selectedUnits && selectedUnits.length == 1)
|
||||
selectedUnitPosition = new LatLng(selectedUnits[0].getFlightData().latitude, selectedUnits[0].getFlightData().longitude);
|
||||
selectedUnitPosition = new LatLng(selectedUnits[0].getPosition().lat, selectedUnits[0].getPosition().lng);
|
||||
|
||||
/* Draw measures from selected unit, from pin location, and from bullseyes */
|
||||
this.#drawMeasure("ref-measure-position", "measure-position", this.#measurePoint, mousePosition);
|
||||
|
||||
@ -8,6 +8,7 @@ import { Panel } from "./panel";
|
||||
import { Switch } from "../controls/switch";
|
||||
import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, emissionsCountermeasuresDescriptions, maxAltitudeValues, maxSpeedValues, minAltitudeValues, minSpeedValues, reactionsToThreat, reactionsToThreatDescriptions, speedIncrements } from "../constants/constants";
|
||||
import { ftToM, knotsToMs, mToFt, msToKnots } from "../other/utils";
|
||||
import { GeneralSettings, Radio, TACAN } from "../@types/unit";
|
||||
|
||||
export class UnitControlPanel extends Panel {
|
||||
#altitudeSlider: Slider;
|
||||
@ -33,7 +34,8 @@ export class UnitControlPanel extends Panel {
|
||||
this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetSpeedType(value? "GS": "CAS"); });
|
||||
|
||||
/* Option buttons */
|
||||
this.#optionButtons["ROE"] = ROEs.map((option: string, index: number) => {
|
||||
// Reversing the ROEs so that the least "aggressive" option is always on the left
|
||||
this.#optionButtons["ROE"] = ROEs.slice(0).reverse().map((option: string, index: number) => {
|
||||
return this.#createOptionButton(option, `roe/${option.toLowerCase()}.svg`, ROEDescriptions[index], () => { getUnitsManager().selectedUnitsSetROE(option); });
|
||||
});
|
||||
|
||||
@ -98,13 +100,13 @@ export class UnitControlPanel extends Panel {
|
||||
if (units.length < 20) {
|
||||
this.getElement().querySelector("#selected-units-container")?.replaceChildren(...units.map((unit: Unit, index: number) => {
|
||||
var button = document.createElement("button");
|
||||
var callsign = unit.getBaseData().unitName || "";
|
||||
var label = unit.getDatabase()?.getByName(unit.getBaseData().name)?.label || unit.getBaseData().name;
|
||||
var callsign = unit.getUnitName() || "";
|
||||
var label = unit.getDatabase()?.getByName(unit.getName())?.label || unit.getName();
|
||||
|
||||
button.setAttribute("data-label", label);
|
||||
button.setAttribute("data-callsign", callsign);
|
||||
|
||||
button.setAttribute("data-coalition", unit.getMissionData().coalition);
|
||||
button.setAttribute("data-coalition", unit.getCoalition());
|
||||
button.classList.add("pill", "highlight-coalition")
|
||||
|
||||
button.addEventListener("click", () => {
|
||||
@ -139,12 +141,12 @@ export class UnitControlPanel extends Panel {
|
||||
element.toggleAttribute("data-show-advanced-settings-button", units.length == 1);
|
||||
|
||||
/* Flight controls */
|
||||
var desiredAltitude: number | undefined = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredAltitude});
|
||||
var desiredAltitudeType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredAltitudeType});
|
||||
var desiredSpeed = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredSpeed});
|
||||
var desiredSpeedType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredSpeedType});
|
||||
var onOff = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().onOff});
|
||||
var followRoads = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().followRoads});
|
||||
var desiredAltitude: number | undefined = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitude()});
|
||||
var desiredAltitudeType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitudeType()});
|
||||
var desiredSpeed = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeed()});
|
||||
var desiredSpeedType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeedType()});
|
||||
var onOff = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getOnOff()});
|
||||
var followRoads = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getFollowRoads()});
|
||||
|
||||
if (selectedUnitsTypes.length == 1) {
|
||||
this.#altitudeTypeSwitch.setValue(desiredAltitudeType != undefined? desiredAltitudeType == "AGL": undefined, false);
|
||||
@ -170,15 +172,15 @@ export class UnitControlPanel extends Panel {
|
||||
|
||||
/* Option buttons */
|
||||
this.#optionButtons["ROE"].forEach((button: HTMLButtonElement) => {
|
||||
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().ROE === button.value))
|
||||
button.classList.toggle("selected", units.every((unit: Unit) => unit.getROE() === button.value))
|
||||
});
|
||||
|
||||
this.#optionButtons["reactionToThreat"].forEach((button: HTMLButtonElement) => {
|
||||
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().reactionToThreat === button.value))
|
||||
button.classList.toggle("selected", units.every((unit: Unit) => unit.getReactionToThreat() === button.value))
|
||||
});
|
||||
|
||||
this.#optionButtons["emissionsCountermeasures"].forEach((button: HTMLButtonElement) => {
|
||||
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().emissionsCountermeasures === button.value))
|
||||
button.classList.toggle("selected", units.every((unit: Unit) => unit.getEmissionsCountermeasures() === button.value))
|
||||
});
|
||||
|
||||
this.#onOffSwitch.setValue(onOff, false);
|
||||
@ -207,11 +209,11 @@ export class UnitControlPanel extends Panel {
|
||||
const radioCallsignNumberInput = this.#advancedSettingsDialog.querySelector("#radio-callsign-number")?.querySelector("input") as HTMLInputElement;
|
||||
|
||||
const unit = units[0];
|
||||
const roles = aircraftDatabase.getByName(unit.getBaseData().name)?.loadouts.map((loadout) => {return loadout.roles})
|
||||
const roles = aircraftDatabase.getByName(unit.getName())?.loadouts.map((loadout) => {return loadout.roles})
|
||||
const tanker = roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Tanker");
|
||||
const AWACS = roles != undefined && Array.prototype.concat.apply([], roles)?.includes("AWACS");
|
||||
const radioMHz = Math.floor(unit.getOptionsData().radio.frequency / 1000000);
|
||||
const radioDecimals = (unit.getOptionsData().radio.frequency / 1000000 - radioMHz) * 1000;
|
||||
const radioMHz = Math.floor(unit.getRadio().frequency / 1000000);
|
||||
const radioDecimals = (unit.getRadio().frequency / 1000000 - radioMHz) * 1000;
|
||||
|
||||
/* Activate the correct options depending on unit type */
|
||||
this.#advancedSettingsDialog.toggleAttribute("data-show-settings", !tanker && !AWACS);
|
||||
@ -223,28 +225,28 @@ export class UnitControlPanel extends Panel {
|
||||
|
||||
/* Set common properties */
|
||||
// Name
|
||||
unitNameEl.innerText = unit.getBaseData().unitName;
|
||||
unitNameEl.innerText = unit.getUnitName();
|
||||
|
||||
// General settings
|
||||
prohibitJettisonCheckbox.checked = unit.getOptionsData().generalSettings.prohibitJettison;
|
||||
prohibitAfterburnerCheckbox.checked = unit.getOptionsData().generalSettings.prohibitAfterburner;
|
||||
prohibitAACheckbox.checked = unit.getOptionsData().generalSettings.prohibitAA;
|
||||
prohibitAGCheckbox.checked = unit.getOptionsData().generalSettings.prohibitAG;
|
||||
prohibitAirWpnCheckbox.checked = unit.getOptionsData().generalSettings.prohibitAirWpn;
|
||||
prohibitJettisonCheckbox.checked = unit.getGeneralSettings().prohibitJettison;
|
||||
prohibitAfterburnerCheckbox.checked = unit.getGeneralSettings().prohibitAfterburner;
|
||||
prohibitAACheckbox.checked = unit.getGeneralSettings().prohibitAA;
|
||||
prohibitAGCheckbox.checked = unit.getGeneralSettings().prohibitAG;
|
||||
prohibitAirWpnCheckbox.checked = unit.getGeneralSettings().prohibitAirWpn;
|
||||
|
||||
// Tasking
|
||||
tankerCheckbox.checked = unit.getTaskData().isTanker;
|
||||
AWACSCheckbox.checked = unit.getTaskData().isAWACS;
|
||||
tankerCheckbox.checked = unit.getIsTanker();
|
||||
AWACSCheckbox.checked = unit.getIsAWACS();
|
||||
|
||||
// TACAN
|
||||
TACANCheckbox.checked = unit.getOptionsData().TACAN.isOn;
|
||||
TACANChannelInput.value = String(unit.getOptionsData().TACAN.channel);
|
||||
TACANCallsignInput.value = String(unit.getOptionsData().TACAN.callsign);
|
||||
this.#TACANXYDropdown.setValue(unit.getOptionsData().TACAN.XY);
|
||||
TACANCheckbox.checked = unit.getTACAN().isOn;
|
||||
TACANChannelInput.value = String(unit.getTACAN().channel);
|
||||
TACANCallsignInput.value = String(unit.getTACAN().callsign);
|
||||
this.#TACANXYDropdown.setValue(unit.getTACAN().XY);
|
||||
|
||||
// Radio
|
||||
radioMhzInput.value = String(radioMHz);
|
||||
radioCallsignNumberInput.value = String(unit.getOptionsData().radio.callsignNumber);
|
||||
radioCallsignNumberInput.value = String(unit.getRadio().callsignNumber);
|
||||
this.#radioDecimalsDropdown.setValue("." + radioDecimals);
|
||||
|
||||
if (tanker) /* Set tanker specific options */
|
||||
@ -255,7 +257,7 @@ export class UnitControlPanel extends Panel {
|
||||
this.#radioCallsignDropdown.setOptions(["Enfield", "Springfield", "Uzi", "Colt", "Dodge", "Ford", "Chevy", "Pontiac"]);
|
||||
|
||||
// This must be done after setting the options
|
||||
if (!this.#radioCallsignDropdown.selectValue(unit.getOptionsData().radio.callsign - 1)) // Ensure the selected value is in the acceptable range
|
||||
if (!this.#radioCallsignDropdown.selectValue(unit.getRadio().callsign - 1)) // Ensure the selected value is in the acceptable range
|
||||
this.#radioCallsignDropdown.selectValue(0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { getUnitsManager } from "..";
|
||||
import { Ammo } from "../@types/unit";
|
||||
import { ConvertDDToDMS, rad2deg } from "../other/utils";
|
||||
import { aircraftDatabase } from "../units/aircraftdatabase";
|
||||
import { Unit } from "../units/unit";
|
||||
@ -51,21 +52,21 @@ export class UnitInfoPanel extends Panel {
|
||||
#onUnitUpdate(unit: Unit) {
|
||||
if (this.getElement() != null && this.getVisible() && unit.getSelected()) {
|
||||
|
||||
const baseData = unit.getBaseData();
|
||||
const baseData = unit.getData();
|
||||
|
||||
/* Set the unit info */
|
||||
this.#unitLabel.innerText = aircraftDatabase.getByName(baseData.name)?.label || baseData.name;
|
||||
this.#unitName.innerText = baseData.unitName;
|
||||
if (unit.getMissionData().flags.Human)
|
||||
if (unit.getHuman())
|
||||
this.#unitControl.innerText = "Human";
|
||||
else if (baseData.controlled)
|
||||
this.#unitControl.innerText = "Olympus controlled";
|
||||
else
|
||||
this.#unitControl.innerText = "DCS Controlled";
|
||||
this.#fuelBar.style.width = String(unit.getMissionData().fuel + "%");
|
||||
this.#fuelPercentage.dataset.percentage = "" + unit.getMissionData().fuel;
|
||||
this.#currentTask.dataset.currentTask = unit.getTaskData().currentTask !== "" ? unit.getTaskData().currentTask : "No task";
|
||||
this.#currentTask.dataset.coalition = unit.getMissionData().coalition;
|
||||
this.#fuelBar.style.width = String(unit.getFuel() + "%");
|
||||
this.#fuelPercentage.dataset.percentage = "" + unit.getFuel();
|
||||
this.#currentTask.dataset.currentTask = unit.getTask() !== "" ? unit.getTask() : "No task";
|
||||
this.#currentTask.dataset.coalition = unit.getCoalition();
|
||||
|
||||
this.#silhouette.src = `/images/units/${unit.getDatabase()?.getByName(baseData.name)?.filename}`;
|
||||
this.#silhouette.classList.toggle("hide", unit.getDatabase()?.getByName(baseData.name)?.filename == undefined || unit.getDatabase()?.getByName(baseData.name)?.filename == '');
|
||||
@ -74,13 +75,13 @@ export class UnitInfoPanel extends Panel {
|
||||
const items = <HTMLElement>this.#loadoutContainer.querySelector("#loadout-items");
|
||||
|
||||
if (items) {
|
||||
const ammo = Object.values(unit.getMissionData().ammo);
|
||||
const ammo = Object.values(unit.getAmmo());
|
||||
if (ammo.length > 0) {
|
||||
items.replaceChildren(...Object.values(unit.getMissionData().ammo).map(
|
||||
(ammo: any) => {
|
||||
items.replaceChildren(...Object.values(unit.getAmmo()).map(
|
||||
(ammo: Ammo) => {
|
||||
var el = document.createElement("div");
|
||||
el.dataset.qty = ammo.count;
|
||||
el.dataset.item = ammo.desc.displayName;
|
||||
el.dataset.qty = `${ammo.quantity}`;
|
||||
el.dataset.item = ammo.name;
|
||||
return el;
|
||||
}
|
||||
));
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { LatLng } from 'leaflet';
|
||||
import { getConnectionStatusPanel, getInfoPopup, getMissionData, getUnitDataTable, getUnitsManager, setConnectionStatus } from '..';
|
||||
import { SpawnOptions } from '../controls/mapcontextmenu';
|
||||
import { GeneralSettings, Radio, TACAN } from '../@types/unit';
|
||||
import { ROEs, emissionsCountermeasures, reactionsToThreat } from '../constants/constants';
|
||||
|
||||
var connected: boolean = false;
|
||||
var paused: boolean = false;
|
||||
@ -37,19 +39,21 @@ export function GET(callback: CallableFunction, uri: string, options?: { time?:
|
||||
if (options?.time != undefined)
|
||||
optionsString = `time=${options.time}`;
|
||||
|
||||
|
||||
xmlHttp.open("GET", `${demoEnabled ? DEMO_ADDRESS : REST_ADDRESS}/${uri}${optionsString ? `?${optionsString}` : ''}`, true);
|
||||
if (credentials)
|
||||
xmlHttp.setRequestHeader("Authorization", "Basic " + credentials);
|
||||
|
||||
if (uri === UNITS_URI)
|
||||
xmlHttp.responseType = "arraybuffer";
|
||||
|
||||
xmlHttp.onload = function (e) {
|
||||
if (xmlHttp.status == 200) {
|
||||
var data = JSON.parse(xmlHttp.responseText);
|
||||
if (uri !== UNITS_URI || (options?.time == 0) || parseInt(data.time) > lastUpdateTime) {
|
||||
setConnected(true);
|
||||
if (xmlHttp.responseType == 'arraybuffer')
|
||||
callback(xmlHttp.response);
|
||||
else {
|
||||
var data = JSON.parse(xmlHttp.responseText);
|
||||
callback(data);
|
||||
lastUpdateTime = parseInt(data.time);
|
||||
if (isNaN(lastUpdateTime))
|
||||
lastUpdateTime = 0;
|
||||
setConnected(true);
|
||||
}
|
||||
} else if (xmlHttp.status == 401) {
|
||||
console.error("Incorrect username/password");
|
||||
@ -95,6 +99,10 @@ export function setAddress(address: string, port: number) {
|
||||
console.log(`Setting REST address to ${REST_ADDRESS}`)
|
||||
}
|
||||
|
||||
export function setLastUpdateTime(newLastUpdateTime: number) {
|
||||
lastUpdateTime = newLastUpdateTime;
|
||||
}
|
||||
|
||||
export function getAirbases(callback: CallableFunction) {
|
||||
GET(callback, AIRBASES_URI);
|
||||
}
|
||||
@ -134,13 +142,13 @@ export function spawnExplosion(intensity: number, latlng: LatLng) {
|
||||
}
|
||||
|
||||
export function spawnGroundUnit(spawnOptions: SpawnOptions) {
|
||||
var command = { "type": spawnOptions.type, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition };
|
||||
var command = { "type": spawnOptions.name, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "immediate": spawnOptions.immediate? true: false };
|
||||
var data = { "spawnGround": command }
|
||||
POST(data, () => { });
|
||||
}
|
||||
|
||||
export function spawnAircraft(spawnOptions: SpawnOptions) {
|
||||
var command = { "type": spawnOptions.type, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "altitude": spawnOptions.altitude, "payloadName": spawnOptions.loadout != null ? spawnOptions.loadout : "", "airbaseName": spawnOptions.airbaseName != null ? spawnOptions.airbaseName : "" };
|
||||
var command = { "type": spawnOptions.name, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "altitude": spawnOptions.altitude, "payloadName": spawnOptions.loadout != null ? spawnOptions.loadout : "", "airbaseName": spawnOptions.airbaseName != null ? spawnOptions.airbaseName : "", "immediate": spawnOptions.immediate? true: false };
|
||||
var data = { "spawnAir": command }
|
||||
POST(data, () => { });
|
||||
}
|
||||
@ -222,19 +230,19 @@ export function createFormation(ID: number, isLeader: boolean, wingmenIDs: numbe
|
||||
}
|
||||
|
||||
export function setROE(ID: number, ROE: string) {
|
||||
var command = { "ID": ID, "ROE": ROE }
|
||||
var command = { "ID": ID, "ROE": ROEs.indexOf(ROE) }
|
||||
var data = { "setROE": command }
|
||||
POST(data, () => { });
|
||||
}
|
||||
|
||||
export function setReactionToThreat(ID: number, reactionToThreat: string) {
|
||||
var command = { "ID": ID, "reactionToThreat": reactionToThreat }
|
||||
var command = { "ID": ID, "reactionToThreat": reactionsToThreat.indexOf(reactionToThreat) }
|
||||
var data = { "setReactionToThreat": command }
|
||||
POST(data, () => { });
|
||||
}
|
||||
|
||||
export function setEmissionsCountermeasures(ID: number, emissionCountermeasure: string) {
|
||||
var command = { "ID": ID, "emissionsCountermeasures": emissionCountermeasure }
|
||||
var command = { "ID": ID, "emissionsCountermeasures": emissionsCountermeasures.indexOf(emissionCountermeasure) }
|
||||
var data = { "setEmissionsCountermeasures": command }
|
||||
POST(data, () => { });
|
||||
}
|
||||
@ -299,8 +307,11 @@ export function startUpdate() {
|
||||
/* On the first connection, force request of full data */
|
||||
getAirbases((data: AirbasesData) => getMissionData()?.update(data));
|
||||
getBullseye((data: BullseyesData) => getMissionData()?.update(data));
|
||||
getMission((data: any) => { getMissionData()?.update(data) });
|
||||
getUnits((data: UnitsData) => getUnitsManager()?.update(data), true /* Does a full refresh */);
|
||||
getMission((data: any) => {
|
||||
getMissionData()?.update(data);
|
||||
checkSessionHash(data.sessionHash);
|
||||
});
|
||||
getUnits((buffer: ArrayBuffer) => getUnitsManager()?.update(buffer), true /* Does a full refresh */);
|
||||
|
||||
requestUpdate();
|
||||
requestRefresh();
|
||||
@ -308,10 +319,9 @@ export function startUpdate() {
|
||||
|
||||
export function requestUpdate() {
|
||||
/* Main update rate = 250ms is minimum time, equal to server update time. */
|
||||
getUnits((data: UnitsData) => {
|
||||
getUnits((buffer: ArrayBuffer) => {
|
||||
if (!getPaused()) {
|
||||
getUnitsManager()?.update(data);
|
||||
checkSessionHash(data.sessionHash);
|
||||
getUnitsManager()?.update(buffer);
|
||||
}
|
||||
}, false);
|
||||
window.setTimeout(() => requestUpdate(), getConnected() ? 250 : 1000);
|
||||
@ -321,22 +331,19 @@ export function requestUpdate() {
|
||||
|
||||
export function requestRefresh() {
|
||||
/* Main refresh rate = 5000ms. */
|
||||
getUnits((data: UnitsData) => {
|
||||
if (!getPaused()) {
|
||||
getUnitsManager()?.update(data);
|
||||
getAirbases((data: AirbasesData) => getMissionData()?.update(data));
|
||||
getBullseye((data: BullseyesData) => getMissionData()?.update(data));
|
||||
getMission((data: any) => {
|
||||
getMissionData()?.update(data)
|
||||
});
|
||||
|
||||
// Update the list of existing units
|
||||
getUnitDataTable()?.update();
|
||||
|
||||
|
||||
if (!getPaused()) {
|
||||
getAirbases((data: AirbasesData) => getMissionData()?.update(data));
|
||||
getBullseye((data: BullseyesData) => getMissionData()?.update(data));
|
||||
getMission((data: any) => {
|
||||
checkSessionHash(data.sessionHash);
|
||||
}
|
||||
}, true);
|
||||
window.setTimeout(() => requestRefresh(), 5000);
|
||||
getMissionData()?.update(data)
|
||||
});
|
||||
|
||||
// Update the list of existing units
|
||||
getUnitDataTable()?.update();
|
||||
}
|
||||
window.setTimeout(() => requestRefresh(), 1000);
|
||||
}
|
||||
|
||||
export function checkSessionHash(newSessionHash: string) {
|
||||
|
||||
150
client/src/units/dataextractor.ts
Normal file
@ -0,0 +1,150 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN } from "../@types/unit";
|
||||
|
||||
export class DataExtractor {
|
||||
#seekPosition = 0;
|
||||
#dataview: DataView;
|
||||
#decoder: TextDecoder;
|
||||
#buffer: ArrayBuffer;
|
||||
|
||||
constructor(buffer: ArrayBuffer) {
|
||||
this.#buffer = buffer;
|
||||
this.#dataview = new DataView(this.#buffer);
|
||||
this.#decoder = new TextDecoder("utf-8");
|
||||
}
|
||||
|
||||
getSeekPosition() {
|
||||
return this.#seekPosition;
|
||||
}
|
||||
|
||||
extractBool() {
|
||||
const value = this.#dataview.getUint8(this.#seekPosition);
|
||||
this.#seekPosition += 1;
|
||||
return value > 0;
|
||||
}
|
||||
|
||||
extractUInt8() {
|
||||
const value = this.#dataview.getUint8(this.#seekPosition);
|
||||
this.#seekPosition += 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
extractUInt16() {
|
||||
const value = this.#dataview.getUint16(this.#seekPosition, true);
|
||||
this.#seekPosition += 2;
|
||||
return value;
|
||||
}
|
||||
|
||||
extractUInt32() {
|
||||
const value = this.#dataview.getUint32(this.#seekPosition, true);
|
||||
this.#seekPosition += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
extractUInt64() {
|
||||
const value = this.#dataview.getBigUint64(this.#seekPosition, true);
|
||||
this.#seekPosition += 8;
|
||||
return value;
|
||||
}
|
||||
|
||||
extractFloat64() {
|
||||
const value = this.#dataview.getFloat64(this.#seekPosition, true);
|
||||
this.#seekPosition += 8;
|
||||
return value;
|
||||
}
|
||||
|
||||
extractLatLng() {
|
||||
return new LatLng(this.extractFloat64(), this.extractFloat64(), this.extractFloat64())
|
||||
}
|
||||
|
||||
extractFromBitmask(bitmask: number, position: number) {
|
||||
return ((bitmask >> position) & 1) > 0;
|
||||
}
|
||||
|
||||
extractString(length?: number) {
|
||||
if (length === undefined)
|
||||
length = this.extractUInt16()
|
||||
const value = this.#decoder.decode(this.#buffer.slice(this.#seekPosition, this.#seekPosition + length));
|
||||
this.#seekPosition += length;
|
||||
return value;
|
||||
}
|
||||
|
||||
extractChar() {
|
||||
return this.extractString(1);
|
||||
}
|
||||
|
||||
extractTACAN() {
|
||||
const value: TACAN = {
|
||||
isOn: this.extractBool(),
|
||||
channel: this.extractUInt8(),
|
||||
XY: this.extractChar(),
|
||||
callsign: this.extractString(4)
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
extractRadio() {
|
||||
const value: Radio = {
|
||||
frequency: this.extractUInt32(),
|
||||
callsign: this.extractUInt8(),
|
||||
callsignNumber: this.extractUInt8()
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
extractGeneralSettings() {
|
||||
const value: GeneralSettings = {
|
||||
prohibitJettison: this.extractBool(),
|
||||
prohibitAA: this.extractBool(),
|
||||
prohibitAG: this.extractBool(),
|
||||
prohibitAfterburner: this.extractBool(),
|
||||
prohibitAirWpn: this.extractBool(),
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
extractAmmo() {
|
||||
const value: Ammo[] = [];
|
||||
const size = this.extractUInt16();
|
||||
for (let idx = 0; idx < size; idx++) {
|
||||
value.push({
|
||||
quantity: this.extractUInt16(),
|
||||
name: this.extractString(33),
|
||||
guidance: this.extractUInt8(),
|
||||
category: this.extractUInt8(),
|
||||
missileCategory: this.extractUInt8()
|
||||
});
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
extractContacts(){
|
||||
const value: Contact[] = [];
|
||||
const size = this.extractUInt16();
|
||||
for (let idx = 0; idx < size; idx++) {
|
||||
value.push({
|
||||
ID: this.extractUInt32(),
|
||||
detectionMethod: this.extractUInt8()
|
||||
});
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
extractActivePath() {
|
||||
const value: LatLng[] = [];
|
||||
const size = this.extractUInt16();
|
||||
for (let idx = 0; idx < size; idx++) {
|
||||
value.push(this.extractLatLng());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
extractOffset() {
|
||||
const value: Offset = {
|
||||
x: this.extractFloat64(),
|
||||
y: this.extractFloat64(),
|
||||
z: this.extractFloat64(),
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@ -16,7 +16,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
|
||||
"items": [
|
||||
],
|
||||
"roles": [
|
||||
"Template"
|
||||
"SAM Sites"
|
||||
],
|
||||
"code": "",
|
||||
"name": "Default"
|
||||
@ -35,7 +35,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
|
||||
"fuel": 1,
|
||||
"items": [],
|
||||
"roles": [
|
||||
"Template"
|
||||
"SAM Sites"
|
||||
],
|
||||
"code": "",
|
||||
"name": "Default"
|
||||
@ -54,7 +54,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
|
||||
"fuel": 1,
|
||||
"items": [],
|
||||
"roles": [
|
||||
"Template"
|
||||
"SAM Sites"
|
||||
],
|
||||
"code": "",
|
||||
"name": "Default"
|
||||
@ -73,7 +73,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
|
||||
"fuel": 1,
|
||||
"items": [],
|
||||
"roles": [
|
||||
"Template"
|
||||
"SAM Sites"
|
||||
],
|
||||
"code": "",
|
||||
"name": "Default"
|
||||
@ -92,7 +92,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
|
||||
"fuel": 1,
|
||||
"items": [],
|
||||
"roles": [
|
||||
"Template"
|
||||
"SAM Sites"
|
||||
],
|
||||
"code": "",
|
||||
"name": "Default"
|
||||
@ -111,7 +111,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
|
||||
"fuel": 1,
|
||||
"items": [],
|
||||
"roles": [
|
||||
"Template"
|
||||
"SAM Sites"
|
||||
],
|
||||
"code": "",
|
||||
"name": "Default"
|
||||
@ -130,7 +130,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
|
||||
"fuel": 1,
|
||||
"items": [],
|
||||
"roles": [
|
||||
"Template"
|
||||
"SAM Sites"
|
||||
],
|
||||
"code": "",
|
||||
"name": "Default"
|
||||
@ -1630,7 +1630,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"SAM"
|
||||
"MANPADS"
|
||||
],
|
||||
"code": "",
|
||||
"name": "Default"
|
||||
@ -1655,7 +1655,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"SAM"
|
||||
"MANPADS"
|
||||
],
|
||||
"code": "",
|
||||
"name": "Default"
|
||||
@ -1680,7 +1680,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"SAM"
|
||||
"MANPADS"
|
||||
],
|
||||
"code": "",
|
||||
"name": "Default"
|
||||
@ -1989,7 +1989,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"SAM"
|
||||
"MANPADS"
|
||||
],
|
||||
"code": "",
|
||||
"name": "Default"
|
||||
@ -2039,7 +2039,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
"SAM"
|
||||
"MANPADS"
|
||||
],
|
||||
"code": "",
|
||||
"name": "Default"
|
||||
|
||||
1711
client/src/units/navalunitdatabase.ts
Normal file
@ -44,7 +44,18 @@ export class UnitDatabase {
|
||||
}
|
||||
}
|
||||
return unitswithrange;
|
||||
}
|
||||
}
|
||||
|
||||
/* Gets a specific blueprint by type */
|
||||
getByType(type: string) {
|
||||
var units = [];
|
||||
for (let unit in this.blueprints) {
|
||||
if (this.blueprints[unit].type === type) {
|
||||
units.push(this.blueprints[unit]);
|
||||
}
|
||||
}
|
||||
return units;
|
||||
}
|
||||
|
||||
/* Get all blueprints by role */
|
||||
getByRole(role: string) {
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
import { LatLng, LatLngBounds } from "leaflet";
|
||||
import { getHotgroupPanel, getInfoPopup, getMap, getUnitDataTable } from "..";
|
||||
import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler } from "..";
|
||||
import { Unit } from "./unit";
|
||||
import { cloneUnit } from "../server/server";
|
||||
import { deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots } from "../other/utils";
|
||||
import { IDLE, MOVE_UNIT } from "../map/map";
|
||||
import { cloneUnit, setLastUpdateTime, spawnGroundUnit } from "../server/server";
|
||||
import { deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polygonArea, randomPointInPoly, randomUnitBlueprintByRole } from "../other/utils";
|
||||
import { CoalitionArea } from "../map/coalitionarea";
|
||||
import { Airbase } from "../missionhandler/airbase";
|
||||
import { groundUnitsDatabase } from "./groundunitsdatabase";
|
||||
import { DataIndexes, IADSRoles, IDLE, MOVE_UNIT } from "../constants/constants";
|
||||
import { DataExtractor } from "./dataextractor";
|
||||
|
||||
export class UnitsManager {
|
||||
#units: { [ID: number]: Unit };
|
||||
@ -28,8 +32,7 @@ export class UnitsManager {
|
||||
getSelectableAircraft() {
|
||||
const units = this.getUnits();
|
||||
return Object.keys(units).reduce((acc: { [key: number]: Unit }, unitId: any) => {
|
||||
const baseData = units[unitId].getBaseData();
|
||||
if (baseData.category === "Aircraft" && baseData.alive === true) {
|
||||
if (units[unitId].getCategory() === "Aircraft" && units[unitId].getAlive() === true) {
|
||||
acc[unitId] = units[unitId];
|
||||
}
|
||||
return acc;
|
||||
@ -48,15 +51,15 @@ export class UnitsManager {
|
||||
}
|
||||
|
||||
getUnitsByHotgroup(hotgroup: number) {
|
||||
return Object.values(this.#units).filter((unit: Unit) => { return unit.getBaseData().alive && unit.getHotgroup() == hotgroup });
|
||||
return Object.values(this.#units).filter((unit: Unit) => { return unit.getAlive() && unit.getHotgroup() == hotgroup });
|
||||
}
|
||||
|
||||
addUnit(ID: number, data: UnitData) {
|
||||
if (data.baseData && data.baseData.category){
|
||||
/* The name of the unit category is exactly the same as the constructor name */
|
||||
var constructor = Unit.getConstructor(data.baseData.category);
|
||||
addUnit(ID: number, category: string) {
|
||||
if (category){
|
||||
/* The name of the unit category is exactly the same as the constructor name */
|
||||
var constructor = Unit.getConstructor(category);
|
||||
if (constructor != undefined) {
|
||||
this.#units[ID] = new constructor(ID, data);
|
||||
this.#units[ID] = new constructor(ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -65,30 +68,26 @@ export class UnitsManager {
|
||||
|
||||
}
|
||||
|
||||
update(data: UnitsData) {
|
||||
var updatedUnits: Unit[] = [];
|
||||
Object.keys(data.units)
|
||||
.filter((ID: string) => !(ID in this.#units))
|
||||
.reduce((timeout: number, ID: string) => {
|
||||
window.setTimeout(() => {
|
||||
if (!(ID in this.#units))
|
||||
this.addUnit(parseInt(ID), data.units[ID]);
|
||||
this.#units[parseInt(ID)]?.setData(data.units[ID]);
|
||||
}, timeout);
|
||||
return timeout + 10;
|
||||
}, 10);
|
||||
update(buffer: ArrayBuffer) {
|
||||
var dataExtractor = new DataExtractor(buffer);
|
||||
var updateTime = Number(dataExtractor.extractUInt64());
|
||||
|
||||
Object.keys(data.units)
|
||||
.filter((ID: string) => ID in this.#units)
|
||||
.forEach((ID: string) => {
|
||||
updatedUnits.push(this.#units[parseInt(ID)]);
|
||||
this.#units[parseInt(ID)]?.setData(data.units[ID])
|
||||
});
|
||||
while (dataExtractor.getSeekPosition() < buffer.byteLength) {
|
||||
const ID = dataExtractor.extractUInt32();
|
||||
if (!(ID in this.#units)) {
|
||||
const datumIndex = dataExtractor.extractUInt8();
|
||||
if (datumIndex == DataIndexes.category) {
|
||||
const category = dataExtractor.extractString();
|
||||
this.addUnit(ID, category);
|
||||
}
|
||||
else {
|
||||
// TODO request a refresh since we must have missed some packets
|
||||
}
|
||||
}
|
||||
this.#units[ID]?.setData(dataExtractor);
|
||||
}
|
||||
|
||||
this.getSelectedUnits().forEach((unit: Unit) => {
|
||||
if (!updatedUnits.includes(unit))
|
||||
unit.setData({})
|
||||
});
|
||||
setLastUpdateTime(updateTime);
|
||||
}
|
||||
|
||||
setHiddenType(key: string, value: boolean) {
|
||||
@ -115,7 +114,7 @@ export class UnitsManager {
|
||||
this.deselectAllUnits();
|
||||
for (let ID in this.#units) {
|
||||
if (this.#units[ID].getHidden() == false) {
|
||||
var latlng = new LatLng(this.#units[ID].getFlightData().latitude, this.#units[ID].getFlightData().longitude);
|
||||
var latlng = new LatLng(this.#units[ID].getPosition().lat, this.#units[ID].getPosition().lng);
|
||||
if (bounds.contains(latlng)) {
|
||||
this.#units[ID].setSelected(true);
|
||||
}
|
||||
@ -132,11 +131,11 @@ export class UnitsManager {
|
||||
}
|
||||
if (options) {
|
||||
if (options.excludeHumans)
|
||||
selectedUnits = selectedUnits.filter((unit: Unit) => { return !unit.getMissionData().flags.Human });
|
||||
selectedUnits = selectedUnits.filter((unit: Unit) => { return !unit.getHuman() });
|
||||
if (options.onlyOnePerGroup) {
|
||||
var temp: Unit[] = [];
|
||||
for (let unit of selectedUnits) {
|
||||
if (!temp.some((otherUnit: Unit) => unit.getBaseData().groupName == otherUnit.getBaseData().groupName))
|
||||
if (!temp.some((otherUnit: Unit) => unit.getGroupName() == otherUnit.getGroupName()))
|
||||
temp.push(unit);
|
||||
}
|
||||
selectedUnits = temp;
|
||||
@ -181,7 +180,7 @@ export class UnitsManager {
|
||||
if (this.getSelectedUnits().length == 0)
|
||||
return undefined;
|
||||
return this.getSelectedUnits().map((unit: Unit) => {
|
||||
return unit.getMissionData().coalition
|
||||
return unit.getCoalition()
|
||||
})?.reduce((a: any, b: any) => {
|
||||
return a == b ? a : undefined
|
||||
});
|
||||
@ -201,8 +200,8 @@ export class UnitsManager {
|
||||
for (let idx in selectedUnits) {
|
||||
const unit = selectedUnits[idx];
|
||||
/* If a unit is following another unit, and that unit is also selected, send the command to the followed unit */
|
||||
if (unit.getTaskData().currentState === "Follow") {
|
||||
const leader = this.getUnitByID(unit.getFormationData().leaderID)
|
||||
if (unit.getState() === "Follow") {
|
||||
const leader = this.getUnitByID(unit.getLeaderID())
|
||||
if (leader && leader.getSelected())
|
||||
leader.addDestination(latlng);
|
||||
else
|
||||
@ -221,8 +220,8 @@ export class UnitsManager {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true });
|
||||
for (let idx in selectedUnits) {
|
||||
const unit = selectedUnits[idx];
|
||||
if (unit.getTaskData().currentState === "Follow") {
|
||||
const leader = this.getUnitByID(unit.getFormationData().leaderID)
|
||||
if (unit.getState() === "Follow") {
|
||||
const leader = this.getUnitByID(unit.getLeaderID())
|
||||
if (leader && leader.getSelected())
|
||||
leader.clearDestinations();
|
||||
else
|
||||
@ -333,13 +332,13 @@ export class UnitsManager {
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].attackUnit(ID);
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `attacking unit ${this.getUnitByID(ID)?.getBaseData().unitName}`);
|
||||
this.#showActionMessage(selectedUnits, `attacking unit ${this.getUnitByID(ID)?.getUnitName()}`);
|
||||
}
|
||||
|
||||
selectedUnitsDelete(explosion: boolean = false) {
|
||||
var selectedUnits = this.getSelectedUnits(); /* Can be applied to humans too */
|
||||
const selectionContainsAHuman = selectedUnits.some( ( unit:Unit ) => {
|
||||
return unit.getMissionData().flags.Human === true;
|
||||
return unit.getHuman() === true;
|
||||
});
|
||||
|
||||
if (selectionContainsAHuman && !confirm( "Your selection includes a human player. Deleting humans causes their vehicle to crash.\n\nAre you sure you want to do this?" ) ) {
|
||||
@ -400,7 +399,7 @@ export class UnitsManager {
|
||||
}
|
||||
count++;
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getBaseData().unitName}`);
|
||||
this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getUnitName()}`);
|
||||
}
|
||||
|
||||
selectedUnitsSetHotgroup(hotgroup: number) {
|
||||
@ -422,7 +421,7 @@ export class UnitsManager {
|
||||
/* Compute the center of the group */
|
||||
var center = { x: 0, y: 0 };
|
||||
selectedUnits.forEach((unit: Unit) => {
|
||||
var mercator = latLngToMercator(unit.getFlightData().latitude, unit.getFlightData().longitude);
|
||||
var mercator = latLngToMercator(unit.getPosition().lat, unit.getPosition().lng);
|
||||
center.x += mercator.x / selectedUnits.length;
|
||||
center.y += mercator.y / selectedUnits.length;
|
||||
});
|
||||
@ -430,7 +429,7 @@ export class UnitsManager {
|
||||
/* Compute the distances from the center of the group */
|
||||
var unitDestinations: { [key: number]: LatLng } = {};
|
||||
selectedUnits.forEach((unit: Unit) => {
|
||||
var mercator = latLngToMercator(unit.getFlightData().latitude, unit.getFlightData().longitude);
|
||||
var mercator = latLngToMercator(unit.getPosition().lat, unit.getPosition().lng);
|
||||
var distancesFromCenter = { dx: mercator.x - center.x, dy: mercator.y - center.y };
|
||||
|
||||
/* Rotate the distance according to the group rotation */
|
||||
@ -490,7 +489,7 @@ export class UnitsManager {
|
||||
if (!this.#pasteDisabled) {
|
||||
for (let idx in this.#copiedUnits) {
|
||||
var unit = this.#copiedUnits[idx];
|
||||
getMap().addTemporaryMarker(getMap().getMouseCoordinates());
|
||||
//getMap().addTemporaryMarker(getMap().getMouseCoordinates());
|
||||
cloneUnit(unit.ID, getMap().getMouseCoordinates());
|
||||
this.#showActionMessage(this.#copiedUnits, `pasted`);
|
||||
}
|
||||
@ -499,6 +498,31 @@ export class UnitsManager {
|
||||
}
|
||||
}
|
||||
|
||||
createIADS(coalitionArea: CoalitionArea, options: {[key: string]: boolean}, density: number) {
|
||||
const activeRoles = Object.keys(options).filter((key: string) => { return options[key]; });
|
||||
const airbases = getMissionHandler().getAirbases();
|
||||
const pointsNumber = polygonArea(coalitionArea) / 1e7 * density / 100;
|
||||
for (let i = 0; i < pointsNumber; i++) {
|
||||
const latlng = randomPointInPoly(coalitionArea);
|
||||
var minDistance: number = Infinity;
|
||||
var maxDistance: number = 0;
|
||||
Object.values(airbases).forEach((airbase: Airbase) => {
|
||||
var distance = airbase.getLatLng().distanceTo(latlng);
|
||||
if (distance < minDistance) minDistance = distance;
|
||||
if (distance > maxDistance) maxDistance = distance;
|
||||
});
|
||||
|
||||
const role = activeRoles[Math.floor(Math.random() * activeRoles.length)];
|
||||
const probability = Math.pow(1 - minDistance / 50e3, 5) * IADSRoles[role];
|
||||
if (Math.random() < probability){
|
||||
const unitBlueprint = randomUnitBlueprintByRole(groundUnitsDatabase, role);
|
||||
const spawnOptions = {role: role, latlng: latlng, name: unitBlueprint.name, coalition: coalitionArea.getCoalition(), immediate: true};
|
||||
spawnGroundUnit(spawnOptions);
|
||||
getMap().addTemporaryMarker(spawnOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/***********************************************/
|
||||
#onKeyUp(event: KeyboardEvent) {
|
||||
if (!keyEventWasInInput(event) && event.key === "Delete" ) {
|
||||
@ -535,8 +559,8 @@ export class UnitsManager {
|
||||
|
||||
#showActionMessage(units: Unit[], message: string) {
|
||||
if (units.length == 1)
|
||||
getInfoPopup().setText(`${units[0].getBaseData().unitName} ${message}`);
|
||||
getInfoPopup().setText(`${units[0].getUnitName()} ${message}`);
|
||||
else if (units.length > 1)
|
||||
getInfoPopup().setText(`${units[0].getBaseData().unitName} and ${units.length - 1} other units ${message}`);
|
||||
getInfoPopup().setText(`${units[0].getUnitName()} and ${units.length - 1} other units ${message}`);
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,17 @@
|
||||
<div id="map-contextmenu" oncontextmenu="return false;">
|
||||
<div id="active-coalition-label" data-active-coalition="blue"></div>
|
||||
<div id="map-contextmenu" class="ol-context-menu" oncontextmenu="return false;">
|
||||
<div id="active-coalition-label" data-coalition="blue"></div>
|
||||
<div id="upper-bar" class="ol-panel">
|
||||
<div id="coalition-switch" class="ol-switch"></div>
|
||||
<button data-active-coalition="blue" id="aircraft-spawn-button" title="Spawn aircraft" data-on-click="contextMenuShow"
|
||||
data-on-click-params='{ "type": "aircraft" }' class="unit-spawn-button"></button>
|
||||
<button data-active-coalition="blue" id="ground-unit-spawn-button" title="Spawn ground unit" data-on-click="contextMenuShow"
|
||||
data-on-click-params='{ "type": "ground-unit" }' class="unit-spawn-button"></button>
|
||||
<button data-active-coalition="blue" id="smoke-spawn-button" title="Spawn smoke" data-on-click="contextMenuShow"
|
||||
data-on-click-params='{ "type": "smoke" }' class="unit-spawn-button"></button>
|
||||
<button data-active-coalition="blue" id="explosion-spawn-button" title="Explosion" data-on-click="contextMenuShow"
|
||||
data-on-click-params='{ "type": "explosion" }' class="unit-spawn-button"></button>
|
||||
<div id="coalition-switch" class="ol-switch ol-coalition-switch"></div>
|
||||
<button data-coalition="blue" id="aircraft-spawn-button" title="Spawn aircraft" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "aircraft" }' class="ol-contexmenu-button"></button>
|
||||
<button data-coalition="blue" id="ground-ol-contexmenu-button" title="Spawn ground unit" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "ground-unit" }' class="ol-contexmenu-button"></button>
|
||||
<button data-coalition="blue" id="smoke-spawn-button" title="Spawn smoke" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "smoke" }' class="ol-contexmenu-button"></button>
|
||||
<button data-coalition="blue" id="explosion-spawn-button" title="Explosion" data-on-click="mapContextMenuShow"
|
||||
data-on-click-params='{ "type": "explosion" }' class="ol-contexmenu-button"></button>
|
||||
</div>
|
||||
<div id="aircraft-spawn-menu" class="ol-panel hide">
|
||||
<div id="aircraft-spawn-menu" class="ol-contexmenu-panel ol-panel hide">
|
||||
<div class="ol-select-container">
|
||||
<div id="aircraft-role-options" class="ol-select">
|
||||
<div class="ol-select-value">Aircraft role</div>
|
||||
@ -54,9 +54,9 @@
|
||||
<div id="loadout-list">
|
||||
</div>
|
||||
</div>
|
||||
<button class="deploy-unit-button" title="" data-active-coalition="blue" data-on-click="contextMenuDeployAircraft" disabled>Deploy unit</button>
|
||||
<button class="deploy-unit-button" title="" data-coalition="blue" data-on-click="contextMenuDeployAircraft" disabled>Deploy unit</button>
|
||||
</div>
|
||||
<div id="ground-unit-spawn-menu" class="ol-panel hide">
|
||||
<div id="ground-unit-spawn-menu" class="ol-panel ol-contexmenu-panel hide">
|
||||
<div class="ol-select-container">
|
||||
<div id="ground-unit-role-options" class="ol-select">
|
||||
<div class="ol-select-value">Ground unit role</div>
|
||||
@ -74,16 +74,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="deploy-unit-button" title="" data-active-coalition="blue" data-on-click="contextMenuDeployGroundUnit" disabled>Deploy unit</button>
|
||||
<button class="deploy-unit-button" title="" data-coalition="blue" data-on-click="contextMenuDeployGroundUnit" disabled>Deploy unit</button>
|
||||
</div>
|
||||
<div id="smoke-spawn-menu" class="ol-panel hide">
|
||||
<div id="smoke-spawn-menu" class="ol-panel ol-contexmenu-panel hide">
|
||||
<button class="smoke-button" title="" data-smoke-color="white" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "white" }'>White smoke</button>
|
||||
<button class="smoke-button" title="" data-smoke-color="blue" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "blue" }'>Blue smoke</button>
|
||||
<button class="smoke-button" title="" data-smoke-color="red" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "red" }'>Red smoke</button>
|
||||
<button class="smoke-button" title="" data-smoke-color="green" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "green" }'>Green smoke</button>
|
||||
<button class="smoke-button" title="" data-smoke-color="orange" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "orange" }'>Orange smoke</button>
|
||||
</div>
|
||||
<div id="explosion-menu" class="ol-panel hide">
|
||||
<div id="explosion-menu" class="ol-panel ol-contexmenu-panel hide">
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "strength": 50 }'>Small explosion</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "strength": 100 }'>Medium explosion</button>
|
||||
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "strength": 200 }'>Big explosion</button>
|
||||
@ -96,17 +96,63 @@
|
||||
</div>
|
||||
|
||||
<div id="airbase-contextmenu" class="ol-panel" oncontextmenu="return false;">
|
||||
|
||||
<h3 id="airbase-name"></h3>
|
||||
|
||||
<div id="airbase-properties"></div>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<h4>Parking available:</h4>
|
||||
<div id="airbase-parking"></div>
|
||||
|
||||
<button id="spawn-airbase-aircraft-button" data-active-coalition="red" title="Spawn aircraft" data-on-click="contextMenuSpawnAirbase" class="deploy-unit-button">Spawn</button>
|
||||
<button id="spawn-airbase-aircraft-button" data-coalition="red" title="Spawn aircraft" data-on-click="contextMenuSpawnAirbase" class="deploy-unit-button">Spawn</button>
|
||||
<button id="land-here-button" title="Land here" data-on-click="contextMenuLandAirbase" class="hide">Land here</button>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="coalition-area-contextmenu" class="ol-context-menu" oncontextmenu="return false;">
|
||||
<div id="area-coalition-label" data-coalition="blue"></div>
|
||||
<div id="upper-bar" class="ol-panel">
|
||||
<div id="coalition-area-switch" class="ol-switch ol-coalition-switch"></div>
|
||||
<button data-coalition="blue" id="iads-button" title="Create Integrated Air Defense System" data-on-click="coalitionAreaContextMenuShow"
|
||||
data-on-click-params='{ "type": "iads" }' class="ol-contexmenu-button"></button>
|
||||
<button data-coalition="blue" id="cap-button" title="Create Combat Air Patrols" data-on-click="coalitionAreaContextMenuShow"
|
||||
data-on-click-params='{ "type": "cap" }' class="ol-contexmenu-button"></button>
|
||||
<button data-coalition="blue" id="coalitionarea-back-button" title="Bring area to back" data-on-click="coalitionAreaBringToBack"
|
||||
class="ol-contexmenu-button"></button>
|
||||
<button data-coalition="blue" id="coalitionarea-delete-button" title="Delete area" data-on-click="coalitionAreaDelete"
|
||||
class="ol-contexmenu-button"></button>
|
||||
</div>
|
||||
<div id="iads-menu" class="ol-panel ol-contexmenu-panel hide">
|
||||
<div id="iads-units-role-options" class="ol-select">
|
||||
<div class="ol-select-value">Unit types</div>
|
||||
<div class="ol-select-options">
|
||||
<!-- This is where all the iads unit roles checkboxes will be shown-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<div class="ol-select-container">
|
||||
<div id="iads-period-options" class="ol-select">
|
||||
<div class="ol-select-value">Units period</div>
|
||||
<div class="ol-select-options">
|
||||
<!-- This is where all the iads unit period buttons will be shown--><!--
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="coalition-units-checkbox" class="ol-checkbox">
|
||||
<label title="Use coalition specific units only (e.g. Patriot sites for blue coalition, SA-2s for red coalition)">
|
||||
<input type="checkbox"/>
|
||||
Coalition specific units
|
||||
</label>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<div id="iads-density-slider" class="ol-slider-container">
|
||||
<dl class="ol-data-grid">
|
||||
<dt> IADS density </dt> <dd> <div class="ol-slider-value"></div> </dd>
|
||||
</dl>
|
||||
<input type="range" min="0" max="100" value="0" class="ol-slider">
|
||||
</div>
|
||||
<button class="create-iads-button" title="" data-coalition="blue" data-on-click="contextMenuCreateIads">Add units to IADS</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -29,38 +29,44 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="unit-visibility-control" class="ol-group">
|
||||
<!-- Here the available visibility controls will be listed -->
|
||||
<div id="unit-visibility-control" class="ol-group ol-navbar-buttons-group">
|
||||
<!-- Here the available visibility controls will be listed -->
|
||||
</div>
|
||||
|
||||
<div id="coalition-visibility-control" class="ol-group ol-group-button-toggle">
|
||||
|
||||
<div>
|
||||
<button id="coalition-visibility-control-blue" data-on-click="toggleCoalitionVisibility"
|
||||
data-on-click-params='{ "coalition": "blue" }'>View <span class="accent-bluefor">BLUEFOR</span></button>
|
||||
data-on-click-params='{ "coalition": "blue" }'><span class="accent-bluefor">BLUEFOR</span></button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button id="coalition-visibility-control-red" data-on-click="toggleCoalitionVisibility"
|
||||
data-on-click-params='{ "coalition": "red" }'>View <span class="accent-redfor">REDFOR</span></button>
|
||||
data-on-click-params='{ "coalition": "red" }'><span class="accent-redfor">REDFOR</span></button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button id="coalition-visibility-control-neutral" data-on-click="toggleCoalitionVisibility"
|
||||
data-on-click-params='{ "coalition": "neutral" }'>View <span
|
||||
class="accent-neutral">NEUTRAL</span></button>
|
||||
data-on-click-params='{ "coalition": "neutral" }'><span class="accent-neutral">NEUTRAL</span></button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div id="atc-navbar-control" class="ol-group-container" data-feature-switch="atc">
|
||||
<div class="ol-group-header">ATC</div>
|
||||
<div id="atc-navbar-control" class="ol-group-container ol-navbar-buttons-group" data-feature-switch="atc">
|
||||
<div class="ol-group">
|
||||
<button data-on-click="toggleElements"
|
||||
data-on-click-params='{"selector": "#strip-board-ground"}'>GND</button>
|
||||
data-on-click-params='{"selector": "#strip-board-ground"}' class="off"><img src="resources/theme/images/buttons/tools/ground.svg" inject-svg></button>
|
||||
<button data-on-click="toggleElements"
|
||||
data-on-click-params='{"selector": "#strip-board-tower"}'>TWR</button>
|
||||
data-on-click-params='{"selector": "#strip-board-tower"}' class="off"><img src="resources/theme/images/buttons/tools/tower.svg" inject-svg></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="map-tools" class="ol-group-container ol-navbar-buttons-group">
|
||||
<div class="ol-group">
|
||||
<button title="Enable area interaction" data-on-click="toggleCoalitionAreaInteraction" class="off">
|
||||
<img src="resources/theme/images/buttons/tools/pen-solid.svg" inject-svg>
|
||||
</button>
|
||||
<button title="Draw Coalition Areas on the map" data-on-click="toggleCoalitionAreaDraw" data-on-click-params='{"type": "polygon"}' class="off">
|
||||
<img src="resources/theme/images/buttons/tools/draw-polygon-solid.svg" inject-svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@ -1047,7 +1047,7 @@
|
||||
<dt>Open air</dt>
|
||||
<dd>5</dd>
|
||||
</dl>
|
||||
<button id="spawn-airbase-aircraft-button" data-active-coalition="red" title="Spawn aircraft" data-on-click="contextMenuSpawnAirbase" class="deploy-unit-button">Spawn</button>
|
||||
<button id="spawn-airbase-aircraft-button" data-coalition="red" title="Spawn aircraft" data-on-click="contextMenuSpawnAirbase" class="deploy-unit-button">Spawn</button>
|
||||
<button id="land-here-button" title="Land here" data-on-click="contextMenuLandAirbase" class="hide">Land here</button>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
local version = "v0.3.0-alpha"
|
||||
|
||||
local debug = false
|
||||
local debug = FALSE
|
||||
|
||||
Olympus.unitCounter = 1
|
||||
Olympus.payloadRegistry = {}
|
||||
@ -343,7 +343,7 @@ function Olympus.spawnGroundUnit(coalition, unitType, lat, lng)
|
||||
units = unitTable,
|
||||
country = countryID,
|
||||
category = 'vehicle',
|
||||
name = "Olympus-" .. Olympus.unitCounter,
|
||||
name = "Ground-" .. Olympus.unitCounter,
|
||||
}
|
||||
mist.dynAdd(vars)
|
||||
Olympus.unitCounter = Olympus.unitCounter + 1
|
||||
|
||||
25274
scripts/unitPayloads.lua
8
src/.vscode/settings.json
vendored
@ -1,8 +0,0 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"*.ejs": "html",
|
||||
"xstring": "cpp",
|
||||
"vector": "cpp",
|
||||
"list": "cpp"
|
||||
}
|
||||
}
|
||||
@ -36,6 +36,7 @@
|
||||
<ClInclude Include="include\aircraft.h" />
|
||||
<ClInclude Include="include\airunit.h" />
|
||||
<ClInclude Include="include\commands.h" />
|
||||
<ClInclude Include="include\datatypes.h" />
|
||||
<ClInclude Include="include\measure.h" />
|
||||
<ClInclude Include="include\groundunit.h" />
|
||||
<ClInclude Include="include\helicopter.h" />
|
||||
@ -52,6 +53,7 @@
|
||||
<ClCompile Include="src\airunit.cpp" />
|
||||
<ClCompile Include="src\commands.cpp" />
|
||||
<ClCompile Include="src\core.cpp" />
|
||||
<ClCompile Include="src\datatypes.cpp" />
|
||||
<ClCompile Include="src\measure.cpp" />
|
||||
<ClCompile Include="src\groundunit.cpp" />
|
||||
<ClCompile Include="src\helicopter.cpp" />
|
||||
|
||||
@ -48,6 +48,9 @@
|
||||
<ClInclude Include="include\measure.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\datatypes.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="src\aircraft.cpp">
|
||||
@ -92,5 +95,8 @@
|
||||
<ClCompile Include="src\measure.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\datatypes.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -4,10 +4,8 @@
|
||||
class Aircraft : public AirUnit
|
||||
{
|
||||
public:
|
||||
Aircraft(json::value json, int ID);
|
||||
Aircraft(json::value json, unsigned int ID);
|
||||
|
||||
virtual wstring getCategory() { return L"Aircraft"; };
|
||||
|
||||
virtual void changeSpeed(wstring change);
|
||||
virtual void changeAltitude(wstring change);
|
||||
virtual void changeSpeed(string change);
|
||||
virtual void changeAltitude(string change);
|
||||
};
|
||||
@ -5,18 +5,17 @@
|
||||
#include "luatools.h"
|
||||
#include "Unit.h"
|
||||
|
||||
#define AIR_DEST_DIST_THR 2000
|
||||
#define AIR_DEST_DIST_THR 2000 // Meters
|
||||
|
||||
class AirUnit : public Unit
|
||||
{
|
||||
public:
|
||||
AirUnit(json::value json, int ID);
|
||||
AirUnit(json::value json, unsigned int ID);
|
||||
|
||||
virtual void setState(int newState);
|
||||
virtual void setState(unsigned char newState);
|
||||
|
||||
virtual wstring getCategory() = 0;
|
||||
virtual void changeSpeed(wstring change) = 0;
|
||||
virtual void changeAltitude(wstring change) = 0;
|
||||
virtual void changeSpeed(string change) = 0;
|
||||
virtual void changeAltitude(string change) = 0;
|
||||
|
||||
protected:
|
||||
virtual void AIloop();
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
#include "logger.h"
|
||||
|
||||
namespace CommandPriority {
|
||||
enum CommandPriorities { LOW, MEDIUM, HIGH };
|
||||
enum CommandPriorities { LOW, MEDIUM, HIGH, IMMEDIATE };
|
||||
};
|
||||
|
||||
namespace SetCommandType {
|
||||
@ -54,6 +54,15 @@ namespace ReactionToThreat {
|
||||
};
|
||||
}
|
||||
|
||||
namespace EmissionCountermeasure {
|
||||
enum ReactionsToThreat {
|
||||
SILENT = 0,
|
||||
ATTACK = 1,
|
||||
DEFEND = 2,
|
||||
FREE = 3
|
||||
};
|
||||
}
|
||||
|
||||
namespace RadarUse {
|
||||
enum RadarUses {
|
||||
NEVER = 0,
|
||||
@ -85,19 +94,19 @@ namespace ECMUse {
|
||||
class Command
|
||||
{
|
||||
public:
|
||||
int getPriority() { return priority; }
|
||||
virtual wstring getString(lua_State* L) = 0;
|
||||
virtual int getLoad() = 0;
|
||||
unsigned int getPriority() { return priority; }
|
||||
virtual string getString(lua_State* L) = 0;
|
||||
virtual unsigned int getLoad() = 0;
|
||||
|
||||
protected:
|
||||
int priority = CommandPriority::LOW;
|
||||
unsigned int priority = CommandPriority::LOW;
|
||||
};
|
||||
|
||||
/* Simple low priority move command (from user click) */
|
||||
class Move : public Command
|
||||
{
|
||||
public:
|
||||
Move(wstring groupName, Coords destination, double speed, wstring speedType, double altitude, wstring altitudeType, wstring taskOptions, wstring category):
|
||||
Move(string groupName, Coords destination, double speed, string speedType, double altitude, string altitudeType, string taskOptions, string category):
|
||||
groupName(groupName),
|
||||
destination(destination),
|
||||
speed(speed),
|
||||
@ -109,35 +118,35 @@ public:
|
||||
{
|
||||
priority = CommandPriority::HIGH;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 5; }
|
||||
virtual string getString(lua_State* L);
|
||||
virtual unsigned int getLoad() { return 5; }
|
||||
|
||||
private:
|
||||
const wstring groupName;
|
||||
const string groupName;
|
||||
const Coords destination;
|
||||
const double speed;
|
||||
const wstring speedType;
|
||||
const string speedType;
|
||||
const double altitude;
|
||||
const wstring altitudeType;
|
||||
const wstring taskOptions;
|
||||
const wstring category;
|
||||
const string altitudeType;
|
||||
const string taskOptions;
|
||||
const string category;
|
||||
};
|
||||
|
||||
/* Smoke command */
|
||||
class Smoke : public Command
|
||||
{
|
||||
public:
|
||||
Smoke(wstring color, Coords location) :
|
||||
Smoke(string color, Coords location) :
|
||||
color(color),
|
||||
location(location)
|
||||
{
|
||||
priority = CommandPriority::LOW;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 5; }
|
||||
virtual string getString(lua_State* L);
|
||||
virtual unsigned int getLoad() { return 5; }
|
||||
|
||||
private:
|
||||
const wstring color;
|
||||
const string color;
|
||||
const Coords location;
|
||||
};
|
||||
|
||||
@ -145,61 +154,65 @@ private:
|
||||
class SpawnGroundUnit : public Command
|
||||
{
|
||||
public:
|
||||
SpawnGroundUnit(wstring coalition, wstring unitType, Coords location) :
|
||||
SpawnGroundUnit(string coalition, string unitType, Coords location, bool immediate) :
|
||||
coalition(coalition),
|
||||
unitType(unitType),
|
||||
location(location)
|
||||
location(location),
|
||||
immediate(immediate)
|
||||
{
|
||||
priority = CommandPriority::LOW;
|
||||
priority = immediate? CommandPriority::IMMEDIATE: CommandPriority::LOW;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 100; }
|
||||
virtual string getString(lua_State* L);
|
||||
virtual unsigned int getLoad() { return 100 * !immediate; }
|
||||
|
||||
private:
|
||||
const wstring coalition;
|
||||
const wstring unitType;
|
||||
const string coalition;
|
||||
const string unitType;
|
||||
const Coords location;
|
||||
const bool immediate;
|
||||
};
|
||||
|
||||
/* Spawn air unit command */
|
||||
class SpawnAircraft : public Command
|
||||
{
|
||||
public:
|
||||
SpawnAircraft(wstring coalition, wstring unitType, Coords location, wstring payloadName, wstring airbaseName) :
|
||||
SpawnAircraft(string coalition, string unitType, Coords location, string payloadName, string airbaseName, bool immediate) :
|
||||
coalition(coalition),
|
||||
unitType(unitType),
|
||||
location(location),
|
||||
payloadName(payloadName),
|
||||
airbaseName(airbaseName)
|
||||
airbaseName(airbaseName),
|
||||
immediate(immediate)
|
||||
{
|
||||
priority = CommandPriority::LOW;
|
||||
priority = immediate ? CommandPriority::IMMEDIATE : CommandPriority::LOW;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 100; }
|
||||
virtual string getString(lua_State* L);
|
||||
virtual unsigned int getLoad() { return 100 * !immediate; }
|
||||
|
||||
private:
|
||||
const wstring coalition;
|
||||
const wstring unitType;
|
||||
const string coalition;
|
||||
const string unitType;
|
||||
const Coords location;
|
||||
const wstring payloadName;
|
||||
const wstring airbaseName;
|
||||
const string payloadName;
|
||||
const string airbaseName;
|
||||
const bool immediate;
|
||||
};
|
||||
|
||||
/* Clone unit command */
|
||||
class Clone : public Command
|
||||
{
|
||||
public:
|
||||
Clone(int ID, Coords location) :
|
||||
Clone(unsigned int ID, Coords location) :
|
||||
ID(ID),
|
||||
location(location)
|
||||
{
|
||||
priority = CommandPriority::LOW;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 100; }
|
||||
virtual string getString(lua_State* L);
|
||||
virtual unsigned int getLoad() { return 100; }
|
||||
|
||||
private:
|
||||
const int ID;
|
||||
const unsigned int ID;
|
||||
const Coords location;
|
||||
};
|
||||
|
||||
@ -207,77 +220,77 @@ private:
|
||||
class Delete : public Command
|
||||
{
|
||||
public:
|
||||
Delete(int ID, bool explosion) :
|
||||
Delete(unsigned int ID, bool explosion) :
|
||||
ID(ID),
|
||||
explosion(explosion)
|
||||
{
|
||||
priority = CommandPriority::HIGH;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 20; }
|
||||
virtual string getString(lua_State* L);
|
||||
virtual unsigned int getLoad() { return 20; }
|
||||
|
||||
private:
|
||||
const int ID;
|
||||
const unsigned int ID;
|
||||
const bool explosion;
|
||||
};
|
||||
|
||||
/* Follow command */
|
||||
/* SetTask command */
|
||||
class SetTask : public Command
|
||||
{
|
||||
public:
|
||||
SetTask(wstring groupName, wstring task) :
|
||||
SetTask(string groupName, string task) :
|
||||
groupName(groupName),
|
||||
task(task)
|
||||
{
|
||||
priority = CommandPriority::MEDIUM;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 10; }
|
||||
virtual string getString(lua_State* L);
|
||||
virtual unsigned int getLoad() { return 2; }
|
||||
|
||||
private:
|
||||
const wstring groupName;
|
||||
const wstring task;
|
||||
const string groupName;
|
||||
const string task;
|
||||
};
|
||||
|
||||
/* Reset task command */
|
||||
class ResetTask : public Command
|
||||
{
|
||||
public:
|
||||
ResetTask(wstring groupName) :
|
||||
ResetTask(string groupName) :
|
||||
groupName(groupName)
|
||||
{
|
||||
priority = CommandPriority::HIGH;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 10; }
|
||||
virtual string getString(lua_State* L);
|
||||
virtual unsigned int getLoad() { return 2; }
|
||||
|
||||
private:
|
||||
const wstring groupName;
|
||||
const string groupName;
|
||||
};
|
||||
|
||||
/* Set command */
|
||||
class SetCommand : public Command
|
||||
{
|
||||
public:
|
||||
SetCommand(wstring groupName, wstring command) :
|
||||
SetCommand(string groupName, string command) :
|
||||
groupName(groupName),
|
||||
command(command)
|
||||
{
|
||||
priority = CommandPriority::HIGH;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 10; }
|
||||
virtual string getString(lua_State* L);
|
||||
virtual unsigned int getLoad() { return 2; }
|
||||
|
||||
private:
|
||||
const wstring groupName;
|
||||
const wstring command;
|
||||
const string groupName;
|
||||
const string command;
|
||||
};
|
||||
|
||||
/* Set option command */
|
||||
class SetOption : public Command
|
||||
{
|
||||
public:
|
||||
SetOption(wstring groupName, int optionID, int optionValue) :
|
||||
SetOption(string groupName, unsigned int optionID, unsigned int optionValue) :
|
||||
groupName(groupName),
|
||||
optionID(optionID),
|
||||
optionValue(optionValue),
|
||||
@ -287,7 +300,7 @@ public:
|
||||
priority = CommandPriority::HIGH;
|
||||
};
|
||||
|
||||
SetOption(wstring groupName, int optionID, bool optionBool) :
|
||||
SetOption(string groupName, unsigned int optionID, bool optionBool) :
|
||||
groupName(groupName),
|
||||
optionID(optionID),
|
||||
optionValue(0),
|
||||
@ -296,13 +309,13 @@ public:
|
||||
{
|
||||
priority = CommandPriority::HIGH;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 10; }
|
||||
virtual string getString(lua_State* L);
|
||||
virtual unsigned int getLoad() { return 2; }
|
||||
|
||||
private:
|
||||
const wstring groupName;
|
||||
const int optionID;
|
||||
const int optionValue;
|
||||
const string groupName;
|
||||
const unsigned int optionID;
|
||||
const unsigned int optionValue;
|
||||
const bool optionBool;
|
||||
const bool isBoolean;
|
||||
};
|
||||
@ -311,17 +324,17 @@ private:
|
||||
class SetOnOff : public Command
|
||||
{
|
||||
public:
|
||||
SetOnOff(wstring groupName, bool onOff) :
|
||||
SetOnOff(string groupName, bool onOff) :
|
||||
groupName(groupName),
|
||||
onOff(onOff)
|
||||
{
|
||||
priority = CommandPriority::HIGH;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 10; }
|
||||
virtual string getString(lua_State* L);
|
||||
virtual unsigned int getLoad() { return 2; }
|
||||
|
||||
private:
|
||||
const wstring groupName;
|
||||
const string groupName;
|
||||
const bool onOff;
|
||||
};
|
||||
|
||||
@ -329,16 +342,16 @@ private:
|
||||
class Explosion : public Command
|
||||
{
|
||||
public:
|
||||
Explosion(int intensity, Coords location) :
|
||||
Explosion(unsigned int intensity, Coords location) :
|
||||
location(location),
|
||||
intensity(intensity)
|
||||
{
|
||||
priority = CommandPriority::MEDIUM;
|
||||
};
|
||||
virtual wstring getString(lua_State* L);
|
||||
virtual int getLoad() { return 10; }
|
||||
virtual string getString(lua_State* L);
|
||||
virtual unsigned int getLoad() { return 10; }
|
||||
|
||||
private:
|
||||
const Coords location;
|
||||
const int intensity;
|
||||
const unsigned int intensity;
|
||||
};
|
||||
|
||||
49
src/core/include/datatypes.h
Normal file
@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
#include "framework.h"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
namespace DataTypes {
|
||||
struct TACAN
|
||||
{
|
||||
bool isOn = false;
|
||||
unsigned char channel = 40;
|
||||
char XY = 'X';
|
||||
char callsign[4];
|
||||
};
|
||||
|
||||
struct Radio
|
||||
{
|
||||
unsigned int frequency = 124000000; // MHz
|
||||
unsigned char callsign = 1;
|
||||
unsigned char callsignNumber = 1;
|
||||
};
|
||||
|
||||
struct GeneralSettings
|
||||
{
|
||||
bool prohibitJettison = false;
|
||||
bool prohibitAA = false;
|
||||
bool prohibitAG = false;
|
||||
bool prohibitAfterburner = false;
|
||||
bool prohibitAirWpn = false;
|
||||
};
|
||||
|
||||
struct Ammo {
|
||||
unsigned short quantity = 0;
|
||||
char name[33];
|
||||
unsigned char guidance = 0;
|
||||
unsigned char category = 0;
|
||||
unsigned char missileCategory = 0;
|
||||
};
|
||||
|
||||
struct Contact {
|
||||
unsigned int ID = 0;
|
||||
unsigned char detectionMethod = 0;
|
||||
};
|
||||
}
|
||||
#pragma pack(pop)
|
||||
|
||||
bool operator==(const DataTypes::TACAN& lhs, const DataTypes::TACAN& rhs);
|
||||
bool operator==(const DataTypes::Radio& lhs, const DataTypes::Radio& rhs);
|
||||
bool operator==(const DataTypes::GeneralSettings& lhs, const DataTypes::GeneralSettings& rhs);
|
||||
bool operator==(const DataTypes::Ammo& lhs, const DataTypes::Ammo& rhs);
|
||||
bool operator==(const DataTypes::Contact& lhs, const DataTypes::Contact& rhs);
|
||||
@ -6,12 +6,11 @@
|
||||
class GroundUnit : public Unit
|
||||
{
|
||||
public:
|
||||
GroundUnit(json::value json, int ID);
|
||||
virtual wstring getCategory() { return L"GroundUnit"; };
|
||||
GroundUnit(json::value json, unsigned int ID);
|
||||
|
||||
virtual void setState(int newState);
|
||||
virtual void setState(unsigned char newState);
|
||||
|
||||
virtual void changeSpeed(wstring change);
|
||||
virtual void changeSpeed(string change);
|
||||
virtual void setOnOff(bool newOnOff);
|
||||
virtual void setFollowRoads(bool newFollowRoads);
|
||||
|
||||
|
||||
@ -4,10 +4,8 @@
|
||||
class Helicopter : public AirUnit
|
||||
{
|
||||
public:
|
||||
Helicopter(json::value json, int ID);
|
||||
Helicopter(json::value json, unsigned int ID);
|
||||
|
||||
virtual wstring getCategory() { return L"Helicopter"; };
|
||||
|
||||
virtual void changeSpeed(wstring change);
|
||||
virtual void changeAltitude(wstring change);
|
||||
virtual void changeSpeed(string change);
|
||||
virtual void changeAltitude(string change);
|
||||
};
|
||||
@ -4,10 +4,9 @@
|
||||
class NavyUnit : public Unit
|
||||
{
|
||||
public:
|
||||
NavyUnit(json::value json, int ID);
|
||||
NavyUnit(json::value json, unsigned int ID);
|
||||
virtual void AIloop();
|
||||
|
||||
virtual wstring getCategory() { return L"NavyUnit"; };
|
||||
virtual void changeSpeed(wstring change);
|
||||
virtual void changeSpeed(string change);
|
||||
|
||||
};
|
||||
@ -11,10 +11,10 @@ public:
|
||||
|
||||
void appendCommand(Command* command);
|
||||
void execute(lua_State* L);
|
||||
void handleRequest(wstring key, json::value value);
|
||||
void handleRequest(string key, json::value value);
|
||||
|
||||
private:
|
||||
list<Command*> commands;
|
||||
int load;
|
||||
unsigned int load;
|
||||
};
|
||||
|
||||
|
||||
@ -28,6 +28,6 @@ private:
|
||||
|
||||
atomic<bool> runListener;
|
||||
|
||||
wstring password = L"";
|
||||
string password = "";
|
||||
};
|
||||
|
||||
|
||||
@ -5,9 +5,59 @@
|
||||
#include "luatools.h"
|
||||
#include "measure.h"
|
||||
#include "logger.h"
|
||||
#include "commands.h"
|
||||
#include "datatypes.h"
|
||||
|
||||
#include <chrono>
|
||||
using namespace std::chrono;
|
||||
|
||||
#define TASK_CHECK_INIT_VALUE 10
|
||||
|
||||
namespace DataIndex {
|
||||
enum DataIndexes {
|
||||
startOfData = 0,
|
||||
category,
|
||||
alive,
|
||||
human,
|
||||
controlled,
|
||||
coalition,
|
||||
country,
|
||||
name,
|
||||
unitName,
|
||||
groupName,
|
||||
state,
|
||||
task,
|
||||
hasTask,
|
||||
position,
|
||||
speed,
|
||||
heading,
|
||||
isTanker,
|
||||
isAWACS,
|
||||
onOff,
|
||||
followRoads,
|
||||
fuel,
|
||||
desiredSpeed,
|
||||
desiredSpeedType,
|
||||
desiredAltitude,
|
||||
desiredAltitudeType,
|
||||
leaderID,
|
||||
formationOffset,
|
||||
targetID,
|
||||
targetPosition,
|
||||
ROE,
|
||||
reactionToThreat,
|
||||
emissionsCountermeasures,
|
||||
TACAN,
|
||||
radio,
|
||||
generalSettings,
|
||||
ammo,
|
||||
contacts,
|
||||
activePath,
|
||||
lastIndex,
|
||||
endOfData = 255
|
||||
};
|
||||
}
|
||||
|
||||
namespace State
|
||||
{
|
||||
enum States
|
||||
@ -28,235 +78,224 @@ namespace State
|
||||
};
|
||||
};
|
||||
|
||||
namespace Options {
|
||||
struct TACAN
|
||||
{
|
||||
bool isOn = false;
|
||||
int channel = 40;
|
||||
wstring XY = L"X";
|
||||
wstring callsign = L"TKR";
|
||||
};
|
||||
|
||||
struct Radio
|
||||
{
|
||||
int frequency = 124000000; // MHz
|
||||
int callsign = 1;
|
||||
int callsignNumber = 1;
|
||||
};
|
||||
|
||||
struct GeneralSettings
|
||||
{
|
||||
bool prohibitJettison = false;
|
||||
bool prohibitAA = false;
|
||||
bool prohibitAG = false;
|
||||
bool prohibitAfterburner = false;
|
||||
bool prohibitAirWpn = false;
|
||||
};
|
||||
}
|
||||
|
||||
class Unit
|
||||
{
|
||||
public:
|
||||
Unit(json::value json, int ID);
|
||||
Unit(json::value json, unsigned int ID);
|
||||
~Unit();
|
||||
|
||||
/********** Public methods **********/
|
||||
/********** Methods **********/
|
||||
void initialize(json::value json);
|
||||
void setDefaults(bool force = false);
|
||||
int getID() { return ID; }
|
||||
|
||||
void runAILoop();
|
||||
void updateExportData(json::value json);
|
||||
|
||||
void updateExportData(json::value json, double dt = 0);
|
||||
void updateMissionData(json::value json);
|
||||
json::value getData(long long time, bool getAll = false);
|
||||
virtual wstring getCategory() { return L"No category"; };
|
||||
|
||||
/********** Base data **********/
|
||||
void setControlled(bool newControlled) { controlled = newControlled; addMeasure(L"controlled", json::value(newControlled)); }
|
||||
void setName(wstring newName) { name = newName; addMeasure(L"name", json::value(newName));}
|
||||
void setUnitName(wstring newUnitName) { unitName = newUnitName; addMeasure(L"unitName", json::value(newUnitName));}
|
||||
void setGroupName(wstring newGroupName) { groupName = newGroupName; addMeasure(L"groupName", json::value(newGroupName));}
|
||||
void setAlive(bool newAlive) { alive = newAlive; addMeasure(L"alive", json::value(newAlive));}
|
||||
void setType(json::value newType) { type = newType; addMeasure(L"type", newType);}
|
||||
void setCountry(int newCountry) { country = newCountry; addMeasure(L"country", json::value(newCountry));}
|
||||
|
||||
bool getControlled() { return controlled; }
|
||||
wstring getName() { return name; }
|
||||
wstring getUnitName() { return unitName; }
|
||||
wstring getGroupName() { return groupName; }
|
||||
bool getAlive() { return alive; }
|
||||
json::value getType() { return type; }
|
||||
int getCountry() { return country; }
|
||||
|
||||
/********** Flight data **********/
|
||||
void setLatitude(double newLatitude) {latitude = newLatitude; addMeasure(L"latitude", json::value(newLatitude));}
|
||||
void setLongitude(double newLongitude) {longitude = newLongitude; addMeasure(L"longitude", json::value(newLongitude));}
|
||||
void setAltitude(double newAltitude) {altitude = newAltitude; addMeasure(L"altitude", json::value(newAltitude));}
|
||||
void setHeading(double newHeading) {heading = newHeading; addMeasure(L"heading", json::value(newHeading));}
|
||||
void setSpeed(double newSpeed) {speed = newSpeed; addMeasure(L"speed", json::value(newSpeed));}
|
||||
|
||||
double getLatitude() { return latitude; }
|
||||
double getLongitude() { return longitude; }
|
||||
double getAltitude() { return altitude; }
|
||||
double getHeading() { return heading; }
|
||||
double getSpeed() { return speed; }
|
||||
|
||||
/********** Mission data **********/
|
||||
void setFuel(double newFuel) { fuel = newFuel; addMeasure(L"fuel", json::value(newFuel));}
|
||||
void setAmmo(json::value newAmmo) { ammo = newAmmo; addMeasure(L"ammo", json::value(newAmmo));}
|
||||
void setContacts(json::value newContacts) {contacts = newContacts; addMeasure(L"contacts", json::value(newContacts));}
|
||||
void setHasTask(bool newHasTask);
|
||||
void setCoalitionID(int newCoalitionID);
|
||||
void setFlags(json::value newFlags) { flags = newFlags; addMeasure(L"flags", json::value(newFlags));}
|
||||
|
||||
double getFuel() { return fuel; }
|
||||
json::value getAmmo() { return ammo; }
|
||||
json::value getTargets() { return contacts; }
|
||||
bool getHasTask() { return hasTask; }
|
||||
wstring getCoalition() { return coalition; }
|
||||
int getCoalitionID();
|
||||
json::value getFlags() { return flags; }
|
||||
|
||||
/********** Formation data **********/
|
||||
void setLeaderID(int newLeaderID) { leaderID = newLeaderID; addMeasure(L"leaderID", json::value(newLeaderID)); }
|
||||
void setFormationOffset(Offset formationOffset);
|
||||
|
||||
int getLeaderID() { return leaderID; }
|
||||
Offset getFormationoffset() { return formationOffset; }
|
||||
|
||||
/********** Task data **********/
|
||||
void setCurrentTask(wstring newCurrentTask) { currentTask = newCurrentTask; addMeasure(L"currentTask", json::value(newCurrentTask)); }
|
||||
void setDesiredSpeed(double newDesiredSpeed);
|
||||
void setDesiredAltitude(double newDesiredAltitude);
|
||||
void setDesiredSpeedType(wstring newDesiredSpeedType);
|
||||
void setDesiredAltitudeType(wstring newDesiredAltitudeType);
|
||||
void setActiveDestination(Coords newActiveDestination) { activeDestination = newActiveDestination; addMeasure(L"activeDestination", json::value("")); } // TODO fix
|
||||
void setActivePath(list<Coords> newActivePath);
|
||||
void setTargetID(int newTargetID) { targetID = newTargetID; addMeasure(L"targetID", json::value(newTargetID));}
|
||||
void setTargetLocation(Coords newTargetLocation);
|
||||
void setIsTanker(bool newIsTanker);
|
||||
void setIsAWACS(bool newIsAWACS);
|
||||
virtual void setOnOff(bool newOnOff) { onOff = newOnOff; addMeasure(L"onOff", json::value(newOnOff));};
|
||||
virtual void setFollowRoads(bool newFollowRoads) { followRoads = newFollowRoads; addMeasure(L"followRoads", json::value(newFollowRoads)); };
|
||||
|
||||
wstring getCurrentTask() { return currentTask; }
|
||||
virtual double getDesiredSpeed() { return desiredSpeed; };
|
||||
virtual double getDesiredAltitude() { return desiredAltitude; };
|
||||
virtual wstring getDesiredSpeedType() { return desiredSpeedType; };
|
||||
virtual wstring getDesiredAltitudeType() { return desiredAltitudeType; };
|
||||
unsigned int getDataPacket(char*& data);
|
||||
unsigned int getID() { return ID; }
|
||||
void getData(stringstream& ss, unsigned long long time);
|
||||
Coords getActiveDestination() { return activeDestination; }
|
||||
list<Coords> getActivePath() { return activePath; }
|
||||
int getTargetID() { return targetID; }
|
||||
Coords getTargetLocation() { return targetLocation; }
|
||||
bool getIsTanker() { return isTanker; }
|
||||
bool getIsAWACS() { return isAWACS; }
|
||||
bool getOnOff() { return onOff; };
|
||||
bool getFollowRoads() { return followRoads; };
|
||||
|
||||
/********** Options data **********/
|
||||
void setROE(wstring newROE, bool force = false);
|
||||
void setReactionToThreat(wstring newReactionToThreat, bool force = false);
|
||||
void setEmissionsCountermeasures(wstring newEmissionsCountermeasures, bool force = false);
|
||||
void setTACAN(Options::TACAN newTACAN, bool force = false);
|
||||
void setRadio(Options::Radio newradio, bool force = false);
|
||||
void setGeneralSettings(Options::GeneralSettings newGeneralSettings, bool force = false);
|
||||
void setEPLRS(bool newEPLRS, bool force = false);
|
||||
|
||||
wstring getROE() { return ROE; }
|
||||
wstring getReactionToThreat() { return reactionToThreat; }
|
||||
wstring getEmissionsCountermeasures() { return emissionsCountermeasures; };
|
||||
Options::TACAN getTACAN() { return TACAN; }
|
||||
Options::Radio getRadio() { return radio; }
|
||||
Options::GeneralSettings getGeneralSettings() { return generalSettings; }
|
||||
bool getEPLRS() { return EPLRS; }
|
||||
|
||||
/********** Control functions **********/
|
||||
void landAt(Coords loc);
|
||||
virtual void changeSpeed(wstring change) {};
|
||||
virtual void changeAltitude(wstring change) {};
|
||||
virtual void changeSpeed(string change) {};
|
||||
virtual void changeAltitude(string change) {};
|
||||
bool setActiveDestination();
|
||||
void resetActiveDestination();
|
||||
virtual void setState(int newState) { state = newState; };
|
||||
void resetTask();
|
||||
void landAt(Coords loc);
|
||||
|
||||
bool updateActivePath(bool looping);
|
||||
void clearActivePath();
|
||||
void pushActivePathFront(Coords newActivePathFront);
|
||||
void pushActivePathBack(Coords newActivePathBack);
|
||||
void popActivePathFront();
|
||||
void goToDestination(string enrouteTask = "nil");
|
||||
bool isDestinationReached(double threshold);
|
||||
|
||||
string getTargetName();
|
||||
string getLeaderName();
|
||||
bool isTargetAlive();
|
||||
bool isLeaderAlive();
|
||||
|
||||
void resetTask();
|
||||
bool checkTaskFailed();
|
||||
void resetTaskFailedCounter();
|
||||
|
||||
void triggerUpdate(unsigned char datumIndex);
|
||||
|
||||
bool hasFreshData(unsigned long long time);
|
||||
bool checkFreshness(unsigned char datumIndex, unsigned long long time);
|
||||
|
||||
/********** Setters **********/
|
||||
virtual void setCategory(string newValue) { updateValue(category, newValue, DataIndex::category); }
|
||||
virtual void setAlive(bool newValue) { updateValue(alive, newValue, DataIndex::alive); }
|
||||
virtual void setHuman(bool newValue) { updateValue(human, newValue, DataIndex::human); }
|
||||
virtual void setControlled(bool newValue) { updateValue(controlled, newValue, DataIndex::controlled); }
|
||||
virtual void setCoalition(unsigned char newValue) { updateValue(coalition, newValue, DataIndex::coalition); }
|
||||
virtual void setCountry(unsigned char newValue) { updateValue(country, newValue, DataIndex::country); }
|
||||
virtual void setName(string newValue) { updateValue(name, newValue, DataIndex::name); }
|
||||
virtual void setUnitName(string newValue) { updateValue(unitName, newValue, DataIndex::unitName); }
|
||||
virtual void setGroupName(string newValue) { updateValue(groupName, newValue, DataIndex::groupName); }
|
||||
virtual void setState(unsigned char newValue) { updateValue(state, newValue, DataIndex::state); };
|
||||
virtual void setTask(string newValue) { updateValue(task, newValue, DataIndex::task); }
|
||||
virtual void setHasTask(bool newValue) { updateValue(hasTask, newValue, DataIndex::hasTask); }
|
||||
virtual void setPosition(Coords newValue) { updateValue(position, newValue, DataIndex::position); }
|
||||
virtual void setSpeed(double newValue) { updateValue(speed, newValue, DataIndex::speed); }
|
||||
virtual void setHeading(double newValue) { updateValue(heading, newValue, DataIndex::heading); }
|
||||
virtual void setIsTanker(bool newValue);
|
||||
virtual void setIsAWACS(bool newValue);
|
||||
virtual void setOnOff(bool newValue) { updateValue(onOff, newValue, DataIndex::onOff); };
|
||||
virtual void setFollowRoads(bool newValue) { updateValue(followRoads, newValue, DataIndex::followRoads); };
|
||||
virtual void setFuel(unsigned short newValue) { updateValue(fuel, newValue, DataIndex::fuel); }
|
||||
virtual void setDesiredSpeed(double newValue);
|
||||
virtual void setDesiredSpeedType(string newValue);
|
||||
virtual void setDesiredAltitude(double newValue);
|
||||
virtual void setDesiredAltitudeType(string newValue);
|
||||
virtual void setLeaderID(unsigned int newValue) { updateValue(leaderID, newValue, DataIndex::leaderID); }
|
||||
virtual void setFormationOffset(Offset formationOffset);
|
||||
virtual void setTargetID(unsigned int newValue) { updateValue(targetID, newValue, DataIndex::targetID); }
|
||||
virtual void setTargetPosition(Coords newValue) { updateValue(targetPosition, newValue, DataIndex::targetPosition); }
|
||||
virtual void setROE(unsigned char newValue, bool force = false);
|
||||
virtual void setReactionToThreat(unsigned char newValue, bool force = false);
|
||||
virtual void setEmissionsCountermeasures(unsigned char newValue, bool force = false);
|
||||
virtual void setTACAN(DataTypes::TACAN newValue, bool force = false);
|
||||
virtual void setRadio(DataTypes::Radio newValue, bool force = false);
|
||||
virtual void setGeneralSettings(DataTypes::GeneralSettings newValue, bool force = false);
|
||||
virtual void setAmmo(vector<DataTypes::Ammo> newValue);
|
||||
virtual void setContacts(vector<DataTypes::Contact> newValue);
|
||||
virtual void setActivePath(list<Coords> newValue);
|
||||
|
||||
/********** Getters **********/
|
||||
virtual string getCategory() { return category; };
|
||||
virtual bool getAlive() { return alive; }
|
||||
virtual bool getHuman() { return human; }
|
||||
virtual bool getControlled() { return controlled; }
|
||||
virtual unsigned char getCoalition() { return coalition; }
|
||||
virtual unsigned char getCountry() { return country; }
|
||||
virtual string getName() { return name; }
|
||||
virtual string getUnitName() { return unitName; }
|
||||
virtual string getGroupName() { return groupName; }
|
||||
virtual unsigned char getState() { return state; }
|
||||
virtual string getTask() { return task; }
|
||||
virtual bool getHasTask() { return hasTask; }
|
||||
virtual Coords getPosition() { return position; }
|
||||
virtual double getSpeed() { return speed; }
|
||||
virtual double getHeading() { return heading; }
|
||||
virtual bool getIsTanker() { return isTanker; }
|
||||
virtual bool getIsAWACS() { return isAWACS; }
|
||||
virtual bool getOnOff() { return onOff; };
|
||||
virtual bool getFollowRoads() { return followRoads; };
|
||||
virtual unsigned short getFuel() { return fuel; }
|
||||
virtual double getDesiredSpeed() { return desiredSpeed; };
|
||||
virtual bool getDesiredSpeedType() { return desiredSpeedType; };
|
||||
virtual double getDesiredAltitude() { return desiredAltitude; };
|
||||
virtual bool getDesiredAltitudeType() { return desiredAltitudeType; };
|
||||
virtual unsigned int getLeaderID() { return leaderID; }
|
||||
virtual Offset getFormationoffset() { return formationOffset; }
|
||||
virtual unsigned int getTargetID() { return targetID; }
|
||||
virtual Coords getTargetPosition() { return targetPosition; }
|
||||
virtual unsigned char getROE() { return ROE; }
|
||||
virtual unsigned char getReactionToThreat() { return reactionToThreat; }
|
||||
virtual unsigned char getEmissionsCountermeasures() { return emissionsCountermeasures; };
|
||||
virtual DataTypes::TACAN getTACAN() { return TACAN; }
|
||||
virtual DataTypes::Radio getRadio() { return radio; }
|
||||
virtual DataTypes::GeneralSettings getGeneralSettings() { return generalSettings; }
|
||||
virtual vector<DataTypes::Ammo> getAmmo() { return ammo; }
|
||||
virtual vector<DataTypes::Contact> getTargets() { return contacts; }
|
||||
virtual list<Coords> getActivePath() { return activePath; }
|
||||
|
||||
protected:
|
||||
int ID;
|
||||
unsigned int ID;
|
||||
|
||||
map<wstring, Measure*> measures;
|
||||
int taskCheckCounter = 0;
|
||||
|
||||
/********** Base data **********/
|
||||
string category;
|
||||
bool alive = false;
|
||||
bool human = false;
|
||||
bool controlled = false;
|
||||
wstring name = L"undefined";
|
||||
wstring unitName = L"undefined";
|
||||
wstring groupName = L"undefined";
|
||||
bool alive = true;
|
||||
json::value type = json::value::null();
|
||||
int country = NULL;
|
||||
|
||||
/********** Flight data **********/
|
||||
double latitude = NULL;
|
||||
double longitude = NULL;
|
||||
double altitude = NULL;
|
||||
unsigned char coalition = NULL;
|
||||
unsigned char country = NULL;
|
||||
string name = "";
|
||||
string unitName = "";
|
||||
string groupName = "";
|
||||
unsigned char state = State::NONE;
|
||||
string task = "";
|
||||
bool hasTask = false;
|
||||
Coords position = Coords(NULL);
|
||||
double speed = NULL;
|
||||
double heading = NULL;
|
||||
|
||||
/********** Mission data **********/
|
||||
double fuel = 0;
|
||||
double initialFuel = 0; // Used internally to detect refueling completed
|
||||
json::value ammo = json::value::null();
|
||||
json::value contacts = json::value::null();
|
||||
bool hasTask = false;
|
||||
wstring coalition = L"";
|
||||
json::value flags = json::value::null();
|
||||
|
||||
/********** Formation data **********/
|
||||
int leaderID = NULL;
|
||||
Offset formationOffset = Offset(NULL);
|
||||
|
||||
/********** Task data **********/
|
||||
wstring currentTask = L"";
|
||||
double desiredSpeed = 0;
|
||||
double desiredAltitude = 0;
|
||||
wstring desiredSpeedType = L"GS";
|
||||
wstring desiredAltitudeType = L"AGL";
|
||||
list<Coords> activePath;
|
||||
Coords activeDestination = Coords(NULL);
|
||||
int targetID = NULL;
|
||||
Coords targetLocation = Coords(NULL);
|
||||
bool isTanker = false;
|
||||
bool isAWACS = false;
|
||||
bool onOff = true;
|
||||
bool followRoads = false;
|
||||
|
||||
/********** Options data **********/
|
||||
wstring ROE = L"Designated";
|
||||
wstring reactionToThreat = L"Evade";
|
||||
wstring emissionsCountermeasures = L"Defend";
|
||||
Options::TACAN TACAN;
|
||||
Options::Radio radio;
|
||||
Options::GeneralSettings generalSettings;
|
||||
bool EPLRS = false;
|
||||
|
||||
/********** State machine **********/
|
||||
int state = State::NONE;
|
||||
unsigned short fuel = 0;
|
||||
double desiredSpeed = 0;
|
||||
bool desiredSpeedType = 1;
|
||||
double desiredAltitude = 0;
|
||||
bool desiredAltitudeType = 1;
|
||||
unsigned int leaderID = NULL;
|
||||
Offset formationOffset = Offset(NULL);
|
||||
unsigned int targetID = NULL;
|
||||
Coords targetPosition = Coords(NULL);
|
||||
unsigned char ROE = ROE::OPEN_FIRE_WEAPON_FREE;
|
||||
unsigned char reactionToThreat = ReactionToThreat::EVADE_FIRE;
|
||||
unsigned char emissionsCountermeasures = EmissionCountermeasure::DEFEND;
|
||||
DataTypes::TACAN TACAN;
|
||||
DataTypes::Radio radio;
|
||||
DataTypes::GeneralSettings generalSettings;
|
||||
vector<DataTypes::Ammo> ammo;
|
||||
vector<DataTypes::Contact> contacts;
|
||||
list<Coords> activePath;
|
||||
|
||||
/********** Other **********/
|
||||
Coords oldPosition = Coords(0); // Used to approximate speed
|
||||
unsigned int taskCheckCounter = 0;
|
||||
Coords activeDestination = Coords(NULL);
|
||||
double initialFuel = 0;
|
||||
Coords oldPosition = Coords(0);
|
||||
map<unsigned char, unsigned long long> updateTimeMap;
|
||||
|
||||
/********** Functions **********/
|
||||
wstring getTargetName();
|
||||
wstring getLeaderName();
|
||||
bool isTargetAlive();
|
||||
bool isLeaderAlive();
|
||||
/********** Private methods **********/
|
||||
virtual void AIloop() = 0;
|
||||
void addMeasure(wstring key, json::value value);
|
||||
bool isDestinationReached(double threshold);
|
||||
bool setActiveDestination();
|
||||
bool updateActivePath(bool looping);
|
||||
void goToDestination(wstring enrouteTask = L"nil");
|
||||
bool checkTaskFailed();
|
||||
void resetTaskFailedCounter();
|
||||
|
||||
void appendString(stringstream& ss, const unsigned char& datumIndex, const string& datumValue) {
|
||||
const unsigned short size = datumValue.size();
|
||||
ss.write((const char*)&datumIndex, sizeof(unsigned char));
|
||||
ss.write((const char*)&size, sizeof(unsigned short));
|
||||
ss << datumValue;
|
||||
}
|
||||
|
||||
/********** Template methods **********/
|
||||
template <typename T>
|
||||
void updateValue(T& value, T& newValue, unsigned char datumIndex)
|
||||
{
|
||||
if (newValue != value)
|
||||
{
|
||||
triggerUpdate(datumIndex);
|
||||
value = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void appendNumeric(stringstream& ss, const unsigned char& datumIndex, T& datumValue) {
|
||||
ss.write((const char*)&datumIndex, sizeof(unsigned char));
|
||||
ss.write((const char*)&datumValue, sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void appendVector(stringstream& ss, const unsigned char& datumIndex, vector<T>& datumValue) {
|
||||
const unsigned short size = datumValue.size();
|
||||
ss.write((const char*)&datumIndex, sizeof(unsigned char));
|
||||
ss.write((const char*)&size, sizeof(unsigned short));
|
||||
|
||||
for (auto& el : datumValue)
|
||||
ss.write((const char*)&el, sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void appendList(stringstream& ss, const unsigned char& datumIndex, list<T>& datumValue) {
|
||||
const unsigned short size = datumValue.size();
|
||||
ss.write((const char*)&datumIndex, sizeof(unsigned char));
|
||||
ss.write((const char*)&size, sizeof(unsigned short));
|
||||
|
||||
for (auto& el: datumValue)
|
||||
ss.write((const char*)&el, sizeof(T));
|
||||
}
|
||||
};
|
||||
|
||||
@ -10,21 +10,22 @@ public:
|
||||
UnitsManager(lua_State* L);
|
||||
~UnitsManager();
|
||||
|
||||
Unit* getUnit(int ID);
|
||||
map<unsigned int, Unit*>& getUnits() { return units; };
|
||||
Unit* getUnit(unsigned int ID);
|
||||
bool isUnitInGroup(Unit* unit);
|
||||
bool isUnitGroupLeader(Unit* unit);
|
||||
Unit* getGroupLeader(int ID);
|
||||
Unit* getGroupLeader(unsigned int ID);
|
||||
Unit* getGroupLeader(Unit* unit);
|
||||
vector<Unit*> getGroupMembers(wstring groupName);
|
||||
void updateExportData(lua_State* L);
|
||||
vector<Unit*> getGroupMembers(string groupName);
|
||||
void updateExportData(lua_State* L, double dt = 0);
|
||||
void updateMissionData(json::value missionData);
|
||||
void runAILoop();
|
||||
void getData(json::value& answer, long long time);
|
||||
void deleteUnit(int ID, bool explosion);
|
||||
void acquireControl(int ID);
|
||||
string getUnitData(stringstream &ss, unsigned long long time);
|
||||
void deleteUnit(unsigned int ID, bool explosion);
|
||||
void acquireControl(unsigned int ID);
|
||||
|
||||
private:
|
||||
map<int, Unit*> units;
|
||||
map<unsigned int, Unit*> units;
|
||||
json::value missionDB;
|
||||
|
||||
};
|
||||
|
||||
@ -4,10 +4,7 @@
|
||||
class Weapon : public Unit
|
||||
{
|
||||
public:
|
||||
Weapon(json::value json, int ID);
|
||||
|
||||
virtual wstring getCategory() = 0;
|
||||
|
||||
Weapon(json::value json, unsigned int ID);
|
||||
protected:
|
||||
/* Weapons are not controllable and have no AIloop */
|
||||
virtual void AIloop() {};
|
||||
@ -16,15 +13,11 @@ protected:
|
||||
class Missile : public Weapon
|
||||
{
|
||||
public:
|
||||
Missile(json::value json, int ID);
|
||||
|
||||
virtual wstring getCategory() { return L"Missile"; };
|
||||
Missile(json::value json, unsigned int ID);
|
||||
};
|
||||
|
||||
class Bomb : public Weapon
|
||||
{
|
||||
public:
|
||||
Bomb(json::value json, int ID);
|
||||
|
||||
virtual wstring getCategory() { return L"Bomb"; };
|
||||
Bomb(json::value json, unsigned int ID);
|
||||
};
|
||||
@ -13,24 +13,23 @@ extern Scheduler* scheduler;
|
||||
extern UnitsManager* unitsManager;
|
||||
|
||||
/* Aircraft */
|
||||
Aircraft::Aircraft(json::value json, int ID) : AirUnit(json, ID)
|
||||
Aircraft::Aircraft(json::value json, unsigned int ID) : AirUnit(json, ID)
|
||||
{
|
||||
log("New Aircraft created with ID: " + to_string(ID));
|
||||
addMeasure(L"category", json::value(getCategory()));
|
||||
|
||||
double desiredSpeed = knotsToMs(300);
|
||||
double desiredAltitude = ftToM(20000);
|
||||
setDesiredSpeed(desiredSpeed);
|
||||
setDesiredAltitude(desiredAltitude);
|
||||
setCategory("Aircraft");
|
||||
setDesiredSpeed(knotsToMs(300));
|
||||
setDesiredAltitude(ftToM(20000));
|
||||
|
||||
};
|
||||
|
||||
void Aircraft::changeSpeed(wstring change)
|
||||
void Aircraft::changeSpeed(string change)
|
||||
{
|
||||
if (change.compare(L"stop") == 0)
|
||||
if (change.compare("stop") == 0)
|
||||
setState(State::IDLE);
|
||||
else if (change.compare(L"slow") == 0)
|
||||
else if (change.compare("slow") == 0)
|
||||
setDesiredSpeed(getDesiredSpeed() - knotsToMs(25));
|
||||
else if (change.compare(L"fast") == 0)
|
||||
else if (change.compare("fast") == 0)
|
||||
setDesiredSpeed(getDesiredSpeed() + knotsToMs(25));
|
||||
|
||||
if (getDesiredSpeed() < knotsToMs(50))
|
||||
@ -42,16 +41,16 @@ void Aircraft::changeSpeed(wstring change)
|
||||
goToDestination(); /* Send the command to reach the destination */
|
||||
}
|
||||
|
||||
void Aircraft::changeAltitude(wstring change)
|
||||
void Aircraft::changeAltitude(string change)
|
||||
{
|
||||
if (change.compare(L"descend") == 0)
|
||||
if (change.compare("descend") == 0)
|
||||
{
|
||||
if (getDesiredAltitude() > 5000)
|
||||
setDesiredAltitude(getDesiredAltitude() - ftToM(2500));
|
||||
else if (getDesiredAltitude() > 0)
|
||||
setDesiredAltitude(getDesiredAltitude() - ftToM(500));
|
||||
}
|
||||
else if (change.compare(L"climb") == 0)
|
||||
else if (change.compare("climb") == 0)
|
||||
{
|
||||
if (getDesiredAltitude() > 5000)
|
||||
setDesiredAltitude(getDesiredAltitude() + ftToM(2500));
|
||||
|
||||
@ -13,12 +13,12 @@ extern Scheduler* scheduler;
|
||||
extern UnitsManager* unitsManager;
|
||||
|
||||
/* Air unit */
|
||||
AirUnit::AirUnit(json::value json, int ID) : Unit(json, ID)
|
||||
AirUnit::AirUnit(json::value json, unsigned int ID) : Unit(json, ID)
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
void AirUnit::setState(int newState)
|
||||
void AirUnit::setState(unsigned char newState)
|
||||
{
|
||||
/************ Perform any action required when LEAVING a state ************/
|
||||
if (newState != state) {
|
||||
@ -46,7 +46,7 @@ void AirUnit::setState(int newState)
|
||||
case State::BOMB_POINT:
|
||||
case State::CARPET_BOMB:
|
||||
case State::BOMB_BUILDING: {
|
||||
setTargetLocation(Coords(NULL));
|
||||
setTargetPosition(Coords(NULL));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -59,57 +59,48 @@ void AirUnit::setState(int newState)
|
||||
case State::IDLE: {
|
||||
clearActivePath();
|
||||
resetActiveDestination();
|
||||
addMeasure(L"currentState", json::value(L"Idle"));
|
||||
break;
|
||||
}
|
||||
case State::REACH_DESTINATION: {
|
||||
resetActiveDestination();
|
||||
addMeasure(L"currentState", json::value(L"Reach destination"));
|
||||
break;
|
||||
}
|
||||
case State::ATTACK: {
|
||||
if (isTargetAlive()) {
|
||||
Unit* target = unitsManager->getUnit(targetID);
|
||||
Coords targetPosition = Coords(target->getLatitude(), target->getLongitude(), 0);
|
||||
Coords targetPosition = Coords(target->getPosition().lat, target->getPosition().lng, 0);
|
||||
clearActivePath();
|
||||
pushActivePathFront(targetPosition);
|
||||
resetActiveDestination();
|
||||
addMeasure(L"currentState", json::value(L"Attack"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case State::FOLLOW: {
|
||||
clearActivePath();
|
||||
resetActiveDestination();
|
||||
addMeasure(L"currentState", json::value(L"Follow"));
|
||||
break;
|
||||
}
|
||||
case State::LAND: {
|
||||
resetActiveDestination();
|
||||
addMeasure(L"currentState", json::value(L"Land"));
|
||||
break;
|
||||
}
|
||||
case State::REFUEL: {
|
||||
initialFuel = fuel;
|
||||
clearActivePath();
|
||||
resetActiveDestination();
|
||||
addMeasure(L"currentState", json::value(L"Refuel"));
|
||||
break;
|
||||
}
|
||||
case State::BOMB_POINT: {
|
||||
addMeasure(L"currentState", json::value(L"Bombing point"));
|
||||
clearActivePath();
|
||||
resetActiveDestination();
|
||||
break;
|
||||
}
|
||||
case State::CARPET_BOMB: {
|
||||
addMeasure(L"currentState", json::value(L"Carpet bombing"));
|
||||
clearActivePath();
|
||||
resetActiveDestination();
|
||||
break;
|
||||
}
|
||||
case State::BOMB_BUILDING: {
|
||||
addMeasure(L"currentState", json::value(L"Bombing building"));
|
||||
clearActivePath();
|
||||
resetActiveDestination();
|
||||
break;
|
||||
@ -120,8 +111,10 @@ void AirUnit::setState(int newState)
|
||||
|
||||
resetTask();
|
||||
|
||||
log(unitName + L" setting state from " + to_wstring(state) + L" to " + to_wstring(newState));
|
||||
log(unitName + " setting state from " + to_string(state) + " to " + to_string(newState));
|
||||
state = newState;
|
||||
|
||||
triggerUpdate(DataIndex::state);
|
||||
}
|
||||
|
||||
void AirUnit::AIloop()
|
||||
@ -129,11 +122,11 @@ void AirUnit::AIloop()
|
||||
/* State machine */
|
||||
switch (state) {
|
||||
case State::IDLE: {
|
||||
currentTask = L"Idle";
|
||||
setTask("Idle");
|
||||
|
||||
if (!getHasTask())
|
||||
{
|
||||
std::wostringstream taskSS;
|
||||
std::ostringstream taskSS;
|
||||
if (isTanker) {
|
||||
taskSS << "{ [1] = { id = 'Tanker' }, [2] = { id = 'Orbit', pattern = 'Race-Track', altitude = " << desiredAltitude << ", speed = " << desiredSpeed << ", altitudeType = '" << desiredAltitudeType << "' } }";
|
||||
}
|
||||
@ -150,23 +143,23 @@ void AirUnit::AIloop()
|
||||
break;
|
||||
}
|
||||
case State::REACH_DESTINATION: {
|
||||
wstring enrouteTask = L"";
|
||||
string enrouteTask = "";
|
||||
bool looping = false;
|
||||
|
||||
if (isTanker)
|
||||
{
|
||||
enrouteTask = L"{ id = 'Tanker' }";
|
||||
currentTask = L"Tanker";
|
||||
enrouteTask = "{ id = 'Tanker' }";
|
||||
setTask("Tanker");
|
||||
}
|
||||
else if (isAWACS)
|
||||
{
|
||||
enrouteTask = L"{ id = 'AWACS' }";
|
||||
currentTask = L"AWACS";
|
||||
enrouteTask = "{ id = 'AWACS' }";
|
||||
setTask("AWACS");
|
||||
}
|
||||
else
|
||||
{
|
||||
enrouteTask = L"nil";
|
||||
currentTask = L"Reaching destination";
|
||||
enrouteTask = "nil";
|
||||
setTask("Reaching destination");
|
||||
}
|
||||
|
||||
if (activeDestination == NULL || !getHasTask())
|
||||
@ -187,8 +180,8 @@ void AirUnit::AIloop()
|
||||
break;
|
||||
}
|
||||
case State::LAND: {
|
||||
wstring enrouteTask = L"{ id = 'Land' }";
|
||||
currentTask = L"Landing";
|
||||
string enrouteTask = "{ id = 'Land' }";
|
||||
setTask("Landing");
|
||||
|
||||
if (activeDestination == NULL)
|
||||
{
|
||||
@ -206,13 +199,13 @@ void AirUnit::AIloop()
|
||||
|
||||
/* Attack state is an "enroute" task, meaning the unit will keep trying to attack even if a new destination is set. This is useful to
|
||||
manoeuvre the unit so that it can detect and engage the target. */
|
||||
std::wostringstream enrouteTaskSS;
|
||||
std::ostringstream enrouteTaskSS;
|
||||
enrouteTaskSS << "{"
|
||||
<< "id = 'EngageUnit'" << ","
|
||||
<< "targetID = " << targetID << ","
|
||||
<< "}";
|
||||
wstring enrouteTask = enrouteTaskSS.str();
|
||||
currentTask = L"Attacking " + getTargetName();
|
||||
string enrouteTask = enrouteTaskSS.str();
|
||||
setTask("Attacking " + getTargetName());
|
||||
|
||||
if (!getHasTask())
|
||||
{
|
||||
@ -232,13 +225,13 @@ void AirUnit::AIloop()
|
||||
break;
|
||||
}
|
||||
|
||||
currentTask = L"Following " + getTargetName();
|
||||
setTask("Following " + getTargetName());
|
||||
|
||||
Unit* leader = unitsManager->getUnit(leaderID);
|
||||
if (!getHasTask()) {
|
||||
if (leader != nullptr && leader->getAlive() && formationOffset != NULL)
|
||||
{
|
||||
std::wostringstream taskSS;
|
||||
std::ostringstream taskSS;
|
||||
taskSS << "{"
|
||||
<< "id = 'FollowUnit'" << ", "
|
||||
<< "leaderID = " << leader->getID() << ","
|
||||
@ -256,11 +249,11 @@ void AirUnit::AIloop()
|
||||
break;
|
||||
}
|
||||
case State::REFUEL: {
|
||||
currentTask = L"Refueling";
|
||||
setTask("Refueling");
|
||||
|
||||
if (!getHasTask()) {
|
||||
if (fuel <= initialFuel) {
|
||||
std::wostringstream taskSS;
|
||||
std::ostringstream taskSS;
|
||||
taskSS << "{"
|
||||
<< "id = 'Refuel'"
|
||||
<< "}";
|
||||
@ -274,22 +267,22 @@ void AirUnit::AIloop()
|
||||
}
|
||||
}
|
||||
case State::BOMB_POINT: {
|
||||
currentTask = L"Bombing point";
|
||||
setTask("Bombing point");
|
||||
|
||||
if (!getHasTask()) {
|
||||
std::wostringstream taskSS;
|
||||
taskSS << "{id = 'Bombing', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << "}";
|
||||
std::ostringstream taskSS;
|
||||
taskSS << "{id = 'Bombing', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << "}";
|
||||
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str()));
|
||||
scheduler->appendCommand(command);
|
||||
setHasTask(true);
|
||||
}
|
||||
}
|
||||
case State::CARPET_BOMB: {
|
||||
currentTask = L"Carpet bombing";
|
||||
setTask("Carpet bombing");
|
||||
|
||||
if (!getHasTask()) {
|
||||
std::wostringstream taskSS;
|
||||
taskSS << "{id = 'CarpetBombing', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << "}";
|
||||
std::ostringstream taskSS;
|
||||
taskSS << "{id = 'CarpetBombing', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << "}";
|
||||
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str()));
|
||||
scheduler->appendCommand(command);
|
||||
setHasTask(true);
|
||||
@ -297,11 +290,11 @@ void AirUnit::AIloop()
|
||||
break;
|
||||
}
|
||||
case State::BOMB_BUILDING: {
|
||||
currentTask = L"Bombing building";
|
||||
setTask("Bombing building");
|
||||
|
||||
if (!getHasTask()) {
|
||||
std::wostringstream taskSS;
|
||||
taskSS << "{id = 'AttackMapObject', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << "}";
|
||||
std::ostringstream taskSS;
|
||||
taskSS << "{id = 'AttackMapObject', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << "}";
|
||||
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str()));
|
||||
scheduler->appendCommand(command);
|
||||
setHasTask(true);
|
||||
@ -311,6 +304,4 @@ void AirUnit::AIloop()
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
addMeasure(L"currentTask", json::value(currentTask));
|
||||
}
|
||||
@ -7,10 +7,10 @@
|
||||
extern UnitsManager* unitsManager;
|
||||
|
||||
/* Move command */
|
||||
wstring Move::getString(lua_State* L)
|
||||
string Move::getString(lua_State* L)
|
||||
{
|
||||
|
||||
std::wostringstream commandSS;
|
||||
std::ostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
commandSS << "Olympus.move, "
|
||||
<< "\"" << groupName << "\"" << ", "
|
||||
@ -26,9 +26,9 @@ wstring Move::getString(lua_State* L)
|
||||
}
|
||||
|
||||
/* Smoke command */
|
||||
wstring Smoke::getString(lua_State* L)
|
||||
string Smoke::getString(lua_State* L)
|
||||
{
|
||||
std::wostringstream commandSS;
|
||||
std::ostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
commandSS << "Olympus.smoke, "
|
||||
<< "\"" << color << "\"" << ", "
|
||||
@ -38,9 +38,9 @@ wstring Smoke::getString(lua_State* L)
|
||||
}
|
||||
|
||||
/* Spawn ground command */
|
||||
wstring SpawnGroundUnit::getString(lua_State* L)
|
||||
string SpawnGroundUnit::getString(lua_State* L)
|
||||
{
|
||||
std::wostringstream commandSS;
|
||||
std::ostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
commandSS << "Olympus.spawnGroundUnit, "
|
||||
<< "\"" << coalition << "\"" << ", "
|
||||
@ -51,16 +51,16 @@ wstring SpawnGroundUnit::getString(lua_State* L)
|
||||
}
|
||||
|
||||
/* Spawn air command */
|
||||
wstring SpawnAircraft::getString(lua_State* L)
|
||||
string SpawnAircraft::getString(lua_State* L)
|
||||
{
|
||||
std::wostringstream optionsSS;
|
||||
std::ostringstream optionsSS;
|
||||
optionsSS.precision(10);
|
||||
optionsSS << "{"
|
||||
<< "payloadName = \"" << payloadName << "\", "
|
||||
<< "airbaseName = \"" << airbaseName << "\", "
|
||||
<< "}";
|
||||
|
||||
std::wostringstream commandSS;
|
||||
std::ostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
commandSS << "Olympus.spawnAircraft, "
|
||||
<< "\"" << coalition << "\"" << ", "
|
||||
@ -73,12 +73,12 @@ wstring SpawnAircraft::getString(lua_State* L)
|
||||
}
|
||||
|
||||
/* Clone unit command */
|
||||
wstring Clone::getString(lua_State* L)
|
||||
string Clone::getString(lua_State* L)
|
||||
{
|
||||
Unit* unit = unitsManager->getUnit(ID);
|
||||
if (unit != nullptr)
|
||||
{
|
||||
std::wostringstream commandSS;
|
||||
std::ostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
commandSS << "Olympus.clone, "
|
||||
<< ID << ", "
|
||||
@ -89,14 +89,14 @@ wstring Clone::getString(lua_State* L)
|
||||
}
|
||||
else
|
||||
{
|
||||
return L"";
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/* Delete unit command */
|
||||
wstring Delete::getString(lua_State* L)
|
||||
string Delete::getString(lua_State* L)
|
||||
{
|
||||
std::wostringstream commandSS;
|
||||
std::ostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
commandSS << "Olympus.delete, "
|
||||
<< ID << ", "
|
||||
@ -105,9 +105,9 @@ wstring Delete::getString(lua_State* L)
|
||||
}
|
||||
|
||||
/* Set task command */
|
||||
wstring SetTask::getString(lua_State* L)
|
||||
string SetTask::getString(lua_State* L)
|
||||
{
|
||||
std::wostringstream commandSS;
|
||||
std::ostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
commandSS << "Olympus.setTask, "
|
||||
<< "\"" << groupName << "\"" << ", "
|
||||
@ -117,9 +117,9 @@ wstring SetTask::getString(lua_State* L)
|
||||
}
|
||||
|
||||
/* Reset task command */
|
||||
wstring ResetTask::getString(lua_State* L)
|
||||
string ResetTask::getString(lua_State* L)
|
||||
{
|
||||
std::wostringstream commandSS;
|
||||
std::ostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
commandSS << "Olympus.resetTask, "
|
||||
<< "\"" << groupName << "\"";
|
||||
@ -128,9 +128,9 @@ wstring ResetTask::getString(lua_State* L)
|
||||
}
|
||||
|
||||
/* Set command command */
|
||||
wstring SetCommand::getString(lua_State* L)
|
||||
string SetCommand::getString(lua_State* L)
|
||||
{
|
||||
std::wostringstream commandSS;
|
||||
std::ostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
commandSS << "Olympus.setCommand, "
|
||||
<< "\"" << groupName << "\"" << ", "
|
||||
@ -140,9 +140,9 @@ wstring SetCommand::getString(lua_State* L)
|
||||
}
|
||||
|
||||
/* Set option command */
|
||||
wstring SetOption::getString(lua_State* L)
|
||||
string SetOption::getString(lua_State* L)
|
||||
{
|
||||
std::wostringstream commandSS;
|
||||
std::ostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
|
||||
if (!isBoolean) {
|
||||
@ -160,9 +160,9 @@ wstring SetOption::getString(lua_State* L)
|
||||
}
|
||||
|
||||
/* Set onOff command */
|
||||
wstring SetOnOff::getString(lua_State* L)
|
||||
string SetOnOff::getString(lua_State* L)
|
||||
{
|
||||
std::wostringstream commandSS;
|
||||
std::ostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
|
||||
commandSS << "Olympus.setOnOff, "
|
||||
@ -173,9 +173,9 @@ wstring SetOnOff::getString(lua_State* L)
|
||||
}
|
||||
|
||||
/* Explosion command */
|
||||
wstring Explosion::getString(lua_State* L)
|
||||
string Explosion::getString(lua_State* L)
|
||||
{
|
||||
std::wostringstream commandSS;
|
||||
std::ostringstream commandSS;
|
||||
commandSS.precision(10);
|
||||
commandSS << "Olympus.explosion, "
|
||||
<< intensity << ", "
|
||||
|
||||
@ -6,18 +6,29 @@
|
||||
#include "scheduler.h"
|
||||
#include "scriptLoader.h"
|
||||
#include "luatools.h"
|
||||
#include <chrono>
|
||||
using namespace std::chrono;
|
||||
|
||||
auto before = std::chrono::system_clock::now();
|
||||
|
||||
/* Singleton objects */
|
||||
UnitsManager* unitsManager = nullptr;
|
||||
Server* server = nullptr;
|
||||
Scheduler* scheduler = nullptr;
|
||||
|
||||
/* Data jsons */
|
||||
json::value airbases;
|
||||
json::value bullseyes;
|
||||
json::value mission;
|
||||
|
||||
mutex mutexLock;
|
||||
bool initialized = false;
|
||||
string sessionHash;
|
||||
|
||||
bool initialized = false;
|
||||
|
||||
unsigned int frameCounter = 0;
|
||||
double frameRate = 30;
|
||||
|
||||
/* Called when DCS simulation stops. All singleton instances are deleted. */
|
||||
extern "C" DllExport int coreDeinit(lua_State* L)
|
||||
{
|
||||
@ -58,25 +69,31 @@ extern "C" DllExport int coreFrame(lua_State* L)
|
||||
if (!initialized)
|
||||
return (0);
|
||||
|
||||
/* Lock for thread safety */
|
||||
lock_guard<mutex> guard(mutexLock);
|
||||
frameCounter++;
|
||||
|
||||
/* Slow down the update rate if the frameRate is very low since it means DCS is struggling to keep up */
|
||||
const std::chrono::duration<double> duration = std::chrono::system_clock::now() - before;
|
||||
|
||||
/* TODO make intervals editable */
|
||||
if (duration.count() > UPDATE_TIME_INTERVAL)
|
||||
if (duration.count() > UPDATE_TIME_INTERVAL * (60.0 / frameRate))
|
||||
{
|
||||
if (unitsManager != nullptr)
|
||||
{
|
||||
unitsManager->updateExportData(L);
|
||||
/* Lock for thread safety */
|
||||
lock_guard<mutex> guard(mutexLock);
|
||||
|
||||
milliseconds ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
|
||||
if (duration.count() > 0)
|
||||
frameRate = frameCounter / duration.count();
|
||||
frameCounter = 0;
|
||||
|
||||
if (unitsManager != nullptr) {
|
||||
unitsManager->updateExportData(L, duration.count());
|
||||
unitsManager->runAILoop();
|
||||
|
||||
}
|
||||
before = std::chrono::system_clock::now();
|
||||
}
|
||||
|
||||
if (scheduler != nullptr)
|
||||
scheduler->execute(L);
|
||||
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
@ -88,18 +105,27 @@ extern "C" DllExport int coreMissionData(lua_State * L)
|
||||
/* Lock for thread safety */
|
||||
lock_guard<mutex> guard(mutexLock);
|
||||
|
||||
lua_getglobal(L, "Olympus");
|
||||
lua_getfield(L, -1, "missionData");
|
||||
json::value missionData = luaTableToJSON(L, -1);
|
||||
try
|
||||
{
|
||||
lua_getglobal(L, "Olympus");
|
||||
lua_getfield(L, -1, "missionData");
|
||||
json::value missionData = luaTableToJSON(L, -1);
|
||||
|
||||
if (missionData.has_object_field(L"unitsData"))
|
||||
unitsManager->updateMissionData(missionData[L"unitsData"]);
|
||||
if (missionData.has_object_field(L"airbases"))
|
||||
airbases = missionData[L"airbases"];
|
||||
if (missionData.has_object_field(L"bullseyes"))
|
||||
bullseyes = missionData[L"bullseyes"];
|
||||
if (missionData.has_object_field(L"mission"))
|
||||
mission = missionData[L"mission"];
|
||||
if (missionData.has_object_field(L"unitsData"))
|
||||
unitsManager->updateMissionData(missionData[L"unitsData"]);
|
||||
if (missionData.has_object_field(L"airbases"))
|
||||
airbases = missionData[L"airbases"];
|
||||
if (missionData.has_object_field(L"bullseyes"))
|
||||
bullseyes = missionData[L"bullseyes"];
|
||||
if (missionData.has_object_field(L"mission"))
|
||||
mission = missionData[L"mission"];
|
||||
}
|
||||
catch (exception const& e)
|
||||
{
|
||||
log(e.what());
|
||||
}
|
||||
|
||||
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
30
src/core/src/datatypes.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
#include "datatypes.h"
|
||||
|
||||
bool operator==(const DataTypes::TACAN& lhs, const DataTypes::TACAN& rhs)
|
||||
{
|
||||
return lhs.isOn == rhs.isOn && lhs.channel == rhs.channel && lhs.XY == rhs.XY && strcmp(lhs.callsign, rhs.callsign) == 0;
|
||||
}
|
||||
|
||||
bool operator==(const DataTypes::Radio& lhs, const DataTypes::Radio& rhs)
|
||||
{
|
||||
return lhs.frequency == rhs.frequency && lhs.callsign == rhs.callsign && lhs.callsignNumber == rhs.callsignNumber;
|
||||
}
|
||||
|
||||
bool operator==(const DataTypes::GeneralSettings& lhs, const DataTypes::GeneralSettings& rhs)
|
||||
{
|
||||
return lhs.prohibitAA == rhs.prohibitAA && lhs.prohibitAfterburner == rhs.prohibitAfterburner && lhs.prohibitAG == rhs.prohibitAG &&
|
||||
lhs.prohibitAirWpn == rhs.prohibitAirWpn && lhs.prohibitJettison == rhs.prohibitJettison;
|
||||
}
|
||||
|
||||
bool operator==(const DataTypes::Ammo& lhs, const DataTypes::Ammo& rhs)
|
||||
{
|
||||
return lhs.category == rhs.category && lhs.guidance == rhs.guidance && lhs.missileCategory == rhs.missileCategory &&
|
||||
lhs.quantity == rhs.quantity && strcmp(lhs.name, rhs.name) == 0;
|
||||
}
|
||||
|
||||
bool operator==(const DataTypes::Contact& lhs, const DataTypes::Contact& rhs)
|
||||
{
|
||||
return lhs.detectionMethod == rhs.detectionMethod && lhs.ID == rhs.ID;
|
||||
}
|
||||
|
||||
|
||||
@ -13,16 +13,15 @@ extern Scheduler* scheduler;
|
||||
extern UnitsManager* unitsManager;
|
||||
|
||||
/* Ground unit */
|
||||
GroundUnit::GroundUnit(json::value json, int ID) : Unit(json, ID)
|
||||
GroundUnit::GroundUnit(json::value json, unsigned int ID) : Unit(json, ID)
|
||||
{
|
||||
log("New Ground Unit created with ID: " + to_string(ID));
|
||||
addMeasure(L"category", json::value(getCategory()));
|
||||
|
||||
double desiredSpeed = 10;
|
||||
setDesiredSpeed(desiredSpeed);
|
||||
setCategory("GroundUnit");
|
||||
setDesiredSpeed(10);
|
||||
};
|
||||
|
||||
void GroundUnit::setState(int newState)
|
||||
void GroundUnit::setState(unsigned char newState)
|
||||
{
|
||||
/************ Perform any action required when LEAVING a state ************/
|
||||
if (newState != state) {
|
||||
@ -34,7 +33,7 @@ void GroundUnit::setState(int newState)
|
||||
break;
|
||||
}
|
||||
case State::FIRE_AT_AREA: {
|
||||
setTargetLocation(Coords(NULL));
|
||||
setTargetPosition(Coords(NULL));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -47,16 +46,13 @@ void GroundUnit::setState(int newState)
|
||||
case State::IDLE: {
|
||||
clearActivePath();
|
||||
resetActiveDestination();
|
||||
addMeasure(L"currentState", json::value(L"Idle"));
|
||||
break;
|
||||
}
|
||||
case State::REACH_DESTINATION: {
|
||||
resetActiveDestination();
|
||||
addMeasure(L"currentState", json::value(L"Reach destination"));
|
||||
break;
|
||||
}
|
||||
case State::FIRE_AT_AREA: {
|
||||
addMeasure(L"currentState", json::value(L"Firing at area"));
|
||||
clearActivePath();
|
||||
resetActiveDestination();
|
||||
break;
|
||||
@ -67,24 +63,26 @@ void GroundUnit::setState(int newState)
|
||||
|
||||
resetTask();
|
||||
|
||||
log(unitName + L" setting state from " + to_wstring(state) + L" to " + to_wstring(newState));
|
||||
log(unitName + " setting state from " + to_string(state) + " to " + to_string(newState));
|
||||
state = newState;
|
||||
|
||||
triggerUpdate(DataIndex::state);
|
||||
}
|
||||
|
||||
void GroundUnit::AIloop()
|
||||
{
|
||||
switch (state) {
|
||||
case State::IDLE: {
|
||||
currentTask = L"Idle";
|
||||
setTask("Idle");
|
||||
if (getHasTask())
|
||||
resetTask();
|
||||
break;
|
||||
}
|
||||
case State::REACH_DESTINATION: {
|
||||
wstring enrouteTask = L"";
|
||||
string enrouteTask = "";
|
||||
bool looping = false;
|
||||
|
||||
std::wostringstream taskSS;
|
||||
std::ostringstream taskSS;
|
||||
taskSS << "{ id = 'FollowRoads', value = " << (getFollowRoads() ? "true" : "false") << " }";
|
||||
enrouteTask = taskSS.str();
|
||||
|
||||
@ -106,11 +104,11 @@ void GroundUnit::AIloop()
|
||||
break;
|
||||
}
|
||||
case State::FIRE_AT_AREA: {
|
||||
currentTask = L"Firing at area";
|
||||
setTask("Firing at area");
|
||||
|
||||
if (!getHasTask()) {
|
||||
std::wostringstream taskSS;
|
||||
taskSS << "{id = 'FireAtPoint', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << ", radius = 1000}";
|
||||
std::ostringstream taskSS;
|
||||
taskSS << "{id = 'FireAtPoint', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << ", radius = 1000}";
|
||||
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str()));
|
||||
scheduler->appendCommand(command);
|
||||
setHasTask(true);
|
||||
@ -119,17 +117,15 @@ void GroundUnit::AIloop()
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
addMeasure(L"currentTask", json::value(currentTask));
|
||||
}
|
||||
|
||||
void GroundUnit::changeSpeed(wstring change)
|
||||
void GroundUnit::changeSpeed(string change)
|
||||
{
|
||||
if (change.compare(L"stop") == 0)
|
||||
if (change.compare("stop") == 0)
|
||||
setState(State::IDLE);
|
||||
else if (change.compare(L"slow") == 0)
|
||||
else if (change.compare("slow") == 0)
|
||||
setDesiredSpeed(getDesiredSpeed() - knotsToMs(5));
|
||||
else if (change.compare(L"fast") == 0)
|
||||
else if (change.compare("fast") == 0)
|
||||
setDesiredSpeed(getDesiredSpeed() + knotsToMs(5));
|
||||
|
||||
if (getDesiredSpeed() < 0)
|
||||
|
||||
@ -13,27 +13,25 @@ extern Scheduler* scheduler;
|
||||
extern UnitsManager* unitsManager;
|
||||
|
||||
/* Helicopter */
|
||||
Helicopter::Helicopter(json::value json, int ID) : AirUnit(json, ID)
|
||||
Helicopter::Helicopter(json::value json, unsigned int ID) : AirUnit(json, ID)
|
||||
{
|
||||
log("New Helicopter created with ID: " + to_string(ID));
|
||||
addMeasure(L"category", json::value(getCategory()));
|
||||
|
||||
double desiredSpeed = knotsToMs(100);
|
||||
double desiredAltitude = ftToM(5000);
|
||||
setDesiredSpeed(desiredSpeed);
|
||||
setDesiredAltitude(desiredAltitude);
|
||||
setCategory("Helicopter");
|
||||
setDesiredSpeed(knotsToMs(100));
|
||||
setDesiredAltitude(ftToM(5000));
|
||||
};
|
||||
|
||||
void Helicopter::changeSpeed(wstring change)
|
||||
void Helicopter::changeSpeed(string change)
|
||||
{
|
||||
if (change.compare(L"stop") == 0)
|
||||
if (change.compare("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 */
|
||||
clearActivePath();
|
||||
}
|
||||
else if (change.compare(L"slow") == 0)
|
||||
else if (change.compare("slow") == 0)
|
||||
desiredSpeed -= knotsToMs(10);
|
||||
else if (change.compare(L"fast") == 0)
|
||||
else if (change.compare("fast") == 0)
|
||||
desiredSpeed += knotsToMs(10);
|
||||
if (desiredSpeed < 0)
|
||||
desiredSpeed = 0;
|
||||
@ -41,16 +39,16 @@ void Helicopter::changeSpeed(wstring change)
|
||||
goToDestination(); /* Send the command to reach the destination */
|
||||
}
|
||||
|
||||
void Helicopter::changeAltitude(wstring change)
|
||||
void Helicopter::changeAltitude(string change)
|
||||
{
|
||||
if (change.compare(L"descend") == 0)
|
||||
if (change.compare("descend") == 0)
|
||||
{
|
||||
if (desiredAltitude > 100)
|
||||
desiredAltitude -= ftToM(100);
|
||||
else if (desiredAltitude > 0)
|
||||
desiredAltitude -= ftToM(10);
|
||||
}
|
||||
else if (change.compare(L"climb") == 0)
|
||||
else if (change.compare("climb") == 0)
|
||||
{
|
||||
if (desiredAltitude > 100)
|
||||
desiredAltitude += ftToM(100);
|
||||
|
||||
@ -13,13 +13,12 @@ extern Scheduler* scheduler;
|
||||
extern UnitsManager* unitsManager;
|
||||
|
||||
/* Navy Unit */
|
||||
NavyUnit::NavyUnit(json::value json, int ID) : Unit(json, ID)
|
||||
NavyUnit::NavyUnit(json::value json, unsigned int ID) : Unit(json, ID)
|
||||
{
|
||||
log("New Navy Unit created with ID: " + to_string(ID));
|
||||
addMeasure(L"category", json::value(getCategory()));
|
||||
|
||||
double desiredSpeed = 10;
|
||||
setDesiredSpeed(desiredSpeed);
|
||||
setCategory("NavyUnit");
|
||||
setDesiredSpeed(10);
|
||||
};
|
||||
|
||||
void NavyUnit::AIloop()
|
||||
@ -27,17 +26,17 @@ void NavyUnit::AIloop()
|
||||
/* TODO */
|
||||
}
|
||||
|
||||
void NavyUnit::changeSpeed(wstring change)
|
||||
void NavyUnit::changeSpeed(string change)
|
||||
{
|
||||
if (change.compare(L"stop") == 0)
|
||||
if (change.compare("stop") == 0)
|
||||
{
|
||||
|
||||
}
|
||||
else if (change.compare(L"slow") == 0)
|
||||
else if (change.compare("slow") == 0)
|
||||
{
|
||||
|
||||
}
|
||||
else if (change.compare(L"fast") == 0)
|
||||
else if (change.compare("fast") == 0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
extern UnitsManager* unitsManager;
|
||||
|
||||
Scheduler::Scheduler(lua_State* L):
|
||||
Scheduler::Scheduler(lua_State* L) :
|
||||
load(0)
|
||||
{
|
||||
LogInfo(L, "Scheduler constructor called successfully");
|
||||
@ -25,106 +25,110 @@ void Scheduler::appendCommand(Command* command)
|
||||
|
||||
void Scheduler::execute(lua_State* L)
|
||||
{
|
||||
/* Decrease the active computation load. New commands can be sent only if the load has reached 0.
|
||||
/* Decrease the active computation load. New commands can be sent only if the load has reached 0.
|
||||
This is needed to avoid server lag. */
|
||||
if (load > 0) {
|
||||
load--;
|
||||
return;
|
||||
}
|
||||
|
||||
int priority = CommandPriority::HIGH;
|
||||
while (priority >= CommandPriority::LOW)
|
||||
{
|
||||
int priority = CommandPriority::IMMEDIATE;
|
||||
while (priority >= CommandPriority::LOW) {
|
||||
for (auto command : commands)
|
||||
{
|
||||
if (command->getPriority() == priority)
|
||||
{
|
||||
wstring commandString = L"Olympus.protectedCall(" + command->getString(L) + L")";
|
||||
if (dostring_in(L, "server", to_string(commandString)))
|
||||
log(L"Error executing command " + commandString);
|
||||
string commandString = "Olympus.protectedCall(" + command->getString(L) + ")";
|
||||
if (dostring_in(L, "server", (commandString)))
|
||||
log("Error executing command " + commandString);
|
||||
else
|
||||
log("Command '" + commandString + "' executed correctly, current load " + to_string(load));
|
||||
load = command->getLoad();
|
||||
commands.remove(command);
|
||||
return;
|
||||
}
|
||||
}
|
||||
priority--;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void Scheduler::handleRequest(wstring key, json::value value)
|
||||
void Scheduler::handleRequest(string key, json::value value)
|
||||
{
|
||||
Command* command = nullptr;
|
||||
|
||||
log(L"Received request with ID: " + key);
|
||||
if (key.compare(L"setPath") == 0)
|
||||
log("Received request with ID: " + key);
|
||||
log(value.serialize());
|
||||
if (key.compare("setPath") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
if (unit != nullptr)
|
||||
{
|
||||
wstring unitName = unit->getUnitName();
|
||||
string unitName = unit->getUnitName();
|
||||
json::value path = value[L"path"];
|
||||
list<Coords> newPath;
|
||||
for (int i = 1; i <= path.as_object().size(); i++)
|
||||
for (unsigned int i = 0; i < path.as_array().size(); i++)
|
||||
{
|
||||
wstring WP = to_wstring(i);
|
||||
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")");
|
||||
string WP = to_string(i);
|
||||
double lat = path[i][L"lat"].as_double();
|
||||
double lng = path[i][L"lng"].as_double();
|
||||
log(unitName + " set path destination " + WP + " (" + to_string(lat) + ", " + to_string(lng) + ")");
|
||||
Coords dest; dest.lat = lat; dest.lng = lng;
|
||||
newPath.push_back(dest);
|
||||
}
|
||||
|
||||
unit->setActivePath(newPath);
|
||||
unit->setState(State::REACH_DESTINATION);
|
||||
log(unitName + L" new path set successfully");
|
||||
log(unitName + " new path set successfully");
|
||||
}
|
||||
}
|
||||
else if (key.compare(L"smoke") == 0)
|
||||
else if (key.compare("smoke") == 0)
|
||||
{
|
||||
wstring color = value[L"color"].as_string();
|
||||
string color = to_string(value[L"color"]);
|
||||
double lat = value[L"location"][L"lat"].as_double();
|
||||
double lng = value[L"location"][L"lng"].as_double();
|
||||
log(L"Adding " + color + L" smoke at (" + to_wstring(lat) + L", " + to_wstring(lng) + L")");
|
||||
log("Adding " + color + " smoke at (" + to_string(lat) + ", " + to_string(lng) + ")");
|
||||
Coords loc; loc.lat = lat; loc.lng = lng;
|
||||
command = dynamic_cast<Command*>(new Smoke(color, loc));
|
||||
}
|
||||
else if (key.compare(L"spawnGround") == 0)
|
||||
else if (key.compare("spawnGround") == 0)
|
||||
{
|
||||
wstring coalition = value[L"coalition"].as_string();
|
||||
wstring type = value[L"type"].as_string();
|
||||
bool immediate = value[L"immediate"].as_bool();
|
||||
string coalition = to_string(value[L"coalition"]);
|
||||
string type = to_string(value[L"type"]);
|
||||
double lat = value[L"location"][L"lat"].as_double();
|
||||
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")");
|
||||
log("Spawning " + coalition + " ground unit of type " + type + " at (" + to_string(lat) + ", " + to_string(lng) + ")");
|
||||
Coords loc; loc.lat = lat; loc.lng = lng;
|
||||
command = dynamic_cast<Command*>(new SpawnGroundUnit(coalition, type, loc));
|
||||
command = dynamic_cast<Command*>(new SpawnGroundUnit(coalition, type, loc, immediate));
|
||||
}
|
||||
else if (key.compare(L"spawnAir") == 0)
|
||||
else if (key.compare("spawnAir") == 0)
|
||||
{
|
||||
wstring coalition = value[L"coalition"].as_string();
|
||||
wstring type = value[L"type"].as_string();
|
||||
bool immediate = value[L"immediate"].as_bool();
|
||||
string coalition = to_string(value[L"coalition"]);
|
||||
string type = to_string(value[L"type"]);
|
||||
double lat = value[L"location"][L"lat"].as_double();
|
||||
double lng = value[L"location"][L"lng"].as_double();
|
||||
double altitude = value[L"altitude"].as_double();
|
||||
Coords loc; loc.lat = lat; loc.lng = lng; loc.alt = altitude;
|
||||
wstring payloadName = value[L"payloadName"].as_string();
|
||||
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 SpawnAircraft(coalition, type, loc, payloadName, airbaseName));
|
||||
string payloadName = to_string(value[L"payloadName"]);
|
||||
string airbaseName = to_string(value[L"airbaseName"]);
|
||||
log("Spawning " + coalition + " air unit of type " + type + " with payload " + payloadName + " at (" + to_string(lat) + ", " + to_string(lng) + " " + airbaseName + ")");
|
||||
command = dynamic_cast<Command*>(new SpawnAircraft(coalition, type, loc, payloadName, airbaseName, immediate));
|
||||
}
|
||||
else if (key.compare(L"attackUnit") == 0)
|
||||
else if (key.compare("attackUnit") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
int targetID = value[L"targetID"].as_integer();
|
||||
unsigned int targetID = value[L"targetID"].as_integer();
|
||||
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
Unit* target = unitsManager->getUnit(targetID);
|
||||
|
||||
wstring unitName;
|
||||
wstring targetName;
|
||||
|
||||
string unitName;
|
||||
string targetName;
|
||||
|
||||
if (unit != nullptr)
|
||||
unitName = unit->getUnitName();
|
||||
else
|
||||
@ -135,24 +139,24 @@ void Scheduler::handleRequest(wstring key, json::value value)
|
||||
else
|
||||
return;
|
||||
|
||||
log(L"Unit " + unitName + L" attacking unit " + targetName);
|
||||
log("Unit " + unitName + " attacking unit " + targetName);
|
||||
unit->setTargetID(targetID);
|
||||
unit->setState(State::ATTACK);
|
||||
}
|
||||
else if (key.compare(L"followUnit") == 0)
|
||||
else if (key.compare("followUnit") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
int leaderID = value[L"targetID"].as_integer();
|
||||
int offsetX = value[L"offsetX"].as_integer();
|
||||
int offsetY = value[L"offsetY"].as_integer();
|
||||
int offsetZ = value[L"offsetZ"].as_integer();
|
||||
unsigned int leaderID = value[L"targetID"].as_double();
|
||||
double offsetX = value[L"offsetX"].as_double();
|
||||
double offsetY = value[L"offsetY"].as_double();
|
||||
double offsetZ = value[L"offsetZ"].as_double();
|
||||
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
Unit* leader = unitsManager->getUnit(leaderID);
|
||||
|
||||
wstring unitName;
|
||||
wstring leaderName;
|
||||
string unitName;
|
||||
string leaderName;
|
||||
|
||||
if (unit != nullptr)
|
||||
unitName = unit->getUnitName();
|
||||
@ -164,95 +168,95 @@ void Scheduler::handleRequest(wstring key, json::value value)
|
||||
else
|
||||
return;
|
||||
|
||||
log(L"Unit " + unitName + L" following unit " + leaderName);
|
||||
log("Unit " + unitName + " following unit " + leaderName);
|
||||
unit->setFormationOffset(Offset(offsetX, offsetY, offsetZ));
|
||||
unit->setLeaderID(leaderID);
|
||||
unit->setState(State::FOLLOW);
|
||||
}
|
||||
else if (key.compare(L"changeSpeed") == 0)
|
||||
else if (key.compare("changeSpeed") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
if (unit != nullptr)
|
||||
unit->changeSpeed(value[L"change"].as_string());
|
||||
unit->changeSpeed(to_string(value[L"change"]));
|
||||
}
|
||||
else if (key.compare(L"changeAltitude") == 0)
|
||||
else if (key.compare("changeAltitude") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
if (unit != nullptr)
|
||||
unit->changeAltitude(value[L"change"].as_string());
|
||||
unit->changeAltitude(to_string(value[L"change"]));
|
||||
}
|
||||
else if (key.compare(L"setSpeed") == 0)
|
||||
else if (key.compare("setSpeed") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
if (unit != nullptr)
|
||||
unit->setDesiredSpeed(value[L"speed"].as_double());
|
||||
}
|
||||
else if (key.compare(L"setSpeedType") == 0)
|
||||
else if (key.compare("setSpeedType") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
if (unit != nullptr)
|
||||
unit->setDesiredSpeedType(value[L"speedType"].as_string());
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
if (unit != nullptr)
|
||||
unit->setDesiredSpeedType(to_string(value[L"speedType"]));
|
||||
}
|
||||
else if (key.compare(L"setAltitude") == 0)
|
||||
else if (key.compare("setAltitude") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
if (unit != nullptr)
|
||||
unit->setDesiredAltitude(value[L"altitude"].as_double());
|
||||
}
|
||||
else if (key.compare(L"setAltitudeType") == 0)
|
||||
else if (key.compare("setAltitudeType") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
if (unit != nullptr)
|
||||
unit->setDesiredAltitudeType(value[L"altitudeType"].as_string());
|
||||
unit->setDesiredAltitudeType(to_string(value[L"altitudeType"]));
|
||||
}
|
||||
else if (key.compare(L"cloneUnit") == 0)
|
||||
else if (key.compare("cloneUnit") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
double lat = value[L"location"][L"lat"].as_double();
|
||||
double lng = value[L"location"][L"lng"].as_double();
|
||||
Coords loc; loc.lat = lat; loc.lng = lng;
|
||||
command = dynamic_cast<Command*>(new Clone(ID, loc));
|
||||
log(L"Cloning unit " + to_wstring(ID));
|
||||
log("Cloning unit " + to_string(ID));
|
||||
}
|
||||
else if (key.compare(L"setROE") == 0)
|
||||
else if (key.compare("setROE") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
wstring ROE = value[L"ROE"].as_string();
|
||||
unsigned char ROE = value[L"ROE"].as_number().to_uint32();
|
||||
unit->setROE(ROE);
|
||||
}
|
||||
else if (key.compare(L"setReactionToThreat") == 0)
|
||||
else if (key.compare("setReactionToThreat") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
wstring reactionToThreat = value[L"reactionToThreat"].as_string();
|
||||
unsigned char reactionToThreat = value[L"reactionToThreat"].as_number().to_uint32();
|
||||
unit->setReactionToThreat(reactionToThreat);
|
||||
}
|
||||
else if (key.compare(L"setEmissionsCountermeasures") == 0)
|
||||
else if (key.compare("setEmissionsCountermeasures") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
wstring emissionsCountermeasures = value[L"emissionsCountermeasures"].as_string();
|
||||
unsigned char emissionsCountermeasures = value[L"emissionsCountermeasures"].as_number().to_uint32();
|
||||
unit->setEmissionsCountermeasures(emissionsCountermeasures);
|
||||
}
|
||||
else if (key.compare(L"landAt") == 0)
|
||||
else if (key.compare("landAt") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
double lat = value[L"location"][L"lat"].as_double();
|
||||
@ -260,22 +264,22 @@ void Scheduler::handleRequest(wstring key, json::value value)
|
||||
Coords loc; loc.lat = lat; loc.lng = lng;
|
||||
unit->landAt(loc);
|
||||
}
|
||||
else if (key.compare(L"deleteUnit") == 0)
|
||||
else if (key.compare("deleteUnit") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
bool explosion = value[L"explosion"].as_bool();
|
||||
unitsManager->deleteUnit(ID, explosion);
|
||||
}
|
||||
else if (key.compare(L"refuel") == 0)
|
||||
else if (key.compare("refuel") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
unit->setState(State::REFUEL);
|
||||
}
|
||||
else if (key.compare(L"setAdvancedOptions") == 0)
|
||||
else if (key.compare("setAdvancedOptions") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
if (unit != nullptr)
|
||||
@ -285,18 +289,21 @@ void Scheduler::handleRequest(wstring key, json::value value)
|
||||
unit->setIsAWACS(value[L"isAWACS"].as_bool());
|
||||
|
||||
/* TACAN Options */
|
||||
auto TACAN = value[L"TACAN"];
|
||||
unit->setTACAN({ TACAN[L"isOn"].as_bool(),
|
||||
TACAN[L"channel"].as_number().to_int32(),
|
||||
TACAN[L"XY"].as_string(),
|
||||
TACAN[L"callsign"].as_string()
|
||||
});
|
||||
DataTypes::TACAN TACAN;
|
||||
TACAN.isOn = value[L"TACAN"][L"isOn"].as_bool();
|
||||
TACAN.channel = static_cast<unsigned char>(value[L"TACAN"][L"channel"].as_number().to_uint32());
|
||||
TACAN.XY = to_string(value[L"TACAN"][L"XY"]).at(0);
|
||||
string callsign = to_string(value[L"TACAN"][L"callsign"]);
|
||||
if (callsign.length() > 3)
|
||||
callsign = callsign.substr(0, 3);
|
||||
strcpy_s(TACAN.callsign, 4, callsign.c_str());
|
||||
unit->setTACAN(TACAN);
|
||||
|
||||
/* Radio Options */
|
||||
auto radio = value[L"radio"];
|
||||
unit->setRadio({ radio[L"frequency"].as_number().to_int32(),
|
||||
radio[L"callsign"].as_number().to_int32(),
|
||||
radio[L"callsignNumber"].as_number().to_int32()
|
||||
unit->setRadio({ radio[L"frequency"].as_number().to_uint32(),
|
||||
static_cast<unsigned char>(radio[L"callsign"].as_number().to_uint32()),
|
||||
static_cast<unsigned char>(radio[L"callsignNumber"].as_number().to_uint32())
|
||||
});
|
||||
|
||||
/* General Settings */
|
||||
@ -311,83 +318,84 @@ void Scheduler::handleRequest(wstring key, json::value value)
|
||||
unit->resetActiveDestination();
|
||||
}
|
||||
}
|
||||
else if (key.compare(L"setFollowRoads") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
else if (key.compare("setFollowRoads") == 0)
|
||||
{
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
bool followRoads = value[L"followRoads"].as_bool();
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
unit->setFollowRoads(followRoads);
|
||||
}
|
||||
else if (key.compare(L"setOnOff") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
else if (key.compare("setOnOff") == 0)
|
||||
{
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
bool onOff = value[L"onOff"].as_bool();
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
unit->setOnOff(onOff);
|
||||
}
|
||||
else if (key.compare(L"explosion") == 0)
|
||||
else if (key.compare("explosion") == 0)
|
||||
{
|
||||
int intensity = value[L"intensity"].as_integer();
|
||||
unsigned int intensity = value[L"intensity"].as_integer();
|
||||
double lat = value[L"location"][L"lat"].as_double();
|
||||
double lng = value[L"location"][L"lng"].as_double();
|
||||
log(L"Adding " + to_wstring(intensity) + L" explosion at (" + to_wstring(lat) + L", " + to_wstring(lng) + L")");
|
||||
log("Adding " + to_string(intensity) + " explosion at (" + to_string(lat) + ", " + to_string(lng) + ")");
|
||||
Coords loc; loc.lat = lat; loc.lng = lng;
|
||||
command = dynamic_cast<Command*>(new Explosion(intensity, loc));
|
||||
}
|
||||
else if (key.compare(L"bombPoint") == 0)
|
||||
else if (key.compare("bombPoint") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
double lat = value[L"location"][L"lat"].as_double();
|
||||
double lng = value[L"location"][L"lng"].as_double();
|
||||
Coords loc; loc.lat = lat; loc.lng = lng;
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
unit->setState(State::BOMB_POINT);
|
||||
unit->setTargetLocation(loc);
|
||||
unit->setTargetPosition(loc);
|
||||
}
|
||||
else if (key.compare(L"carpetBomb") == 0)
|
||||
else if (key.compare("carpetBomb") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
double lat = value[L"location"][L"lat"].as_double();
|
||||
double lng = value[L"location"][L"lng"].as_double();
|
||||
Coords loc; loc.lat = lat; loc.lng = lng;
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
unit->setState(State::CARPET_BOMB);
|
||||
unit->setTargetLocation(loc);
|
||||
unit->setTargetPosition(loc);
|
||||
}
|
||||
else if (key.compare(L"bombBuilding") == 0)
|
||||
else if (key.compare("bombBuilding") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
double lat = value[L"location"][L"lat"].as_double();
|
||||
double lng = value[L"location"][L"lng"].as_double();
|
||||
Coords loc; loc.lat = lat; loc.lng = lng;
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
unit->setState(State::BOMB_BUILDING);
|
||||
unit->setTargetLocation(loc);
|
||||
unit->setTargetPosition(loc);
|
||||
}
|
||||
else if (key.compare(L"fireAtArea") == 0)
|
||||
else if (key.compare("fireAtArea") == 0)
|
||||
{
|
||||
int ID = value[L"ID"].as_integer();
|
||||
unsigned int ID = value[L"ID"].as_integer();
|
||||
unitsManager->acquireControl(ID);
|
||||
double lat = value[L"location"][L"lat"].as_double();
|
||||
double lng = value[L"location"][L"lng"].as_double();
|
||||
Coords loc; loc.lat = lat; loc.lng = lng;
|
||||
Unit* unit = unitsManager->getGroupLeader(ID);
|
||||
unit->setState(State::FIRE_AT_AREA);
|
||||
unit->setTargetLocation(loc);
|
||||
unit->setTargetPosition(loc);
|
||||
}
|
||||
else
|
||||
{
|
||||
log(L"Unknown command: " + key);
|
||||
log("Unknown command: " + key);
|
||||
}
|
||||
|
||||
|
||||
if (command != nullptr)
|
||||
{
|
||||
appendCommand(command);
|
||||
log("New command appended correctly to stack. Current server load: " + to_string(load));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,10 +13,12 @@ bool executeLuaScript(lua_State* L, string path)
|
||||
if (dostring_in(L, "server", str.c_str()) != 0)
|
||||
{
|
||||
log("Error registering " + path);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
log(path + " registered successfully");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@ Server::Server(lua_State* L):
|
||||
serverThread(nullptr),
|
||||
runListener(true)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
void Server::start(lua_State* L)
|
||||
@ -69,9 +69,11 @@ void Server::handle_get(http_request request)
|
||||
/* Lock for thread safety */
|
||||
lock_guard<mutex> guard(mutexLock);
|
||||
|
||||
milliseconds ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
|
||||
|
||||
http_response response(status_codes::OK);
|
||||
string authorization = to_base64("admin:" + to_string(password));
|
||||
if (password == L"" || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second == L"Basic " + to_wstring(authorization)))
|
||||
string authorization = to_base64("admin:" + password);
|
||||
if (password.length() == 0 || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second.compare(L"Basic " + to_wstring(authorization)) == 0))
|
||||
{
|
||||
std::exception_ptr eptr;
|
||||
try {
|
||||
@ -80,7 +82,8 @@ void Server::handle_get(http_request request)
|
||||
|
||||
if (path.size() > 0)
|
||||
{
|
||||
if (path[0] == UNITS_URI)
|
||||
string URI = to_string(path[0]);
|
||||
if (URI.compare(UNITS_URI) == 0)
|
||||
{
|
||||
map<utility::string_t, utility::string_t> query = request.relative_uri().split_query(request.relative_uri().query());
|
||||
long long time = 0;
|
||||
@ -93,27 +96,33 @@ void Server::handle_get(http_request request)
|
||||
time = 0;
|
||||
}
|
||||
}
|
||||
unitsManager->getData(answer, time);
|
||||
}
|
||||
else if (path[0] == LOGS_URI)
|
||||
{
|
||||
auto logs = json::value::object();
|
||||
getLogsJSON(logs, 100); // By reference, for thread safety. Get the last 100 log entries
|
||||
answer[L"logs"] = logs;
|
||||
}
|
||||
else if (path[0] == AIRBASES_URI)
|
||||
answer[L"airbases"] = airbases;
|
||||
else if (path[0] == BULLSEYE_URI)
|
||||
answer[L"bullseyes"] = bullseyes;
|
||||
else if (path[0] == MISSION_URI)
|
||||
answer[L"mission"] = mission;
|
||||
unsigned long long updateTime = ms.count();
|
||||
|
||||
milliseconds ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
|
||||
answer[L"time"] = json::value::string(to_wstring(ms.count()));
|
||||
answer[L"sessionHash"] = json::value::string(to_wstring(sessionHash));
|
||||
stringstream ss;
|
||||
ss.write((char*)&updateTime, sizeof(updateTime));
|
||||
unitsManager->getUnitData(ss, time);
|
||||
response.set_body(concurrency::streams::bytestream::open_istream(ss.str()));
|
||||
}
|
||||
else {
|
||||
if (URI.compare(LOGS_URI) == 0)
|
||||
{
|
||||
auto logs = json::value::object();
|
||||
getLogsJSON(logs, 100); // By reference, for thread safety. Get the last 100 log entries
|
||||
answer[L"logs"] = logs;
|
||||
}
|
||||
else if (URI.compare(AIRBASES_URI) == 0)
|
||||
answer[L"airbases"] = airbases;
|
||||
else if (URI.compare(BULLSEYE_URI) == 0)
|
||||
answer[L"bullseyes"] = bullseyes;
|
||||
else if (URI.compare(MISSION_URI) == 0)
|
||||
answer[L"mission"] = mission;
|
||||
|
||||
|
||||
answer[L"time"] = json::value::string(to_wstring(ms.count()));
|
||||
answer[L"sessionHash"] = json::value::string(to_wstring(sessionHash));
|
||||
response.set_body(answer);
|
||||
}
|
||||
}
|
||||
|
||||
response.set_body(answer);
|
||||
}
|
||||
catch (...) {
|
||||
eptr = std::current_exception(); // capture
|
||||
@ -135,8 +144,8 @@ void Server::handle_get(http_request request)
|
||||
void Server::handle_request(http_request request, function<void(json::value const&, json::value&)> action)
|
||||
{
|
||||
http_response response(status_codes::OK);
|
||||
string authorization = to_base64("admin:" + to_string(password));
|
||||
if (password == L"" || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second == L"Basic " + to_wstring(authorization)))
|
||||
string authorization = to_base64("admin:" + password);
|
||||
if (password.length() == 0 || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second.compare(L"Basic " + to_wstring(authorization)) == 0))
|
||||
{
|
||||
auto answer = json::value::object();
|
||||
request.extract_json().then([&answer, &action](pplx::task<json::value> task)
|
||||
@ -144,11 +153,8 @@ void Server::handle_request(http_request request, function<void(json::value cons
|
||||
try
|
||||
{
|
||||
auto const& jvalue = task.get();
|
||||
|
||||
if (!jvalue.is_null())
|
||||
{
|
||||
action(jvalue, answer);
|
||||
}
|
||||
}
|
||||
catch (http_exception const& e)
|
||||
{
|
||||
@ -184,7 +190,7 @@ void Server::handle_put(http_request request)
|
||||
auto value = e.second;
|
||||
std::exception_ptr eptr;
|
||||
try {
|
||||
scheduler->handleRequest(key, value);
|
||||
scheduler->handleRequest(to_string(key), value);
|
||||
}
|
||||
catch (...) {
|
||||
eptr = std::current_exception(); // capture
|
||||
@ -196,8 +202,8 @@ void Server::handle_put(http_request request)
|
||||
|
||||
void Server::task()
|
||||
{
|
||||
wstring address = wstring(REST_ADDRESS);
|
||||
wstring modLocation;
|
||||
string address = REST_ADDRESS;
|
||||
string modLocation;
|
||||
char* buf = nullptr;
|
||||
size_t sz = 0;
|
||||
if (_dupenv_s(&buf, &sz, "DCSOLYMPUS_PATH") == 0 && buf != nullptr)
|
||||
@ -206,31 +212,31 @@ void Server::task()
|
||||
std::stringstream ss;
|
||||
ss << ifstream.rdbuf();
|
||||
std::error_code errorCode;
|
||||
json::value config = json::value::parse(to_wstring(ss.str()), errorCode);
|
||||
json::value config = json::value::parse(ss.str(), errorCode);
|
||||
if (config.is_object() && config.has_object_field(L"server") &&
|
||||
config[L"server"].has_string_field(L"address") && config[L"server"].has_number_field(L"port"))
|
||||
{
|
||||
address = L"http://" + config[L"server"][L"address"].as_string() + L":" + to_wstring(config[L"server"][L"port"].as_number().to_int32());
|
||||
log(L"Starting server on " + address);
|
||||
address = "http://" + to_string(config[L"server"][L"address"]) + ":" + to_string(config[L"server"][L"port"].as_number().to_int32());
|
||||
log("Starting server on " + address);
|
||||
}
|
||||
else
|
||||
log(L"Error reading configuration file. Starting server on " + address);
|
||||
log("Error reading configuration file. Starting server on " + address);
|
||||
|
||||
if (config.is_object() && config.has_object_field(L"authentication") &&
|
||||
config[L"authentication"].has_string_field(L"password"))
|
||||
{
|
||||
password = config[L"authentication"][L"password"].as_string();
|
||||
password = to_string(config[L"authentication"][L"password"]);
|
||||
}
|
||||
else
|
||||
log(L"Error reading configuration file. No password set.");
|
||||
log("Error reading configuration file. No password set.");
|
||||
free(buf);
|
||||
}
|
||||
else
|
||||
{
|
||||
log(L"DCSOLYMPUS_PATH environment variable is missing, starting server on " + address);
|
||||
log("DCSOLYMPUS_PATH environment variable is missing, starting server on " + address);
|
||||
}
|
||||
|
||||
http_listener listener(address + L"/" + wstring(REST_URI));
|
||||
http_listener listener(to_wstring(address + "/" + REST_URI));
|
||||
|
||||
std::function<void(http_request)> handle_options = std::bind(&Server::handle_options, this, std::placeholders::_1);
|
||||
std::function<void(http_request)> handle_get = std::bind(&Server::handle_get, this, std::placeholders::_1);
|
||||
|
||||
@ -15,24 +15,7 @@ using namespace GeographicLib;
|
||||
extern Scheduler* scheduler;
|
||||
extern UnitsManager* unitsManager;
|
||||
|
||||
// TODO: Make dedicated file
|
||||
bool operator==(const Options::TACAN& lhs, const Options::TACAN& rhs)
|
||||
{
|
||||
return lhs.isOn == rhs.isOn && lhs.channel == rhs.channel && lhs.XY == rhs.XY && lhs.callsign == rhs.callsign;
|
||||
}
|
||||
|
||||
bool operator==(const Options::Radio& lhs, const Options::Radio& rhs)
|
||||
{
|
||||
return lhs.frequency == rhs.frequency && lhs.callsign == rhs.callsign && lhs.callsignNumber == rhs.callsignNumber;
|
||||
}
|
||||
|
||||
bool operator==(const Options::GeneralSettings& lhs, const Options::GeneralSettings& rhs)
|
||||
{
|
||||
return lhs.prohibitAA == rhs.prohibitAA && lhs.prohibitAfterburner == rhs.prohibitAfterburner && lhs.prohibitAG == rhs.prohibitAG &&
|
||||
lhs.prohibitAirWpn == rhs.prohibitAirWpn && lhs.prohibitJettison == rhs.prohibitJettison;
|
||||
}
|
||||
|
||||
Unit::Unit(json::value json, int ID) :
|
||||
Unit::Unit(json::value json, unsigned int ID) :
|
||||
ID(ID)
|
||||
{
|
||||
log("Creating unit with ID: " + to_string(ID));
|
||||
@ -45,215 +28,259 @@ Unit::~Unit()
|
||||
|
||||
void Unit::initialize(json::value json)
|
||||
{
|
||||
if (json.has_string_field(L"Name"))
|
||||
setName(to_string(json[L"Name"]));
|
||||
if (json.has_string_field(L"UnitName"))
|
||||
setUnitName(to_string(json[L"UnitName"]));
|
||||
if (json.has_string_field(L"GroupName"))
|
||||
setGroupName(to_string(json[L"GroupName"]));
|
||||
if (json.has_number_field(L"Country"))
|
||||
setCountry(json[L"Country"].as_number().to_int32());
|
||||
if (json.has_number_field(L"CoalitionID"))
|
||||
setCoalition(json[L"CoalitionID"].as_number().to_int32());
|
||||
|
||||
if (json.has_object_field(L"Flags"))
|
||||
setHuman(json[L"Flags"][L"Human"].as_bool());
|
||||
|
||||
/* All units which contain the name "Olympus" are automatically under AI control */
|
||||
if (getUnitName().find("Olympus") != string::npos)
|
||||
setControlled(true);
|
||||
|
||||
updateExportData(json);
|
||||
setDefaults();
|
||||
}
|
||||
|
||||
void Unit::setDefaults(bool force)
|
||||
{
|
||||
const bool isUnitControlledByOlympus = getControlled();
|
||||
const bool isUnitAlive = getAlive();
|
||||
const bool isUnitLeader = unitsManager->isUnitGroupLeader(this);
|
||||
const bool isUnitLeaderOfAGroupWithOtherUnits = unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this);
|
||||
const bool isUnitHuman = getFlags()[L"Human"].as_bool();
|
||||
if (isUnitControlledByOlympus && (isUnitAlive || isUnitLeaderOfAGroupWithOtherUnits) && isUnitLeader && !isUnitHuman) {
|
||||
/* Set the default IDLE state */
|
||||
setState(State::IDLE);
|
||||
if (!getControlled()) return;
|
||||
if (!unitsManager->isUnitGroupLeader(this)) return;
|
||||
if (!(getAlive() || unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this))) return;
|
||||
if (getHuman()) return;
|
||||
|
||||
/* Set desired altitude to be equal to current altitude so the unit does not climb/descend after spawn */
|
||||
setDesiredAltitude(altitude);
|
||||
/* Set the default IDLE state */
|
||||
setState(State::IDLE);
|
||||
|
||||
/* Set the default options (these are all defaults so will only affect the export data, no DCS command will be sent) */
|
||||
setROE(L"Designated", force);
|
||||
setReactionToThreat(L"Evade", force);
|
||||
setEmissionsCountermeasures(L"Defend", force);
|
||||
setTACAN(TACAN, force);
|
||||
setRadio(radio, force);
|
||||
setEPLRS(EPLRS, force);
|
||||
setGeneralSettings(generalSettings, force);
|
||||
setOnOff(onOff);
|
||||
setFollowRoads(followRoads);
|
||||
}
|
||||
}
|
||||
/* Set desired altitude to be equal to current altitude so the unit does not climb/descend after spawn */
|
||||
setDesiredAltitude(position.alt);
|
||||
|
||||
void Unit::addMeasure(wstring key, json::value value)
|
||||
{
|
||||
milliseconds ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
|
||||
if (measures.find(key) == measures.end())
|
||||
measures[key] = new Measure(value, ms.count());
|
||||
else
|
||||
{
|
||||
if (measures[key]->getValue() != value)
|
||||
{
|
||||
measures[key]->setValue(value);
|
||||
measures[key]->setTime(ms.count());
|
||||
}
|
||||
}
|
||||
/* Set the default options */
|
||||
setROE(ROE::OPEN_FIRE_WEAPON_FREE, force);
|
||||
setReactionToThreat(ReactionToThreat::EVADE_FIRE, force);
|
||||
setEmissionsCountermeasures(EmissionCountermeasure::DEFEND, force);
|
||||
strcpy_s(TACAN.callsign, 4, "TKR");
|
||||
setTACAN(TACAN, force);
|
||||
setRadio(radio, force);
|
||||
setGeneralSettings(generalSettings, force);
|
||||
}
|
||||
|
||||
void Unit::runAILoop() {
|
||||
/* If the unit is alive and it is not a human, run the AI Loop that performs the requested commands and instructions (moving, attacking, etc) */
|
||||
const bool isUnitControlledByOlympus = getControlled();
|
||||
/* If the unit is alive, controlled and it is not a human, run the AI Loop that performs the requested commands and instructions (moving, attacking, etc) */
|
||||
if (!getControlled()) return;
|
||||
if (!unitsManager->isUnitGroupLeader(this)) return;
|
||||
if (human) return;
|
||||
|
||||
/* Keep running the AI loop even if the unit is dead if it is the leader of a group which has other members in it */
|
||||
const bool isUnitAlive = getAlive();
|
||||
const bool isUnitLeader = unitsManager->isUnitGroupLeader(this);
|
||||
const bool isUnitLeaderOfAGroupWithOtherUnits = unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this);
|
||||
const bool isUnitHuman = getFlags()[L"Human"].as_bool();
|
||||
if (!(isUnitAlive || isUnitLeaderOfAGroupWithOtherUnits)) return;
|
||||
|
||||
// Keep running the AI loop even if the unit is dead if it is the leader of a group which has other members in it
|
||||
if (isUnitControlledByOlympus && (isUnitAlive || isUnitLeaderOfAGroupWithOtherUnits) && isUnitLeader && !isUnitHuman)
|
||||
{
|
||||
if (checkTaskFailed() && state != State::IDLE && State::LAND)
|
||||
setState(State::IDLE);
|
||||
if (checkTaskFailed() && state != State::IDLE && State::LAND)
|
||||
setState(State::IDLE);
|
||||
|
||||
AIloop();
|
||||
}
|
||||
AIloop();
|
||||
}
|
||||
|
||||
void Unit::updateExportData(json::value json)
|
||||
void Unit::updateExportData(json::value json, double dt)
|
||||
{
|
||||
Coords newPosition = Coords(NULL);
|
||||
double newHeading = 0;
|
||||
double newSpeed = 0;
|
||||
|
||||
if (json.has_object_field(L"LatLongAlt"))
|
||||
{
|
||||
setPosition({
|
||||
json[L"LatLongAlt"][L"Lat"].as_number().to_double(),
|
||||
json[L"LatLongAlt"][L"Long"].as_number().to_double(),
|
||||
json[L"LatLongAlt"][L"Alt"].as_number().to_double()
|
||||
});
|
||||
}
|
||||
if (json.has_number_field(L"Heading"))
|
||||
setHeading(json[L"Heading"].as_number().to_double());
|
||||
|
||||
/* 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;
|
||||
Geodesic::WGS84().Inverse(latitude, longitude, oldPosition.lat, oldPosition.lng, dist);
|
||||
setSpeed(getSpeed() * 0.95 + (dist / UPDATE_TIME_INTERVAL) * 0.05);
|
||||
Geodesic::WGS84().Inverse(getPosition().lat, getPosition().lng, oldPosition.lat, oldPosition.lng, dist);
|
||||
if (dt > 0)
|
||||
setSpeed(getSpeed() * 0.95 + (dist / dt) * 0.05);
|
||||
}
|
||||
oldPosition = Coords(latitude, longitude, altitude);
|
||||
|
||||
if (json.has_string_field(L"Name"))
|
||||
setName(json[L"Name"].as_string());
|
||||
if (json.has_string_field(L"UnitName"))
|
||||
setUnitName(json[L"UnitName"].as_string());
|
||||
if (json.has_string_field(L"GroupName"))
|
||||
setGroupName(json[L"GroupName"].as_string());
|
||||
if (json.has_object_field(L"Type"))
|
||||
setType(json[L"Type"]);
|
||||
if (json.has_number_field(L"Country"))
|
||||
setCountry(json[L"Country"].as_number().to_int32());
|
||||
if (json.has_number_field(L"CoalitionID"))
|
||||
setCoalitionID(json[L"CoalitionID"].as_number().to_int32());
|
||||
if (json.has_object_field(L"LatLongAlt"))
|
||||
{
|
||||
setLatitude(json[L"LatLongAlt"][L"Lat"].as_number().to_double());
|
||||
setLongitude(json[L"LatLongAlt"][L"Long"].as_number().to_double());
|
||||
setAltitude(json[L"LatLongAlt"][L"Alt"].as_number().to_double());
|
||||
}
|
||||
if (json.has_number_field(L"Heading"))
|
||||
setHeading(json[L"Heading"].as_number().to_double());
|
||||
if (json.has_object_field(L"Flags"))
|
||||
setFlags(json[L"Flags"]);
|
||||
|
||||
/* All units which contain the name "Olympus" are automatically under AI control */
|
||||
if (getUnitName().find(L"Olympus") != wstring::npos)
|
||||
setControlled(true);
|
||||
oldPosition = position;
|
||||
}
|
||||
|
||||
void Unit::updateMissionData(json::value json)
|
||||
{
|
||||
if (json.has_number_field(L"fuel"))
|
||||
setFuel(int(json[L"fuel"].as_number().to_double() * 100));
|
||||
if (json.has_object_field(L"ammo"))
|
||||
setAmmo(json[L"ammo"]);
|
||||
if (json.has_object_field(L"contacts"))
|
||||
setContacts(json[L"contacts"]);
|
||||
if (json.has_number_field(L"fuel")) {
|
||||
setFuel(short(json[L"fuel"].as_number().to_double() * 100));
|
||||
}
|
||||
|
||||
if (json.has_object_field(L"ammo")) {
|
||||
vector<DataTypes::Ammo> ammo;
|
||||
for (auto const& el : json[L"ammo"].as_object()) {
|
||||
DataTypes::Ammo ammoItem;
|
||||
auto ammoJson = el.second;
|
||||
ammoItem.quantity = ammoJson[L"count"].as_number().to_uint32();
|
||||
string name = to_string(ammoJson[L"desc"][L"displayName"].as_string()).substr(0, sizeof(ammoItem.name) - 1);
|
||||
strcpy_s(ammoItem.name, sizeof(ammoItem.name), name.c_str());
|
||||
|
||||
if (ammoJson[L"desc"].has_number_field(L"guidance"))
|
||||
ammoItem.guidance = ammoJson[L"desc"][L"guidance"].as_number().to_uint32();
|
||||
|
||||
if (ammoJson[L"desc"].has_number_field(L"category"))
|
||||
ammoItem.category = ammoJson[L"desc"][L"category"].as_number().to_uint32();
|
||||
|
||||
if (ammoJson[L"desc"].has_number_field(L"missileCategory"))
|
||||
ammoItem.missileCategory = ammoJson[L"desc"][L"missileCategory"].as_number().to_uint32();
|
||||
ammo.push_back(ammoItem);
|
||||
}
|
||||
setAmmo(ammo);
|
||||
}
|
||||
|
||||
if (json.has_object_field(L"contacts")) {
|
||||
vector<DataTypes::Contact> contacts;
|
||||
for (auto const& el : json[L"contacts"].as_object()) {
|
||||
DataTypes::Contact contactItem;
|
||||
auto contactJson = el.second;
|
||||
contactItem.ID = contactJson[L"object"][L"id_"].as_number().to_uint32();
|
||||
|
||||
string detectionMethod = to_string(contactJson[L"detectionMethod"]);
|
||||
if (detectionMethod.compare("VISUAL") == 0) contactItem.detectionMethod = 1;
|
||||
else if (detectionMethod.compare("OPTIC") == 0) contactItem.detectionMethod = 2;
|
||||
else if (detectionMethod.compare("RADAR") == 0) contactItem.detectionMethod = 4;
|
||||
else if (detectionMethod.compare("IRST") == 0) contactItem.detectionMethod = 8;
|
||||
else if (detectionMethod.compare("RWR") == 0) contactItem.detectionMethod = 16;
|
||||
else if (detectionMethod.compare("DLINK") == 0) contactItem.detectionMethod = 32;
|
||||
contacts.push_back(contactItem);
|
||||
}
|
||||
setContacts(contacts);
|
||||
}
|
||||
|
||||
if (json.has_boolean_field(L"hasTask"))
|
||||
setHasTask(json[L"hasTask"].as_bool());
|
||||
}
|
||||
|
||||
json::value Unit::getData(long long time, bool sendAll)
|
||||
bool Unit::checkFreshness(unsigned char datumIndex, unsigned long long time) {
|
||||
auto it = updateTimeMap.find(datumIndex);
|
||||
if (it == updateTimeMap.end())
|
||||
return false;
|
||||
else
|
||||
return it->second > time;
|
||||
}
|
||||
|
||||
bool Unit::hasFreshData(unsigned long long time) {
|
||||
for (auto it : updateTimeMap)
|
||||
if (it.second > time)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void Unit::getData(stringstream& ss, unsigned long long time)
|
||||
{
|
||||
auto json = json::value::object();
|
||||
Unit* leader = this;
|
||||
if (unitsManager->isUnitInGroup(this) && !unitsManager->isUnitGroupLeader(this))
|
||||
leader = unitsManager->getGroupLeader(this);
|
||||
|
||||
/* If the unit is in a group, task & option data is given by the group leader */
|
||||
if (unitsManager->isUnitInGroup(this) && !unitsManager->isUnitGroupLeader(this))
|
||||
json = unitsManager->getGroupLeader(this)->getData(time, true);
|
||||
|
||||
/********** Base data **********/
|
||||
json[L"baseData"] = json::value::object();
|
||||
for (auto key : { L"controlled", L"name", L"unitName", L"groupName", L"alive", L"category"})
|
||||
if (!leader->hasFreshData(time)) return;
|
||||
|
||||
const unsigned char endOfData = DataIndex::endOfData;
|
||||
ss.write((const char*)&ID, sizeof(ID));
|
||||
for (unsigned char datumIndex = DataIndex::startOfData + 1; datumIndex < DataIndex::lastIndex; datumIndex++)
|
||||
{
|
||||
if (measures.find(key) != measures.end() && measures[key]->getTime() > time)
|
||||
json[L"baseData"][key] = measures[key]->getValue();
|
||||
}
|
||||
if (json[L"baseData"].size() == 0)
|
||||
json.erase(L"baseData");
|
||||
|
||||
if (alive || sendAll) {
|
||||
/********** Flight data **********/
|
||||
json[L"flightData"] = json::value::object();
|
||||
for (auto key : { L"latitude", L"longitude", L"altitude", L"speed", L"heading" })
|
||||
{
|
||||
if (measures.find(key) != measures.end() && measures[key]->getTime() > time)
|
||||
json[L"flightData"][key] = measures[key]->getValue();
|
||||
}
|
||||
if (json[L"flightData"].size() == 0)
|
||||
json.erase(L"flightData");
|
||||
|
||||
/********** Mission data **********/
|
||||
json[L"missionData"] = json::value::object();
|
||||
for (auto key : { L"fuel", L"ammo", L"contacts", L"hasTask", L"coalition", L"flags" })
|
||||
{
|
||||
if (measures.find(key) != measures.end() && measures[key]->getTime() > time)
|
||||
json[L"missionData"][key] = measures[key]->getValue();
|
||||
}
|
||||
if (json[L"missionData"].size() == 0)
|
||||
json.erase(L"missionData");
|
||||
|
||||
/********** Formation data **********/
|
||||
json[L"formationData"] = json::value::object();
|
||||
for (auto key : { L"leaderID" })
|
||||
{
|
||||
if (measures.find(key) != measures.end() && measures[key]->getTime() > time)
|
||||
json[L"formationData"][key] = measures[key]->getValue();
|
||||
}
|
||||
if (json[L"formationData"].size() == 0)
|
||||
json.erase(L"formationData");
|
||||
|
||||
/* If the unit is in a group, task & option data is given by the group leader */
|
||||
if (unitsManager->isUnitGroupLeader(this)) {
|
||||
/********** Task data **********/
|
||||
json[L"taskData"] = json::value::object();
|
||||
for (auto key : { L"currentState", L"currentTask", L"desiredSpeed", L"desiredAltitude", L"desiredSpeedType", L"desiredAltitudeType", L"activePath", L"isTanker", L"isAWACS", L"onOff", L"followRoads", L"targetID", L"targetLocation" })
|
||||
{
|
||||
if (measures.find(key) != measures.end() && measures[key]->getTime() > time)
|
||||
json[L"taskData"][key] = measures[key]->getValue();
|
||||
}
|
||||
if (json[L"taskData"].size() == 0)
|
||||
json.erase(L"taskData");
|
||||
|
||||
/********** Options data **********/
|
||||
json[L"optionsData"] = json::value::object();
|
||||
for (auto key : { L"ROE", L"reactionToThreat", L"emissionsCountermeasures", L"TACAN", L"radio", L"generalSettings" })
|
||||
{
|
||||
if (measures.find(key) != measures.end() && measures[key]->getTime() > time)
|
||||
json[L"optionsData"][key] = measures[key]->getValue();
|
||||
}
|
||||
if (json[L"optionsData"].size() == 0)
|
||||
json.erase(L"optionsData");
|
||||
/* When units are in a group, most data comes from the group leader */
|
||||
switch (datumIndex) {
|
||||
case DataIndex::category: if (checkFreshness(datumIndex, time)) appendString(ss, datumIndex, category); break;
|
||||
case DataIndex::alive: if (checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, alive); break;
|
||||
case DataIndex::human: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->human); break;
|
||||
case DataIndex::controlled: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->controlled); break;
|
||||
case DataIndex::coalition: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->coalition); break;
|
||||
case DataIndex::country: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->country); break;
|
||||
case DataIndex::name: if (checkFreshness(datumIndex, time)) appendString(ss, datumIndex, name); break;
|
||||
case DataIndex::unitName: if (checkFreshness(datumIndex, time)) appendString(ss, datumIndex, unitName); break;
|
||||
case DataIndex::groupName: if (leader->checkFreshness(datumIndex, time)) appendString(ss, datumIndex, leader->groupName); break;
|
||||
case DataIndex::state: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->state); break;
|
||||
case DataIndex::task: if (leader->checkFreshness(datumIndex, time)) appendString(ss, datumIndex, leader->task); break;
|
||||
case DataIndex::hasTask: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->hasTask); break;
|
||||
case DataIndex::position: if (checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, position); break;
|
||||
case DataIndex::speed: if (checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, speed); break;
|
||||
case DataIndex::heading: if (checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, heading); break;
|
||||
case DataIndex::isTanker: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->isTanker); break;
|
||||
case DataIndex::isAWACS: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->isAWACS); break;
|
||||
case DataIndex::onOff: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->onOff); break;
|
||||
case DataIndex::followRoads: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->followRoads); break;
|
||||
case DataIndex::fuel: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, fuel); break;
|
||||
case DataIndex::desiredSpeed: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->desiredSpeed); break;
|
||||
case DataIndex::desiredSpeedType: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->desiredSpeedType); break;
|
||||
case DataIndex::desiredAltitude: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->desiredAltitude); break;
|
||||
case DataIndex::desiredAltitudeType: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->desiredAltitudeType); break;
|
||||
case DataIndex::leaderID: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->leaderID); break;
|
||||
case DataIndex::formationOffset: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->formationOffset); break;
|
||||
case DataIndex::targetID: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->targetID); break;
|
||||
case DataIndex::targetPosition: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->targetPosition); break;
|
||||
case DataIndex::ROE: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->ROE); break;
|
||||
case DataIndex::reactionToThreat: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->reactionToThreat); break;
|
||||
case DataIndex::emissionsCountermeasures: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->emissionsCountermeasures); break;
|
||||
case DataIndex::TACAN: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->TACAN); break;
|
||||
case DataIndex::radio: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->radio); break;
|
||||
case DataIndex::generalSettings: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->generalSettings); break;
|
||||
case DataIndex::ammo: if (checkFreshness(datumIndex, time)) appendVector(ss, datumIndex, ammo); break;
|
||||
case DataIndex::contacts: if (checkFreshness(datumIndex, time)) appendVector(ss, datumIndex, contacts); break;
|
||||
case DataIndex::activePath: if (leader->checkFreshness(datumIndex, time)) appendList(ss, datumIndex, leader->activePath); break;
|
||||
}
|
||||
}
|
||||
ss.write((const char*)&endOfData, sizeof(endOfData));
|
||||
}
|
||||
|
||||
return json;
|
||||
void Unit::setAmmo(vector<DataTypes::Ammo> newValue)
|
||||
{
|
||||
if (ammo.size() == newValue.size()) {
|
||||
bool equal = true;
|
||||
for (int i = 0; i < ammo.size(); i++) {
|
||||
if (ammo.at(i) != newValue.at(i))
|
||||
{
|
||||
equal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (equal)
|
||||
return;
|
||||
}
|
||||
ammo = newValue;
|
||||
triggerUpdate(DataIndex::ammo);
|
||||
}
|
||||
|
||||
void Unit::setContacts(vector<DataTypes::Contact> newValue)
|
||||
{
|
||||
if (contacts.size() == newValue.size()) {
|
||||
bool equal = true;
|
||||
for (int i = 0; i < contacts.size(); i++) {
|
||||
if (contacts.at(i) != newValue.at(i))
|
||||
{
|
||||
equal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (equal)
|
||||
return;
|
||||
}
|
||||
contacts = newValue;
|
||||
triggerUpdate(DataIndex::contacts);
|
||||
}
|
||||
|
||||
void Unit::setActivePath(list<Coords> newPath)
|
||||
{
|
||||
activePath = newPath;
|
||||
resetActiveDestination();
|
||||
|
||||
auto path = json::value::object();
|
||||
if (activePath.size() > 0) {
|
||||
int count = 1;
|
||||
for (auto& destination : activePath)
|
||||
{
|
||||
auto json = json::value::object();
|
||||
json[L"lat"] = destination.lat;
|
||||
json[L"lng"] = destination.lng;
|
||||
json[L"alt"] = destination.alt;
|
||||
path[to_wstring(count++)] = json;
|
||||
}
|
||||
}
|
||||
addMeasure(L"activePath", path);
|
||||
}
|
||||
|
||||
void Unit::clearActivePath()
|
||||
@ -283,28 +310,7 @@ void Unit::popActivePathFront()
|
||||
setActivePath(path);
|
||||
}
|
||||
|
||||
void Unit::setCoalitionID(int newCoalitionID)
|
||||
{
|
||||
if (newCoalitionID == 0)
|
||||
coalition = L"neutral";
|
||||
else if (newCoalitionID == 1)
|
||||
coalition = L"red";
|
||||
else
|
||||
coalition = L"blue";
|
||||
addMeasure(L"coalition", json::value(coalition));
|
||||
}
|
||||
|
||||
int Unit::getCoalitionID()
|
||||
{
|
||||
if (coalition == L"neutral")
|
||||
return 0;
|
||||
else if (coalition == L"red")
|
||||
return 1;
|
||||
else
|
||||
return 2;
|
||||
}
|
||||
|
||||
wstring Unit::getTargetName()
|
||||
string Unit::getTargetName()
|
||||
{
|
||||
if (isTargetAlive())
|
||||
{
|
||||
@ -312,7 +318,7 @@ wstring Unit::getTargetName()
|
||||
if (target != nullptr)
|
||||
return target->getUnitName();
|
||||
}
|
||||
return L"";
|
||||
return "";
|
||||
}
|
||||
|
||||
bool Unit::isTargetAlive()
|
||||
@ -327,7 +333,7 @@ bool Unit::isTargetAlive()
|
||||
return false;
|
||||
}
|
||||
|
||||
wstring Unit::getLeaderName()
|
||||
string Unit::getLeaderName()
|
||||
{
|
||||
if (isLeaderAlive())
|
||||
{
|
||||
@ -335,7 +341,7 @@ wstring Unit::getLeaderName()
|
||||
if (leader != nullptr)
|
||||
return leader->getUnitName();
|
||||
}
|
||||
return L"";
|
||||
return "";
|
||||
}
|
||||
|
||||
bool Unit::isLeaderAlive()
|
||||
@ -367,86 +373,60 @@ void Unit::setFormationOffset(Offset newFormationOffset)
|
||||
{
|
||||
formationOffset = newFormationOffset;
|
||||
resetTask();
|
||||
|
||||
triggerUpdate(DataIndex::formationOffset);
|
||||
}
|
||||
|
||||
void Unit::setROE(wstring newROE, bool force) {
|
||||
addMeasure(L"ROE", json::value(newROE));
|
||||
|
||||
void Unit::setROE(unsigned char newROE, bool force)
|
||||
{
|
||||
if (ROE != newROE || force) {
|
||||
ROE = newROE;
|
||||
|
||||
int ROEEnum;
|
||||
if (ROE.compare(L"Free") == 0)
|
||||
ROEEnum = ROE::WEAPON_FREE;
|
||||
else if (ROE.compare(L"Designated free") == 0)
|
||||
ROEEnum = ROE::OPEN_FIRE_WEAPON_FREE;
|
||||
else if (ROE.compare(L"Designated") == 0)
|
||||
ROEEnum = ROE::OPEN_FIRE;
|
||||
else if (ROE.compare(L"Return") == 0)
|
||||
ROEEnum = ROE::RETURN_FIRE;
|
||||
else if (ROE.compare(L"Hold") == 0)
|
||||
ROEEnum = ROE::WEAPON_HOLD;
|
||||
else
|
||||
return;
|
||||
|
||||
Command* command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::ROE, ROEEnum));
|
||||
Command* command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::ROE, static_cast<unsigned int>(ROE)));
|
||||
scheduler->appendCommand(command);
|
||||
|
||||
triggerUpdate(DataIndex::ROE);
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::setReactionToThreat(wstring newReactionToThreat, bool force) {
|
||||
addMeasure(L"reactionToThreat", json::value(newReactionToThreat));
|
||||
|
||||
void Unit::setReactionToThreat(unsigned char newReactionToThreat, bool force)
|
||||
{
|
||||
if (reactionToThreat != newReactionToThreat || force) {
|
||||
reactionToThreat = newReactionToThreat;
|
||||
|
||||
int reactionToThreatEnum;
|
||||
if (reactionToThreat.compare(L"None") == 0)
|
||||
reactionToThreatEnum = ReactionToThreat::NO_REACTION;
|
||||
else if (reactionToThreat.compare(L"Passive") == 0)
|
||||
reactionToThreatEnum = ReactionToThreat::PASSIVE_DEFENCE;
|
||||
else if (reactionToThreat.compare(L"Evade") == 0)
|
||||
reactionToThreatEnum = ReactionToThreat::EVADE_FIRE;
|
||||
else if (reactionToThreat.compare(L"Escape") == 0)
|
||||
reactionToThreatEnum = ReactionToThreat::BYPASS_AND_ESCAPE;
|
||||
else if (reactionToThreat.compare(L"Abort") == 0)
|
||||
reactionToThreatEnum = ReactionToThreat::ALLOW_ABORT_MISSION;
|
||||
else
|
||||
return;
|
||||
|
||||
Command* command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::REACTION_ON_THREAT, reactionToThreatEnum));
|
||||
Command* command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::REACTION_ON_THREAT, static_cast<unsigned int>(reactionToThreat)));
|
||||
scheduler->appendCommand(command);
|
||||
|
||||
triggerUpdate(DataIndex::reactionToThreat);
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::setEmissionsCountermeasures(wstring newEmissionsCountermeasures, bool force) {
|
||||
addMeasure(L"emissionsCountermeasures", json::value(newEmissionsCountermeasures));
|
||||
|
||||
void Unit::setEmissionsCountermeasures(unsigned char newEmissionsCountermeasures, bool force)
|
||||
{
|
||||
if (emissionsCountermeasures != newEmissionsCountermeasures || force) {
|
||||
emissionsCountermeasures = newEmissionsCountermeasures;
|
||||
|
||||
int radarEnum;
|
||||
int flareEnum;
|
||||
int ECMEnum;
|
||||
if (emissionsCountermeasures.compare(L"Silent") == 0)
|
||||
unsigned int radarEnum;
|
||||
unsigned int flareEnum;
|
||||
unsigned int ECMEnum;
|
||||
if (emissionsCountermeasures == EmissionCountermeasure::SILENT)
|
||||
{
|
||||
radarEnum = RadarUse::NEVER;
|
||||
flareEnum = FlareUse::NEVER;
|
||||
ECMEnum = ECMUse::NEVER_USE;
|
||||
}
|
||||
else if (emissionsCountermeasures.compare(L"Attack") == 0)
|
||||
else if (emissionsCountermeasures == EmissionCountermeasure::ATTACK)
|
||||
{
|
||||
radarEnum = RadarUse::FOR_ATTACK_ONLY;
|
||||
flareEnum = FlareUse::AGAINST_FIRED_MISSILE;
|
||||
ECMEnum = ECMUse::USE_IF_ONLY_LOCK_BY_RADAR;
|
||||
}
|
||||
else if (emissionsCountermeasures.compare(L"Defend") == 0)
|
||||
else if (emissionsCountermeasures == EmissionCountermeasure::DEFEND)
|
||||
{
|
||||
radarEnum = RadarUse::FOR_SEARCH_IF_REQUIRED;
|
||||
flareEnum = FlareUse::WHEN_FLYING_IN_SAM_WEZ;
|
||||
ECMEnum = ECMUse::USE_IF_DETECTED_LOCK_BY_RADAR;
|
||||
}
|
||||
else if (emissionsCountermeasures.compare(L"Free") == 0)
|
||||
else if (emissionsCountermeasures == EmissionCountermeasure::FREE)
|
||||
{
|
||||
radarEnum = RadarUse::FOR_CONTINUOUS_SEARCH;
|
||||
flareEnum = FlareUse::WHEN_FLYING_NEAR_ENEMIES;
|
||||
@ -465,45 +445,49 @@ void Unit::setEmissionsCountermeasures(wstring newEmissionsCountermeasures, bool
|
||||
|
||||
command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::ECM_USING, ECMEnum));
|
||||
scheduler->appendCommand(command);
|
||||
|
||||
triggerUpdate(DataIndex::emissionsCountermeasures);
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::landAt(Coords loc) {
|
||||
void Unit::landAt(Coords loc)
|
||||
{
|
||||
clearActivePath();
|
||||
pushActivePathBack(loc);
|
||||
setState(State::LAND);
|
||||
}
|
||||
|
||||
void Unit::setIsTanker(bool newIsTanker) {
|
||||
isTanker = newIsTanker;
|
||||
resetTask();
|
||||
addMeasure(L"isTanker", json::value(newIsTanker));
|
||||
void Unit::setIsTanker(bool newIsTanker)
|
||||
{
|
||||
if (isTanker != newIsTanker) {
|
||||
isTanker = newIsTanker;
|
||||
resetTask();
|
||||
|
||||
triggerUpdate(DataIndex::isTanker);
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::setIsAWACS(bool newIsAWACS) {
|
||||
isAWACS = newIsAWACS;
|
||||
resetTask();
|
||||
addMeasure(L"isAWACS", json::value(newIsAWACS));
|
||||
setEPLRS(isAWACS);
|
||||
void Unit::setIsAWACS(bool newIsAWACS)
|
||||
{
|
||||
if (isAWACS != newIsAWACS) {
|
||||
isAWACS = newIsAWACS;
|
||||
resetTask();
|
||||
|
||||
triggerUpdate(DataIndex::isAWACS);
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::setTACAN(Options::TACAN newTACAN, bool force) {
|
||||
auto json = json::value();
|
||||
json[L"isOn"] = json::value(newTACAN.isOn);
|
||||
json[L"channel"] = json::value(newTACAN.channel);
|
||||
json[L"XY"] = json::value(newTACAN.XY);
|
||||
json[L"callsign"] = json::value(newTACAN.callsign);
|
||||
addMeasure(L"TACAN", json);
|
||||
|
||||
void Unit::setTACAN(DataTypes::TACAN newTACAN, bool force)
|
||||
{
|
||||
if (TACAN != newTACAN || force)
|
||||
{
|
||||
TACAN = newTACAN;
|
||||
if (TACAN.isOn) {
|
||||
std::wostringstream commandSS;
|
||||
std::ostringstream commandSS;
|
||||
commandSS << "{"
|
||||
<< "id = 'ActivateBeacon',"
|
||||
<< "params = {"
|
||||
<< "type = " << ((TACAN.XY.compare(L"X") == 0) ? 4 : 5) << ","
|
||||
<< "type = " << ((TACAN.XY == 'X' == 0) ? 4 : 5) << ","
|
||||
<< "system = 3,"
|
||||
<< "name = \"Olympus_TACAN\","
|
||||
<< "callsign = \"" << TACAN.callsign << "\", "
|
||||
@ -514,7 +498,7 @@ void Unit::setTACAN(Options::TACAN newTACAN, bool force) {
|
||||
scheduler->appendCommand(command);
|
||||
}
|
||||
else {
|
||||
std::wostringstream commandSS;
|
||||
std::ostringstream commandSS;
|
||||
commandSS << "{"
|
||||
<< "id = 'DeactivateBeacon',"
|
||||
<< "params = {"
|
||||
@ -523,22 +507,18 @@ void Unit::setTACAN(Options::TACAN newTACAN, bool force) {
|
||||
Command* command = dynamic_cast<Command*>(new SetCommand(groupName, commandSS.str()));
|
||||
scheduler->appendCommand(command);
|
||||
}
|
||||
|
||||
triggerUpdate(DataIndex::TACAN);
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::setRadio(Options::Radio newRadio, bool force) {
|
||||
|
||||
auto json = json::value();
|
||||
json[L"frequency"] = json::value(newRadio.frequency);
|
||||
json[L"callsign"] = json::value(newRadio.callsign);
|
||||
json[L"callsignNumber"] = json::value(newRadio.callsignNumber);
|
||||
addMeasure(L"radio", json);
|
||||
|
||||
void Unit::setRadio(DataTypes::Radio newRadio, bool force)
|
||||
{
|
||||
if (radio != newRadio || force)
|
||||
{
|
||||
radio = newRadio;
|
||||
|
||||
std::wostringstream commandSS;
|
||||
std::ostringstream commandSS;
|
||||
Command* command;
|
||||
|
||||
commandSS << "{"
|
||||
@ -552,7 +532,7 @@ void Unit::setRadio(Options::Radio newRadio, bool force) {
|
||||
scheduler->appendCommand(command);
|
||||
|
||||
// Clear the stringstream
|
||||
commandSS.str(wstring());
|
||||
commandSS.str(string(""));
|
||||
|
||||
commandSS << "{"
|
||||
<< "id = 'SetCallsign',"
|
||||
@ -563,38 +543,13 @@ void Unit::setRadio(Options::Radio newRadio, bool force) {
|
||||
<< "}";
|
||||
command = dynamic_cast<Command*>(new SetCommand(groupName, commandSS.str()));
|
||||
scheduler->appendCommand(command);
|
||||
|
||||
triggerUpdate(DataIndex::radio);
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::setEPLRS(bool newEPLRS, bool force)
|
||||
void Unit::setGeneralSettings(DataTypes::GeneralSettings newGeneralSettings, bool force)
|
||||
{
|
||||
//addMeasure(L"EPLRS", json::value(newEPLRS));
|
||||
//
|
||||
//if (EPLRS != newEPLRS || force) {
|
||||
// EPLRS = newEPLRS;
|
||||
//
|
||||
// std::wostringstream commandSS;
|
||||
// commandSS << "{"
|
||||
// << "id = 'EPLRS',"
|
||||
// << "params = {"
|
||||
// << "value = " << (EPLRS ? "true" : "false") << ", "
|
||||
// << "}"
|
||||
// << "}";
|
||||
// Command* command = dynamic_cast<Command*>(new SetCommand(ID, commandSS.str()));
|
||||
// scheduler->appendCommand(command);
|
||||
//}
|
||||
}
|
||||
|
||||
void Unit::setGeneralSettings(Options::GeneralSettings newGeneralSettings, bool force) {
|
||||
|
||||
auto json = json::value();
|
||||
json[L"prohibitJettison"] = json::value(newGeneralSettings.prohibitJettison);
|
||||
json[L"prohibitAA"] = json::value(newGeneralSettings.prohibitAA);
|
||||
json[L"prohibitAG"] = json::value(newGeneralSettings.prohibitAG);
|
||||
json[L"prohibitAfterburner"] = json::value(newGeneralSettings.prohibitAfterburner);
|
||||
json[L"prohibitAirWpn"] = json::value(newGeneralSettings.prohibitAirWpn);
|
||||
addMeasure(L"generalSettings", json);
|
||||
|
||||
if (generalSettings != newGeneralSettings)
|
||||
{
|
||||
generalSettings = newGeneralSettings;
|
||||
@ -610,50 +565,60 @@ void Unit::setGeneralSettings(Options::GeneralSettings newGeneralSettings, bool
|
||||
scheduler->appendCommand(command);
|
||||
command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::ENGAGE_AIR_WEAPONS, !generalSettings.prohibitAirWpn));
|
||||
scheduler->appendCommand(command);
|
||||
|
||||
triggerUpdate(DataIndex::generalSettings);
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::setDesiredSpeed(double newDesiredSpeed) {
|
||||
desiredSpeed = newDesiredSpeed;
|
||||
addMeasure(L"desiredSpeed", json::value(newDesiredSpeed));
|
||||
void Unit::setDesiredSpeed(double newDesiredSpeed)
|
||||
{
|
||||
desiredSpeed = newDesiredSpeed;
|
||||
if (state == State::IDLE)
|
||||
resetTask();
|
||||
else
|
||||
goToDestination(); /* Send the command to reach the destination */
|
||||
|
||||
triggerUpdate(DataIndex::desiredSpeed);
|
||||
}
|
||||
|
||||
void Unit::setDesiredAltitude(double newDesiredAltitude) {
|
||||
void Unit::setDesiredAltitude(double newDesiredAltitude)
|
||||
{
|
||||
desiredAltitude = newDesiredAltitude;
|
||||
addMeasure(L"desiredAltitude", json::value(newDesiredAltitude));
|
||||
if (state == State::IDLE)
|
||||
resetTask();
|
||||
else
|
||||
goToDestination(); /* Send the command to reach the destination */
|
||||
|
||||
triggerUpdate(DataIndex::desiredAltitude);
|
||||
}
|
||||
|
||||
void Unit::setDesiredSpeedType(wstring newDesiredSpeedType) {
|
||||
desiredSpeedType = newDesiredSpeedType;
|
||||
addMeasure(L"desiredSpeedType", json::value(newDesiredSpeedType));
|
||||
void Unit::setDesiredSpeedType(string newDesiredSpeedType)
|
||||
{
|
||||
desiredSpeedType = newDesiredSpeedType.compare("GS") == 0;
|
||||
if (state == State::IDLE)
|
||||
resetTask();
|
||||
else
|
||||
goToDestination(); /* Send the command to reach the destination */
|
||||
|
||||
triggerUpdate(DataIndex::desiredSpeedType);
|
||||
}
|
||||
|
||||
void Unit::setDesiredAltitudeType(wstring newDesiredAltitudeType) {
|
||||
desiredAltitudeType = newDesiredAltitudeType;
|
||||
addMeasure(L"desiredAltitudeType", json::value(newDesiredAltitudeType));
|
||||
void Unit::setDesiredAltitudeType(string newDesiredAltitudeType)
|
||||
{
|
||||
desiredAltitudeType = newDesiredAltitudeType.compare("AGL") == 0;
|
||||
if (state == State::IDLE)
|
||||
resetTask();
|
||||
else
|
||||
goToDestination(); /* Send the command to reach the destination */
|
||||
|
||||
triggerUpdate(DataIndex::desiredAltitudeType);
|
||||
}
|
||||
|
||||
void Unit::goToDestination(wstring enrouteTask)
|
||||
void Unit::goToDestination(string enrouteTask)
|
||||
{
|
||||
if (activeDestination != NULL)
|
||||
{
|
||||
Command* command = dynamic_cast<Command*>(new Move(groupName, activeDestination, getDesiredSpeed(), getDesiredSpeedType(), getDesiredAltitude(), getDesiredAltitudeType(), enrouteTask, getCategory()));
|
||||
Command* command = dynamic_cast<Command*>(new Move(groupName, activeDestination, getDesiredSpeed(), getDesiredSpeedType() ? "GS" : "CAS", getDesiredAltitude(), getDesiredAltitudeType() ? "AGL" : "ASL", enrouteTask, getCategory()));
|
||||
scheduler->appendCommand(command);
|
||||
setHasTask(true);
|
||||
}
|
||||
@ -664,19 +629,20 @@ bool Unit::isDestinationReached(double threshold)
|
||||
if (activeDestination != NULL)
|
||||
{
|
||||
/* Check if any unit in the group has reached the point */
|
||||
for (auto const& p: unitsManager->getGroupMembers(groupName))
|
||||
for (auto const& p : unitsManager->getGroupMembers(groupName))
|
||||
{
|
||||
double dist = 0;
|
||||
Geodesic::WGS84().Inverse(p->getLatitude(), p->getLongitude(), activeDestination.lat, activeDestination.lng, dist);
|
||||
Geodesic::WGS84().Inverse(p->getPosition().lat, p->getPosition().lng, activeDestination.lat, activeDestination.lng, dist);
|
||||
if (dist < threshold)
|
||||
{
|
||||
log(unitName + L" destination reached");
|
||||
log(unitName + " destination reached");
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return true;
|
||||
@ -687,13 +653,17 @@ bool Unit::setActiveDestination()
|
||||
if (activePath.size() > 0)
|
||||
{
|
||||
activeDestination = activePath.front();
|
||||
log(unitName + L" active destination set to queue front");
|
||||
log(unitName + " active destination set to queue front");
|
||||
|
||||
triggerUpdate(DataIndex::activePath);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
activeDestination = Coords(0);
|
||||
log(unitName + L" active destination set to NULL");
|
||||
log(unitName + " active destination set to NULL");
|
||||
|
||||
triggerUpdate(DataIndex::activePath);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -706,7 +676,7 @@ bool Unit::updateActivePath(bool looping)
|
||||
if (looping)
|
||||
pushActivePathBack(activePath.front());
|
||||
popActivePathFront();
|
||||
log(unitName + L" active path front popped");
|
||||
log(unitName + " active path front popped");
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
@ -714,16 +684,9 @@ bool Unit::updateActivePath(bool looping)
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::setTargetLocation(Coords newTargetLocation) {
|
||||
targetLocation = newTargetLocation;
|
||||
auto json = json::value();
|
||||
json[L"latitude"] = json::value(newTargetLocation.lat);
|
||||
json[L"longitude"] = json::value(newTargetLocation.lng);
|
||||
addMeasure(L"targetLocation", json::value(json));
|
||||
}
|
||||
|
||||
bool Unit::checkTaskFailed() {
|
||||
if (getHasTask())
|
||||
bool Unit::checkTaskFailed()
|
||||
{
|
||||
if (getHasTask())
|
||||
return false;
|
||||
else {
|
||||
if (taskCheckCounter > 0)
|
||||
@ -736,7 +699,6 @@ void Unit::resetTaskFailedCounter() {
|
||||
taskCheckCounter = TASK_CHECK_INIT_VALUE;
|
||||
}
|
||||
|
||||
void Unit::setHasTask(bool newHasTask) {
|
||||
hasTask = newHasTask;
|
||||
addMeasure(L"hasTask", json::value(newHasTask));
|
||||
}
|
||||
void Unit::triggerUpdate(unsigned char datumIndex) {
|
||||
updateTimeMap[datumIndex] = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
@ -10,6 +10,9 @@
|
||||
#include "commands.h"
|
||||
#include "scheduler.h"
|
||||
|
||||
#include "base64.hpp"
|
||||
using namespace base64;
|
||||
|
||||
extern Scheduler* scheduler;
|
||||
|
||||
UnitsManager::UnitsManager(lua_State* L)
|
||||
@ -22,7 +25,7 @@ UnitsManager::~UnitsManager()
|
||||
|
||||
}
|
||||
|
||||
Unit* UnitsManager::getUnit(int ID)
|
||||
Unit* UnitsManager::getUnit(unsigned int ID)
|
||||
{
|
||||
if (units.find(ID) == units.end()) {
|
||||
return nullptr;
|
||||
@ -35,7 +38,7 @@ Unit* UnitsManager::getUnit(int ID)
|
||||
bool UnitsManager::isUnitInGroup(Unit* unit)
|
||||
{
|
||||
if (unit != nullptr) {
|
||||
wstring groupName = unit->getGroupName();
|
||||
string groupName = unit->getGroupName();
|
||||
for (auto const& p : units)
|
||||
{
|
||||
if (p.second->getGroupName().compare(groupName) == 0 && p.second != unit)
|
||||
@ -57,26 +60,19 @@ bool UnitsManager::isUnitGroupLeader(Unit* unit)
|
||||
Unit* UnitsManager::getGroupLeader(Unit* unit)
|
||||
{
|
||||
if (unit != nullptr) {
|
||||
wstring groupName = unit->getGroupName();
|
||||
|
||||
/* Get the unit IDs in order */
|
||||
std::vector<int> keys;
|
||||
for (auto const& p : units)
|
||||
keys.push_back(p.first);
|
||||
sort(keys.begin(), keys.end());
|
||||
string groupName = unit->getGroupName();
|
||||
|
||||
/* Find the first unit that has the same groupName */
|
||||
for (auto const& tempID : keys)
|
||||
for (auto const& p : units)
|
||||
{
|
||||
Unit* tempUnit = getUnit(tempID);
|
||||
if (tempUnit != nullptr && tempUnit->getGroupName().compare(groupName) == 0)
|
||||
return tempUnit;
|
||||
if (p.second->getGroupName().compare(groupName) == 0)
|
||||
return p.second;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
vector<Unit*> UnitsManager::getGroupMembers(wstring groupName)
|
||||
vector<Unit*> UnitsManager::getGroupMembers(string groupName)
|
||||
{
|
||||
vector<Unit*> members;
|
||||
for (auto const& p : units)
|
||||
@ -87,20 +83,20 @@ vector<Unit*> UnitsManager::getGroupMembers(wstring groupName)
|
||||
return members;
|
||||
}
|
||||
|
||||
Unit* UnitsManager::getGroupLeader(int ID)
|
||||
Unit* UnitsManager::getGroupLeader(unsigned int ID)
|
||||
{
|
||||
Unit* unit = getUnit(ID);
|
||||
return getGroupLeader(unit);
|
||||
}
|
||||
|
||||
void UnitsManager::updateExportData(lua_State* L)
|
||||
void UnitsManager::updateExportData(lua_State* L, double dt)
|
||||
{
|
||||
map<int, json::value> unitJSONs = getAllUnits(L);
|
||||
map<unsigned int, json::value> unitJSONs = getAllUnits(L);
|
||||
|
||||
/* Update all units, create them if needed TODO: move code to get constructor in dedicated function */
|
||||
for (auto const& p : unitJSONs)
|
||||
{
|
||||
int ID = p.first;
|
||||
unsigned int ID = p.first;
|
||||
if (units.count(ID) == 0)
|
||||
{
|
||||
json::value type = static_cast<json::value>(p.second)[L"Type"];
|
||||
@ -132,15 +128,13 @@ void UnitsManager::updateExportData(lua_State* L)
|
||||
else {
|
||||
/* Update the unit if present*/
|
||||
if (units.count(ID) != 0)
|
||||
units[ID]->updateExportData(p.second);
|
||||
units[ID]->updateExportData(p.second, dt);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the units that are not present in the JSON as dead (probably have been destroyed) */
|
||||
for (auto const& unit : units)
|
||||
{
|
||||
unit.second->setAlive(unitJSONs.find(unit.first) != unitJSONs.end());
|
||||
}
|
||||
}
|
||||
|
||||
void UnitsManager::updateMissionData(json::value missionData)
|
||||
@ -148,35 +142,26 @@ void UnitsManager::updateMissionData(json::value missionData)
|
||||
/* Update all units */
|
||||
for (auto const& p : units)
|
||||
{
|
||||
int ID = p.first;
|
||||
unsigned int ID = p.first;
|
||||
if (missionData.has_field(to_wstring(ID)))
|
||||
{
|
||||
p.second->updateMissionData(missionData[to_wstring(ID)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UnitsManager::runAILoop() {
|
||||
/* Run the AI Loop on all units */
|
||||
for (auto const& unit : units)
|
||||
{
|
||||
unit.second->runAILoop();
|
||||
}
|
||||
}
|
||||
|
||||
void UnitsManager::getData(json::value& answer, long long time)
|
||||
string UnitsManager::getUnitData(stringstream &ss, unsigned long long time)
|
||||
{
|
||||
auto unitsJson = json::value::object();
|
||||
for (auto const& p : units)
|
||||
{
|
||||
auto unitJson = p.second->getData(time);
|
||||
if (unitJson.size() > 0)
|
||||
unitsJson[to_wstring(p.first)] = p.second->getData(time);
|
||||
}
|
||||
answer[L"units"] = unitsJson;
|
||||
p.second->getData(ss, time);
|
||||
return to_base64(ss.str());
|
||||
}
|
||||
|
||||
void UnitsManager::deleteUnit(int ID, bool explosion)
|
||||
void UnitsManager::deleteUnit(unsigned int ID, bool explosion)
|
||||
{
|
||||
if (getUnit(ID) != nullptr)
|
||||
{
|
||||
@ -185,7 +170,7 @@ void UnitsManager::deleteUnit(int ID, bool explosion)
|
||||
}
|
||||
}
|
||||
|
||||
void UnitsManager::acquireControl(int ID) {
|
||||
void UnitsManager::acquireControl(unsigned int ID) {
|
||||
Unit* unit = getUnit(ID);
|
||||
if (unit != nullptr) {
|
||||
for (auto const& groupMember : getGroupMembers(unit->getGroupName())) {
|
||||
|
||||
@ -13,21 +13,21 @@ extern Scheduler* scheduler;
|
||||
extern UnitsManager* unitsManager;
|
||||
|
||||
/* Weapon */
|
||||
Weapon::Weapon(json::value json, int ID) : Unit(json, ID)
|
||||
Weapon::Weapon(json::value json, unsigned int ID) : Unit(json, ID)
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
/* Missile */
|
||||
Missile::Missile(json::value json, int ID) : Weapon(json, ID)
|
||||
Missile::Missile(json::value json, unsigned int ID) : Weapon(json, ID)
|
||||
{
|
||||
log("New Missile created with ID: " + to_string(ID));
|
||||
addMeasure(L"category", json::value(getCategory()));
|
||||
setCategory("Missile");
|
||||
};
|
||||
|
||||
/* Bomb */
|
||||
Bomb::Bomb(json::value json, int ID) : Weapon(json, ID)
|
||||
Bomb::Bomb(json::value json, unsigned int ID) : Weapon(json, ID)
|
||||
{
|
||||
log("New Bomb created with ID: " + to_string(ID));
|
||||
addMeasure(L"category", json::value(getCategory()));
|
||||
setCategory("Bomb");
|
||||
};
|
||||
@ -5,8 +5,8 @@
|
||||
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);
|
||||
void DllExport Log(lua_State* L, string message, unsigned int level);
|
||||
int DllExport dostring_in(lua_State* L, string target, string command);
|
||||
map<int, json::value> DllExport getAllUnits(lua_State* L);
|
||||
int DllExport TACANChannelToFrequency(int channel, wstring XY);
|
||||
map<unsigned int, json::value> DllExport getAllUnits(lua_State* L);
|
||||
unsigned int DllExport TACANChannelToFrequency(unsigned int channel, char XY);
|
||||
|
||||
|
||||
@ -42,7 +42,7 @@ void LogError(lua_State* L, string message)
|
||||
Log(L, message, errorLevel);
|
||||
}
|
||||
|
||||
void Log(lua_State* L, string message, int level)
|
||||
void Log(lua_State* L, string message, unsigned int level)
|
||||
{
|
||||
STACK_INIT;
|
||||
|
||||
@ -56,10 +56,10 @@ void Log(lua_State* L, string message, int level)
|
||||
STACK_CLEAN;
|
||||
}
|
||||
|
||||
map<int, json::value> getAllUnits(lua_State* L)
|
||||
map<unsigned int, json::value> getAllUnits(lua_State* L)
|
||||
{
|
||||
int res = 0;
|
||||
map<int, json::value> units;
|
||||
unsigned int res = 0;
|
||||
map<unsigned int, json::value> units;
|
||||
|
||||
STACK_INIT;
|
||||
|
||||
@ -83,7 +83,8 @@ map<int, json::value> getAllUnits(lua_State* L)
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, 2) != 0)
|
||||
{
|
||||
int ID = lua_tonumber(L, -2);
|
||||
unsigned int ID = lua_tonumber(L, -2);
|
||||
// TODO more efficient method can be used, converting all the lua data to a json object may be overkill
|
||||
units[ID] = luaTableToJSON(L, -1);
|
||||
STACK_POP(1)
|
||||
}
|
||||
@ -103,8 +104,8 @@ int dostring_in(lua_State* L, string target, string command)
|
||||
return lua_pcall(L, 2, 0, 0);
|
||||
}
|
||||
|
||||
int TACANChannelToFrequency(int channel, wstring XY)
|
||||
unsigned int TACANChannelToFrequency(unsigned int channel, char XY)
|
||||
{
|
||||
int basef = (XY == L"X" && channel > 63) || (XY == L"Y" && channel < 64) ? 1087: 961;
|
||||
unsigned int basef = (XY == 'X' && channel > 63) || (XY == 'Y' && channel < 64) ? 1087 : 961;
|
||||
return (basef + channel) * 1000000;
|
||||
}
|
||||
@ -3,4 +3,4 @@
|
||||
|
||||
void DllExport log(const std::string& sMessage);
|
||||
void DllExport log(const std::wstring& sMessage);
|
||||
void DllExport getLogsJSON(json::value& json, int logsNumber = NULL);
|
||||
void DllExport getLogsJSON(json::value& json, unsigned int logsNumber = NULL);
|
||||
|
||||
@ -7,7 +7,7 @@ class Logger
|
||||
public:
|
||||
void log(const string& sMessage);
|
||||
void log(const wstring& sMessage);
|
||||
void toJSON(json::value& json, int logsNumber = NULL);
|
||||
void toJSON(json::value& json, unsigned int logsNumber = NULL);
|
||||
|
||||
static Logger* GetLogger();
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ void log(const wstring& message)
|
||||
LOGGER->log(message);
|
||||
}
|
||||
|
||||
void getLogsJSON(json::value& json, int logsNumber)
|
||||
void getLogsJSON(json::value& json, unsigned int logsNumber)
|
||||
{
|
||||
LOGGER->toJSON(json, logsNumber);
|
||||
}
|
||||
@ -32,10 +32,10 @@ void Logger::Close()
|
||||
m_Logfile.close();
|
||||
}
|
||||
|
||||
void Logger::toJSON(json::value& json, int logsNumber)
|
||||
void Logger::toJSON(json::value& json, unsigned int logsNumber)
|
||||
{
|
||||
lock_guard<mutex> guard(mutexLock);
|
||||
int i = 0;
|
||||
unsigned int i = 0;
|
||||
for (auto itr = m_logs.end(); itr != m_logs.begin(); --itr)
|
||||
{
|
||||
json[to_wstring(m_logs.size() - 1 - i)] = json::value::string(to_wstring(*itr));
|
||||
|
||||
@ -2,12 +2,12 @@
|
||||
|
||||
#define VERSION "v0.2.1"
|
||||
#define LOG_NAME "Olympus_log.txt"
|
||||
#define REST_ADDRESS L"http://localhost:30000"
|
||||
#define REST_URI L"olympus"
|
||||
#define UNITS_URI L"units"
|
||||
#define LOGS_URI L"logs"
|
||||
#define AIRBASES_URI L"airbases"
|
||||
#define BULLSEYE_URI L"bullseyes"
|
||||
#define MISSION_URI L"mission"
|
||||
#define REST_ADDRESS "http://localhost:30000"
|
||||
#define REST_URI "olympus"
|
||||
#define UNITS_URI "units"
|
||||
#define LOGS_URI "logs"
|
||||
#define AIRBASES_URI "airbases"
|
||||
#define BULLSEYE_URI "bullseyes"
|
||||
#define MISSION_URI "mission"
|
||||
|
||||
#define UPDATE_TIME_INTERVAL 0.25
|
||||
@ -20,6 +20,7 @@
|
||||
#include <codecvt>
|
||||
#include <cpprest/http_listener.h>
|
||||
#include <cpprest/json.h>
|
||||
#include <cpprest/streams.h>
|
||||
#include <set>
|
||||
|
||||
using namespace std;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include "framework.h"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct Coords {
|
||||
double lat = 0;
|
||||
double lng = 0;
|
||||
@ -12,22 +13,24 @@ struct Offset {
|
||||
double y = 0;
|
||||
double z = 0;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
// Get current date/time, format is YYYY-MM-DD.HH:mm:ss
|
||||
const DllExport std::string CurrentDateTime();
|
||||
std::wstring DllExport to_wstring(const std::string& str);
|
||||
std::string DllExport to_string(json::value& value);
|
||||
std::string DllExport to_string(const std::wstring& wstr);
|
||||
std::string DllExport random_string(size_t length);
|
||||
|
||||
bool DllExport operator== (const Coords& a, const Coords& b);
|
||||
bool DllExport operator!= (const Coords& a, const Coords& b);
|
||||
bool DllExport operator== (const Coords& a, const int& b);
|
||||
bool DllExport operator!= (const Coords& a, const int& b);
|
||||
bool DllExport operator== (const Coords& a, const double& b);
|
||||
bool DllExport operator!= (const Coords& a, const double& b);
|
||||
|
||||
bool DllExport operator== (const Offset& a, const Offset& b);
|
||||
bool DllExport operator!= (const Offset& a, const Offset& b);
|
||||
bool DllExport operator== (const Offset& a, const int& b);
|
||||
bool DllExport operator!= (const Offset& a, const int& b);
|
||||
bool DllExport operator== (const Offset& a, const double& b);
|
||||
bool DllExport operator!= (const Offset& a, const double& b);
|
||||
|
||||
double DllExport knotsToMs(const double knots);
|
||||
double DllExport msToKnots(const double ms);
|
||||
|
||||
@ -14,12 +14,16 @@ const std::string CurrentDateTime()
|
||||
|
||||
std::wstring to_wstring(const std::string& str)
|
||||
{
|
||||
int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0);
|
||||
unsigned int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (unsigned int)str.size(), NULL, 0);
|
||||
std::wstring wstrTo(size_needed, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), &wstrTo[0], size_needed);
|
||||
MultiByteToWideChar(CP_UTF8, 0, &str[0], (unsigned int)str.size(), &wstrTo[0], size_needed);
|
||||
return wstrTo;
|
||||
}
|
||||
|
||||
std::string to_string(json::value& value) {
|
||||
return to_string(value.as_string());
|
||||
}
|
||||
|
||||
std::string to_string(const std::wstring& wstr)
|
||||
{
|
||||
if (wstr.empty())
|
||||
@ -27,14 +31,14 @@ std::string to_string(const std::wstring& wstr)
|
||||
return "";
|
||||
}
|
||||
|
||||
const auto size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr.at(0), (int)wstr.size(), nullptr, 0, nullptr, nullptr);
|
||||
const auto size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr.at(0), (unsigned int)wstr.size(), nullptr, 0, nullptr, nullptr);
|
||||
if (size_needed <= 0)
|
||||
{
|
||||
throw std::runtime_error("WideCharToMultiByte() failed: " + std::to_string(size_needed));
|
||||
}
|
||||
|
||||
std::string result(size_needed, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, &wstr.at(0), (int)wstr.size(), &result.at(0), size_needed, nullptr, nullptr);
|
||||
WideCharToMultiByte(CP_UTF8, 0, &wstr.at(0), (unsigned int)wstr.size(), &result.at(0), size_needed, nullptr, nullptr);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -56,13 +60,13 @@ std::string random_string(size_t length)
|
||||
|
||||
bool operator== (const Coords& a, const Coords& b) { return a.lat == b.lat && a.lng == b.lng && a.alt == b.alt; }
|
||||
bool operator!= (const Coords& a, const Coords& b) { return !(a == b); }
|
||||
bool operator== (const Coords& a, const int& b) { return a.lat == b && a.lng == b && a.alt == b; }
|
||||
bool operator!= (const Coords& a, const int& b) { return !(a == b); }
|
||||
bool operator== (const Coords& a, const double& b) { return a.lat == b && a.lng == b && a.alt == b; }
|
||||
bool operator!= (const Coords& a, const double& b) { return !(a == b); }
|
||||
|
||||
bool operator== (const Offset& a, const Offset& b) { return a.x == b.x && a.y == b.y && a.z == b.z; }
|
||||
bool operator!= (const Offset& a, const Offset& b) { return !(a == b); }
|
||||
bool operator== (const Offset& a, const int& b) { return a.x == b && a.y == b && a.z == b; }
|
||||
bool operator!= (const Offset& a, const int& b) { return !(a == b); }
|
||||
bool operator== (const Offset& a, const double& b) { return a.x == b && a.y == b && a.z == b; }
|
||||
bool operator!= (const Offset& a, const double& b) { return !(a == b); }
|
||||
|
||||
|
||||
double knotsToMs(const double knots) {
|
||||
@ -79,4 +83,4 @@ double ftToM(const double ft) {
|
||||
|
||||
double mToFt(const double m) {
|
||||
return m / 0.3048;
|
||||
}
|
||||
}
|
||||
|
||||
142
third-party/base64/include/base64.hpp
vendored
@ -6,76 +6,82 @@
|
||||
|
||||
namespace base64 {
|
||||
|
||||
inline std::string get_base64_chars() {
|
||||
static std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
return base64_chars;
|
||||
}
|
||||
inline std::string get_base64_chars() {
|
||||
static std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
return base64_chars;
|
||||
}
|
||||
|
||||
inline std::string to_base64(const char* data, size_t size) {
|
||||
int counter = 0;
|
||||
uint32_t bit_stream = 0;
|
||||
const std::string base64_chars = get_base64_chars();
|
||||
std::string encoded;
|
||||
encoded.reserve(ceil(4.0 / 3.0 * size));
|
||||
int offset = 0;
|
||||
for (unsigned int idx = 0; idx < size; idx++) {
|
||||
unsigned char c = data[idx];
|
||||
auto num_val = static_cast<unsigned int>(c);
|
||||
offset = 16 - counter % 3 * 8;
|
||||
bit_stream += num_val << offset;
|
||||
if (offset == 16) {
|
||||
encoded += base64_chars.at(bit_stream >> 18 & 0x3f);
|
||||
}
|
||||
if (offset == 8) {
|
||||
encoded += base64_chars.at(bit_stream >> 12 & 0x3f);
|
||||
}
|
||||
if (offset == 0 && counter != 3) {
|
||||
encoded += base64_chars.at(bit_stream >> 6 & 0x3f);
|
||||
encoded += base64_chars.at(bit_stream & 0x3f);
|
||||
bit_stream = 0;
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
if (offset == 16) {
|
||||
encoded += base64_chars.at(bit_stream >> 12 & 0x3f);
|
||||
encoded += "==";
|
||||
}
|
||||
if (offset == 8) {
|
||||
encoded += base64_chars.at(bit_stream >> 6 & 0x3f);
|
||||
encoded += '=';
|
||||
}
|
||||
return encoded;
|
||||
}
|
||||
|
||||
inline std::string to_base64(std::string const &data) {
|
||||
int counter = 0;
|
||||
uint32_t bit_stream = 0;
|
||||
const std::string base64_chars = get_base64_chars();
|
||||
std::string encoded;
|
||||
int offset = 0;
|
||||
for (unsigned char c : data) {
|
||||
auto num_val = static_cast<unsigned int>(c);
|
||||
offset = 16 - counter % 3 * 8;
|
||||
bit_stream += num_val << offset;
|
||||
if (offset == 16) {
|
||||
encoded += base64_chars.at(bit_stream >> 18 & 0x3f);
|
||||
}
|
||||
if (offset == 8) {
|
||||
encoded += base64_chars.at(bit_stream >> 12 & 0x3f);
|
||||
}
|
||||
if (offset == 0 && counter != 3) {
|
||||
encoded += base64_chars.at(bit_stream >> 6 & 0x3f);
|
||||
encoded += base64_chars.at(bit_stream & 0x3f);
|
||||
bit_stream = 0;
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
if (offset == 16) {
|
||||
encoded += base64_chars.at(bit_stream >> 12 & 0x3f);
|
||||
encoded += "==";
|
||||
}
|
||||
if (offset == 8) {
|
||||
encoded += base64_chars.at(bit_stream >> 6 & 0x3f);
|
||||
encoded += '=';
|
||||
}
|
||||
return encoded;
|
||||
}
|
||||
|
||||
inline std::string from_base64(std::string const &data) {
|
||||
int counter = 0;
|
||||
uint32_t bit_stream = 0;
|
||||
std::string decoded;
|
||||
int offset = 0;
|
||||
const std::string base64_chars = get_base64_chars();
|
||||
for (unsigned char c : data) {
|
||||
auto num_val = base64_chars.find(c);
|
||||
if (num_val != std::string::npos) {
|
||||
offset = 18 - counter % 4 * 6;
|
||||
bit_stream += num_val << offset;
|
||||
if (offset == 12) {
|
||||
decoded += static_cast<char>(bit_stream >> 16 & 0xff);
|
||||
}
|
||||
if (offset == 6) {
|
||||
decoded += static_cast<char>(bit_stream >> 8 & 0xff);
|
||||
}
|
||||
if (offset == 0 && counter != 4) {
|
||||
decoded += static_cast<char>(bit_stream & 0xff);
|
||||
bit_stream = 0;
|
||||
}
|
||||
} else if (c != '=') {
|
||||
return std::string();
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
inline std::string to_base64(std::string const& data) {
|
||||
return to_base64(data.c_str(), data.length());
|
||||
}
|
||||
|
||||
inline std::string from_base64(std::string const& data) {
|
||||
int counter = 0;
|
||||
uint32_t bit_stream = 0;
|
||||
std::string decoded;
|
||||
int offset = 0;
|
||||
const std::string base64_chars = get_base64_chars();
|
||||
for (unsigned char c : data) {
|
||||
auto num_val = base64_chars.find(c);
|
||||
if (num_val != std::string::npos) {
|
||||
offset = 18 - counter % 4 * 6;
|
||||
bit_stream += num_val << offset;
|
||||
if (offset == 12) {
|
||||
decoded += static_cast<char>(bit_stream >> 16 & 0xff);
|
||||
}
|
||||
if (offset == 6) {
|
||||
decoded += static_cast<char>(bit_stream >> 8 & 0xff);
|
||||
}
|
||||
if (offset == 0 && counter != 4) {
|
||||
decoded += static_cast<char>(bit_stream & 0xff);
|
||||
bit_stream = 0;
|
||||
}
|
||||
}
|
||||
else if (c != '=') {
|
||||
return std::string();
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // BASE_64_HPP
|
||||