Merge pull request #243 from Pax1601/209-update-follow-functionality-for-ordering-units

209 update follow functionality for ordering units
This commit is contained in:
Pax1601 2023-05-01 10:37:59 +02:00 committed by GitHub
commit 89a71ef04e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1635 additions and 337 deletions

View File

@ -2,7 +2,7 @@
const DEMO_UNIT_DATA = {
["1"]:{
baseData: {
AI: true,
AI: false,
name: "KC-135",
unitName: "Olympus 1-1",
groupName: "Group 1",
@ -18,7 +18,7 @@ const DEMO_UNIT_DATA = {
},
missionData: {
fuel: 50,
flags: {human: true},
flags: {Human: false},
ammo: [
{
count: 4,
@ -47,6 +47,7 @@ const DEMO_UNIT_DATA = {
},
taskData: {
currentTask: "Holding",
currentState: "Idle",
activePath: undefined,
targetSpeed: 400,
targetAltitude: 3000,
@ -67,7 +68,7 @@ const DEMO_UNIT_DATA = {
},
["2"]:{
baseData: {
AI: false,
AI: true,
name: "KC-135",
unitName: "Olympus 1-2",
groupName: "Group 1",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -392,8 +392,8 @@
padding: var( --unit-aircraft-ammo-radius );
}
[data-object|="unit"] .unit-summary {
pointer-events: none;
column-gap: 6px;
color:white;
display:flex;
@ -438,14 +438,14 @@
[data-object|="unit"][data-pilot|="ai"]:hover .unit-ammo,
[data-object|="unit"][data-pilot|="ai"]:hover .unit-fuel {
[data-object|="unit"]:hover .unit-ammo,
[data-object|="unit"]:hover .unit-fuel {
display:flex;
}
[data-object|="unit"][data-is-in-hotgroup] .unit-hotgroup,
[data-object|="unit"][data-pilot|="ai"][data-is-selected] .unit-ammo,
[data-object|="unit"][data-pilot|="ai"][data-is-selected] .unit-fuel,
[data-object|="unit"][data-is-selected] .unit-ammo,
[data-object|="unit"][data-is-selected] .unit-fuel,
[data-object|="unit"][data-is-selected] .unit-selected-spotlight {
display:flex;
}
@ -501,7 +501,7 @@
}
}
[data-object|="unit"][data-pilot|="ai"][data-has-low-fuel] .unit-fuel {
[data-object|="unit"][data-has-low-fuel] .unit-fuel {
animation: pulse 1.5s linear infinite;
}

View File

@ -1,10 +1,83 @@
<svg width="63" height="63" viewBox="0 0 63 63" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M31.4998 46.9756C40.0468 46.9756 46.9756 40.0468 46.9756 31.4998C46.9756 22.9528 40.0468 16.024 31.4998 16.024C22.9528 16.024 16.024 22.9528 16.024 31.4998C16.024 40.0468 22.9528 46.9756 31.4998 46.9756ZM31.4998 52.734C43.2271 52.734 52.734 43.2271 52.734 31.4998C52.734 19.7725 43.2271 10.2656 31.4998 10.2656C19.7725 10.2656 10.2656 19.7725 10.2656 31.4998C10.2656 43.2271 19.7725 52.734 31.4998 52.734Z" fill="white"/>
<path d="M35.3291 61L37.3291 61L37.3291 59L37.3291 49.2532L37.3291 47.2532L35.3291 47.2532L27.6709 47.2532L25.6709 47.2532L25.6709 49.2532L25.6709 59L25.6709 61L27.6709 61L35.3291 61Z" fill="#247BE2" stroke="white" stroke-width="4"/>
<path d="M47.2529 35.3291V37.3291H49.2529H58.9998H60.9998V35.3291V27.6709V25.6709L58.9998 25.6709H49.2529H47.2529V27.6709V35.3291Z" fill="#247BE2" stroke="white" stroke-width="4"/>
<path d="M35.3291 15.7471L37.3291 15.7471L37.3291 13.7471L37.3291 4.00023L37.3291 2.00023L35.3291 2.00023L27.6709 2.00023L25.6709 2.00023L25.6709 4.00023L25.6709 13.7471L25.6709 15.7471L27.6709 15.7471L35.3291 15.7471Z" fill="#247BE2" stroke="white" stroke-width="4"/>
<path d="M2 35.3291V37.3291H4H13.7468H15.7468V35.3291L15.7468 27.6709V25.6709L13.7468 25.6709H4H2V27.6709L2 35.3291Z" fill="#247BE2" stroke="white" stroke-width="4"/>
<circle cx="31.5001" cy="31.5001" r="15.4494" fill="white" stroke="#247BE2" stroke-width="6"/>
<line x1="25.7895" y1="23.9132" x2="36.5242" y2="38.6028" stroke="#247BE2" stroke-width="2" stroke-linecap="square"/>
<line x1="38.9357" y1="33.9313" x2="25.9313" y2="33.0643" stroke="#247BE2" stroke-width="2" stroke-linecap="square"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="icon_airbase_blue.svg"
id="svg18"
version="1.1"
fill="none"
viewBox="0 0 63 63"
height="63"
width="63">
<metadata
id="metadata24">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs22" />
<sodipodi:namedview
inkscape:current-layer="svg18"
inkscape:window-maximized="1"
inkscape:window-y="-8"
inkscape:window-x="1912"
inkscape:cy="31.5"
inkscape:cx="31.5"
inkscape:zoom="13.492063"
showgrid="false"
id="namedview20"
inkscape:window-height="1017"
inkscape:window-width="1920"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<path
id="path2"
fill="white"
d="M31.4998 46.9756C40.0468 46.9756 46.9756 40.0468 46.9756 31.4998C46.9756 22.9528 40.0468 16.024 31.4998 16.024C22.9528 16.024 16.024 22.9528 16.024 31.4998C16.024 40.0468 22.9528 46.9756 31.4998 46.9756ZM31.4998 52.734C43.2271 52.734 52.734 43.2271 52.734 31.4998C52.734 19.7725 43.2271 10.2656 31.4998 10.2656C19.7725 10.2656 10.2656 19.7725 10.2656 31.4998C10.2656 43.2271 19.7725 52.734 31.4998 52.734Z"
clip-rule="evenodd"
fill-rule="evenodd" />
<circle
id="circle12"
stroke-width="6"
stroke="#247BE2"
fill="white"
r="15.4494"
cy="31.5001"
cx="31.5001" />
<line
id="line14"
stroke-linecap="square"
stroke-width="2"
stroke="#247BE2"
y2="38.6028"
x2="36.5242"
y1="23.9132"
x1="25.7895" />
<line
id="line16"
stroke-linecap="square"
stroke-width="2"
stroke="#247BE2"
y2="33.0643"
x2="25.9313"
y1="33.9313"
x1="38.9357" />
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1,10 +1,83 @@
<svg width="63" height="63" viewBox="0 0 63 63" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M31.4998 46.9756C40.0468 46.9756 46.9756 40.0468 46.9756 31.4998C46.9756 22.9528 40.0468 16.024 31.4998 16.024C22.9528 16.024 16.024 22.9528 16.024 31.4998C16.024 40.0468 22.9528 46.9756 31.4998 46.9756ZM31.4998 52.734C43.2271 52.734 52.734 43.2271 52.734 31.4998C52.734 19.7725 43.2271 10.2656 31.4998 10.2656C19.7725 10.2656 10.2656 19.7725 10.2656 31.4998C10.2656 43.2271 19.7725 52.734 31.4998 52.734Z" fill="white"/>
<path d="M35.3291 61L37.3291 61L37.3291 59L37.3291 49.2532L37.3291 47.2532L35.3291 47.2532L27.6709 47.2532L25.6709 47.2532L25.6709 49.2532L25.6709 59L25.6709 61L27.6709 61L35.3291 61Z" fill="#CFD9E8" stroke="white" stroke-width="4"/>
<path d="M47.2529 35.3291V37.3291H49.2529H58.9998H60.9998V35.3291V27.6709V25.6709L58.9998 25.6709H49.2529H47.2529V27.6709V35.3291Z" fill="#CFD9E8" stroke="white" stroke-width="4"/>
<path d="M35.3291 15.7471L37.3291 15.7471L37.3291 13.7471L37.3291 4.00023L37.3291 2.00023L35.3291 2.00023L27.6709 2.00023L25.6709 2.00023L25.6709 4.00023L25.6709 13.7471L25.6709 15.7471L27.6709 15.7471L35.3291 15.7471Z" fill="#CFD9E8" stroke="white" stroke-width="4"/>
<path d="M2 35.3291V37.3291H4H13.7468H15.7468V35.3291L15.7468 27.6709V25.6709L13.7468 25.6709H4H2V27.6709L2 35.3291Z" fill="#CFD9E8" stroke="white" stroke-width="4"/>
<circle cx="31.5001" cy="31.5001" r="15.4494" fill="white" stroke="#CFD9E8" stroke-width="6"/>
<line x1="25.7895" y1="23.9132" x2="36.5242" y2="38.6028" stroke="#CFD9E8" stroke-width="2" stroke-linecap="square"/>
<line x1="38.9357" y1="33.9313" x2="25.9313" y2="33.0643" stroke="#CFD9E8" stroke-width="2" stroke-linecap="square"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="icon_airbase_neutral.svg"
id="svg18"
version="1.1"
fill="none"
viewBox="0 0 63 63"
height="63"
width="63">
<metadata
id="metadata24">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs22" />
<sodipodi:namedview
inkscape:current-layer="svg18"
inkscape:window-maximized="1"
inkscape:window-y="-8"
inkscape:window-x="1912"
inkscape:cy="31.5"
inkscape:cx="31.5"
inkscape:zoom="13.492063"
showgrid="false"
id="namedview20"
inkscape:window-height="1017"
inkscape:window-width="1920"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<path
id="path2"
fill="white"
d="M31.4998 46.9756C40.0468 46.9756 46.9756 40.0468 46.9756 31.4998C46.9756 22.9528 40.0468 16.024 31.4998 16.024C22.9528 16.024 16.024 22.9528 16.024 31.4998C16.024 40.0468 22.9528 46.9756 31.4998 46.9756ZM31.4998 52.734C43.2271 52.734 52.734 43.2271 52.734 31.4998C52.734 19.7725 43.2271 10.2656 31.4998 10.2656C19.7725 10.2656 10.2656 19.7725 10.2656 31.4998C10.2656 43.2271 19.7725 52.734 31.4998 52.734Z"
clip-rule="evenodd"
fill-rule="evenodd" />
<circle
id="circle12"
stroke-width="6"
stroke="#CFD9E8"
fill="white"
r="15.4494"
cy="31.5001"
cx="31.5001" />
<line
id="line14"
stroke-linecap="square"
stroke-width="2"
stroke="#CFD9E8"
y2="38.6028"
x2="36.5242"
y1="23.9132"
x1="25.7895" />
<line
id="line16"
stroke-linecap="square"
stroke-width="2"
stroke="#CFD9E8"
y2="33.0643"
x2="25.9313"
y1="33.9313"
x1="38.9357" />
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1,10 +1,83 @@
<svg width="63" height="63" viewBox="0 0 63 63" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M31.4998 46.9756C40.0468 46.9756 46.9756 40.0468 46.9756 31.4998C46.9756 22.9528 40.0468 16.024 31.4998 16.024C22.9528 16.024 16.024 22.9528 16.024 31.4998C16.024 40.0468 22.9528 46.9756 31.4998 46.9756ZM31.4998 52.734C43.2271 52.734 52.734 43.2271 52.734 31.4998C52.734 19.7725 43.2271 10.2656 31.4998 10.2656C19.7725 10.2656 10.2656 19.7725 10.2656 31.4998C10.2656 43.2271 19.7725 52.734 31.4998 52.734Z" fill="white"/>
<path d="M35.3291 61L37.3291 61L37.3291 59L37.3291 49.2532L37.3291 47.2532L35.3291 47.2532L27.6709 47.2532L25.6709 47.2532L25.6709 49.2532L25.6709 59L25.6709 61L27.6709 61L35.3291 61Z" fill="#ff5858" stroke="white" stroke-width="4"/>
<path d="M47.2529 35.3291V37.3291H49.2529H58.9998H60.9998V35.3291V27.6709V25.6709L58.9998 25.6709H49.2529H47.2529V27.6709V35.3291Z" fill="#ff5858" stroke="white" stroke-width="4"/>
<path d="M35.3291 15.7471L37.3291 15.7471L37.3291 13.7471L37.3291 4.00023L37.3291 2.00023L35.3291 2.00023L27.6709 2.00023L25.6709 2.00023L25.6709 4.00023L25.6709 13.7471L25.6709 15.7471L27.6709 15.7471L35.3291 15.7471Z" fill="#ff5858" stroke="white" stroke-width="4"/>
<path d="M2 35.3291V37.3291H4H13.7468H15.7468V35.3291L15.7468 27.6709V25.6709L13.7468 25.6709H4H2V27.6709L2 35.3291Z" fill="#ff5858" stroke="white" stroke-width="4"/>
<circle cx="31.5001" cy="31.5001" r="15.4494" fill="white" stroke="#ff5858" stroke-width="6"/>
<line x1="25.7895" y1="23.9132" x2="36.5242" y2="38.6028" stroke="#ff5858" stroke-width="2" stroke-linecap="square"/>
<line x1="38.9357" y1="33.9313" x2="25.9313" y2="33.0643" stroke="#ff5858" stroke-width="2" stroke-linecap="square"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="icon_airbase_red.svg"
id="svg18"
version="1.1"
fill="none"
viewBox="0 0 63 63"
height="63"
width="63">
<metadata
id="metadata24">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs22" />
<sodipodi:namedview
inkscape:current-layer="svg18"
inkscape:window-maximized="1"
inkscape:window-y="-8"
inkscape:window-x="1912"
inkscape:cy="31.5"
inkscape:cx="31.5"
inkscape:zoom="13.492063"
showgrid="false"
id="namedview20"
inkscape:window-height="1017"
inkscape:window-width="1920"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<path
id="path2"
fill="white"
d="M31.4998 46.9756C40.0468 46.9756 46.9756 40.0468 46.9756 31.4998C46.9756 22.9528 40.0468 16.024 31.4998 16.024C22.9528 16.024 16.024 22.9528 16.024 31.4998C16.024 40.0468 22.9528 46.9756 31.4998 46.9756ZM31.4998 52.734C43.2271 52.734 52.734 43.2271 52.734 31.4998C52.734 19.7725 43.2271 10.2656 31.4998 10.2656C19.7725 10.2656 10.2656 19.7725 10.2656 31.4998C10.2656 43.2271 19.7725 52.734 31.4998 52.734Z"
clip-rule="evenodd"
fill-rule="evenodd" />
<circle
id="circle12"
stroke-width="6"
stroke="#ff5858"
fill="white"
r="15.4494"
cy="31.5001"
cx="31.5001" />
<line
id="line14"
stroke-linecap="square"
stroke-width="2"
stroke="#ff5858"
y2="38.6028"
x2="36.5242"
y1="23.9132"
x1="25.7895" />
<line
id="line16"
stroke-linecap="square"
stroke-width="2"
stroke="#ff5858"
y2="33.0643"
x2="25.9313"
y1="33.9313"
x1="38.9357" />
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 91 KiB

View File

@ -29,11 +29,7 @@ interface MissionData {
}
interface FormationData {
formation: string;
isLeader: boolean;
isWingman: boolean;
leaderID: number;
wingmenIDs: number[];
}
interface TaskData {

View File

@ -381,57 +381,6 @@ export class HelicopterDatabase extends UnitDatabase {
],
"filename": "ah-1.png"
},
"AH-1W": {
"name": "AH-1W",
"label": "AH-1W Cobra",
"shortLabel": "AH1",
"loadouts": [
{
"fuel": 1,
"items": [
{
"name": "BGM-71 TOW",
"quantity": 8
},
{
"name": "Hydra-70 WP",
"quantity": 38
}
],
"roles": [
"CAS"
],
"code": "8xBGM-71, 38xHYDRA-70 WP",
"name": "TOW / Hydra"
},
{
"fuel": 1,
"items": [
{
"name": "Hydra-70",
"quantity": 76
}
],
"roles": [
"CAS"
],
"code": "76xHYDRA-70",
"name": "Hydra"
},
{
"fuel": 1,
"items": [
],
"roles": [
""
],
"code": "",
"name": "Empty Loadout"
}
],
"filename": "ah-1.png"
},
"Mi-26": {
"name": "Mi-26",
"label": "Mi-26 Halo",

View File

@ -40,11 +40,7 @@ export class Unit extends Marker {
coalition: "",
},
formationData: {
formation: "",
isLeader: false,
isWingman: false,
leaderID: 0,
wingmenIDs: [],
leaderID: 0
},
taskData: {
currentState: "IDLE",
@ -71,6 +67,7 @@ export class Unit extends Marker {
#selectable: boolean;
#selected: boolean = false;
#hovered: boolean = false;
#hidden: boolean = false;
#preventClick: boolean = false;
@ -81,7 +78,6 @@ export class Unit extends Marker {
#miniMapMarker: CircleMarker | null = null;
#timer: number = 0;
#forceUpdate: boolean = false;
static getConstructor(type: string) {
if (type === "GroundUnit") return GroundUnit;
@ -102,6 +98,8 @@ export class Unit extends Marker {
this.on('click', (e) => this.#onClick(e));
this.on('dblclick', (e) => this.#onDoubleClick(e));
this.on('contextmenu', (e) => this.#onContextMenu(e));
this.on('mouseover', () => { this.#hovered = true;})
this.on('mouseout', () => { this.#hovered = false;})
this.#pathPolyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1 });
this.#pathPolyline.addTo(getMap());
@ -123,7 +121,8 @@ export class Unit extends Marker {
var icon = new DivIcon({
html: this.getMarkerHTML(),
className: 'leaflet-unit-marker',
iconAnchor: [0, 0]
iconAnchor: [25, 0],
iconSize: [50, 50],
});
this.setIcon(icon);
}
@ -143,13 +142,11 @@ export class Unit extends Marker {
}
setData(data: UpdateData) {
var updateMarker = false;
if ((data.flightData.latitude != undefined && data.flightData.longitude != undefined && (this.getFlightData().latitude != data.flightData.latitude || this.getFlightData().longitude != data.flightData.longitude))
|| (data.flightData.heading != undefined && this.getFlightData().heading != data.flightData.heading)
|| (data.baseData.alive != undefined && this.getBaseData().alive != data.baseData.alive)
|| this.#forceUpdate || !getMap().hasLayer(this))
updateMarker = true;
/* Check if data has changed comparing new values to old values */
const positionChanged = (data.flightData.latitude != undefined && data.flightData.longitude != undefined && (this.getFlightData().latitude != data.flightData.latitude || this.getFlightData().longitude != data.flightData.longitude));
const headingChanged = (data.flightData.heading != undefined && this.getFlightData().heading != data.flightData.heading);
const aliveChanged = (data.baseData.alive != undefined && this.getBaseData().alive != data.baseData.alive);
var updateMarker = (positionChanged || headingChanged || aliveChanged || !getMap().hasLayer(this))
if (data.baseData != undefined)
{
@ -314,38 +311,18 @@ export class Unit extends Marker {
return getUnitsManager().getUnitByID(this.getFormationData().leaderID);
}
getFormation() {
return [<Unit>this].concat(this.getWingmen())
}
getWingmen() {
var wingmen: Unit[] = [];
if (this.getFormationData().wingmenIDs != undefined) {
for (let ID of this.getFormationData().wingmenIDs) {
var unit = getUnitsManager().getUnitByID(ID)
if (unit)
wingmen.push(unit);
}
}
return wingmen;
}
attackUnit(targetID: number) {
/* Units can't attack themselves */
if (this.ID != targetID) {
attackUnit(this.ID, targetID);
}
else {
// TODO: show a message
}
}
followUnit(targetID: number, offset: {"x": number, "y": number, "z": number}) {
/* Units can't follow themselves */
if (this.ID != targetID) {
followUnit(this.ID, targetID, offset);
}
else {
// TODO: show a message
}
}
landAt(latlng: LatLng) {
@ -399,7 +376,7 @@ export class Unit extends Marker {
if (!e.originalEvent.ctrlKey) {
getUnitsManager().deselectAllUnits();
}
this.setSelected(true);
this.setSelected(!this.getSelected());
}
}
this.#preventClick = false;
@ -416,12 +393,11 @@ export class Unit extends Marker {
options["Center"] = `<div id="center-map">Center map</div>`;
if (getUnitsManager().getSelectedUnits().length > 0 && !(getUnitsManager().getSelectedUnits().includes(this)))
if (getUnitsManager().getSelectedUnits().length > 0 && !(getUnitsManager().getSelectedUnits().length == 1 && (getUnitsManager().getSelectedUnits().includes(this))))
{
options = {
'Attack': `<div id="attack">Attack</div>`,
'Follow': `<div id="follow">Follow</div>`,
}
options['Attack'] = `<div id="attack">Attack</div>`;
if (getUnitsManager().getSelectedUnitsType() === "Aircraft")
options['Follow'] = `<div id="follow">Follow</div>`;
}
else if ((getUnitsManager().getSelectedUnits().length > 0 && (getUnitsManager().getSelectedUnits().includes(this))) || getUnitsManager().getSelectedUnits().length == 0)
{
@ -487,30 +463,12 @@ export class Unit extends Marker {
// Z: left-right, positive right
var offset = {"x": 0, "y": 0, "z": 0};
if (action == "Trail")
{
offset.x = -50; offset.y = -30; offset.z = 0;
}
else if (action == "Echelon (LH)")
{
offset.x = -50; offset.y = -10; offset.z = -50;
}
else if (action == "Echelon (RH)")
{
offset.x = -50; offset.y = -10; offset.z = 50;
}
else if (action == "Line abreast (RH)")
{
offset.x = 0; offset.y = 0; offset.z = 50;
}
else if (action == "Line abreast (LH)")
{
offset.x = 0; offset.y = 0; offset.z = -50;
}
else if (action == "Front")
{
offset.x = 100; offset.y = 0; offset.z = 0;
}
if (action == "Trail") { offset.x = -50; offset.y = -30; offset.z = 0; }
else if (action == "Echelon (LH)") { offset.x = -50; offset.y = -10; offset.z = -50; }
else if (action == "Echelon (RH)") { offset.x = -50; offset.y = -10; offset.z = 50; }
else if (action == "Line abreast (RH)") { offset.x = 0; offset.y = 0; offset.z = 50; }
else if (action == "Line abreast (LH)") { offset.x = 0; offset.y = 0; offset.z = -50; }
else if (action == "Front") { offset.x = 100; offset.y = 0; offset.z = 0; }
getUnitsManager().selectedUnitsFollowUnit(this.ID, offset);
}
}
@ -518,6 +476,7 @@ export class Unit extends Marker {
#updateMarker() {
this.updateVisibility();
/* Draw the minimap marker */
if (this.getBaseData().alive )
{
if (this.#miniMapMarker == null)
@ -544,47 +503,47 @@ export class Unit extends Marker {
}
}
/* Draw the marker */
if (!this.getHidden()) {
this.setLatLng(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude));
var element = this.getElement();
if (element != null) {
/* Draw the velocity vector */
element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.getFlightData().speed / 5}px;`);
element.querySelector(".unit")?.setAttribute("data-pilot", this.getMissionData().flags.human? "human": "ai");
/* Set fuel data */
element.querySelector(".unit-fuel-level")?.setAttribute("style", `width: ${this.getMissionData().fuel}%`);
element.querySelector(".unit")?.toggleAttribute("data-has-low-fuel", this.getMissionData().fuel < 20);
/* Set dead/alive flag */
element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.getBaseData().alive);
if (this.getMissionData().flags.Human) // Unit is human
/* Set current unit state */
if (this.getMissionData().flags.Human) // Unit is human
element.querySelector(".unit")?.setAttribute("data-state", "human");
else if (!this.getBaseData().AI) // Unit is under DCS control (no Olympus)
else if (!this.getBaseData().AI) // Unit is under DCS control (not Olympus)
element.querySelector(".unit")?.setAttribute("data-state", "dcs");
else // Unit is under Olympus control
else // Unit is under Olympus control
element.querySelector(".unit")?.setAttribute("data-state", this.getTaskData().currentState.toLowerCase());
var unitAltitudeDiv = element.querySelector(".unit-altitude");
if (unitAltitudeDiv != null)
unitAltitudeDiv.innerHTML = "FL" + String(Math.floor(this.getFlightData().altitude / 0.3048 / 1000));
var unitSpeedDiv = element.querySelector(".unit-speed");
if (unitSpeedDiv != null)
unitSpeedDiv.innerHTML = String(Math.floor(this.getFlightData().speed * 1.94384 ) );
/* Set altitude and speed */
if (element.querySelector(".unit-altitude"))
(<HTMLElement> element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(this.getFlightData().altitude / 0.3048 / 1000));
if (element.querySelector(".unit-speed"))
(<HTMLElement> element.querySelector(".unit-speed")).innerHTML = String(Math.floor(this.getFlightData().speed * 1.94384 ) );
/* Rotate elements according to heading */
element.querySelectorAll( "[data-rotate-to-heading]" ).forEach( el => {
const headingDeg = rad2deg( this.getFlightData().heading );
let currentStyle = el.getAttribute( "style" ) || "";
el.setAttribute( "style", currentStyle + `transform:rotate(${headingDeg}deg);` );
});
}
/* Set vertical offset for altitude stacking */
var pos = getMap().latLngToLayerPoint(this.getLatLng()).round();
this.setZIndexOffset(1000 + Math.floor(this.getFlightData().altitude) - pos.y);
this.setZIndexOffset(1000 + Math.floor(this.getFlightData().altitude) - pos.y + (this.#hovered || this.#selected? 5000: 0));
}
this.#forceUpdate = false;
}
#drawPath() {
@ -643,7 +602,7 @@ export class Unit extends Marker {
color = "#00FF00";
else
color = "#FFFFFF";
var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 1, smoothFactor: 1 });
var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1 });
targetPolyline.addTo(getMap());
this.#targetsPolylines.push(targetPolyline)
}

View File

@ -119,35 +119,7 @@ export class UnitsManager {
this.#units[ID].setSelected(false);
}
}
getSelectedLeaders() {
var leaders: Unit[] = [];
for (let idx in this.getSelectedUnits())
{
var unit = this.getSelectedUnits()[idx];
if (unit.getFormationData().isLeader)
leaders.push(unit);
else if (unit.getFormationData().isWingman)
{
var leader = unit.getLeader();
if (leader && !leaders.includes(leader))
leaders.push(leader);
}
}
return leaders;
}
getSelectedSingletons() {
var singletons: Unit[] = [];
for (let idx in this.getSelectedUnits())
{
var unit = this.getSelectedUnits()[idx];
if (!unit.getFormationData().isLeader && !unit.getFormationData().isWingman)
singletons.push(unit);
}
return singletons;
}
getSelectedUnitsType () {
if (this.getSelectedUnits().length == 0)
return undefined;
@ -191,8 +163,17 @@ export class UnitsManager {
selectedUnitsAddDestination(latlng: L.LatLng) {
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits) {
var commandedUnit = selectedUnits[idx];
commandedUnit.addDestination(latlng);
const unit = selectedUnits[idx];
if (unit.getTaskData().currentState === "Follow")
{
const leader = this.getUnitByID(unit.getFormationData().leaderID)
if (leader && leader.getSelected())
leader.addDestination(latlng);
else
unit.addDestination(latlng);
}
else
unit.addDestination(latlng);
}
this.#showActionMessage(selectedUnits, " new destination added");
}
@ -200,8 +181,17 @@ export class UnitsManager {
selectedUnitsClearDestinations() {
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits) {
var commandedUnit = selectedUnits[idx];
commandedUnit.clearDestinations();
const unit = selectedUnits[idx];
if (unit.getTaskData().currentState === "Follow")
{
const leader = this.getUnitByID(unit.getFormationData().leaderID)
if (leader && leader.getSelected())
leader.clearDestinations();
else
unit.clearDestinations();
}
else
unit.clearDestinations();
}
}

View File

@ -23,7 +23,6 @@ protected:
virtual void setState(int newState);
bool isDestinationReached();
bool setActiveDestination();
void createHoldingPattern();
bool updateActivePath(bool looping);
void goToDestination(wstring enrouteTask = L"nil");
};

View File

@ -79,17 +79,9 @@ public:
json::value getFlags() { return flags; }
/********** Formation data **********/
void setIsLeader(bool newIsLeader);
void setIsWingman(bool newIsWingman);
void setLeader(Unit* newLeader);
void setWingmen(vector<Unit*> newWingmen);
void setFormation(wstring newFormation) { formation = newFormation; addMeasure(L"formation", json::value(formation));}
void setLeaderID(int newLeaderID) { leaderID = newLeaderID; addMeasure(L"leaderID", json::value(newLeaderID)); }
void setFormationOffset(Offset formationOffset);
bool getIsLeader() { return isLeader; }
bool getIsWingman() { return isWingman; }
Unit* getLeader() { return leader; }
vector<Unit*> getWingmen() { return wingmen; }
wstring getFormation() { return formation; }
int getLeaderID() { return leaderID; }
Offset getFormationoffset() { return formationOffset; }
/********** Task data **********/
@ -177,11 +169,7 @@ protected:
json::value flags = json::value::null();
/********** Formation data **********/
bool isLeader = false;
bool isWingman = false;
wstring formation = L"";
Unit *leader = nullptr;
vector<Unit *> wingmen;
int leaderID = NULL;
Offset formationOffset = Offset(NULL);
/********** Task data **********/
@ -215,7 +203,9 @@ protected:
/********** Functions **********/
virtual wstring getCategory() { return L"No category"; };
wstring getTargetName();
wstring getLeaderName();
bool isTargetAlive();
bool isLeaderAlive();
virtual void AIloop() = 0;
void addMeasure(wstring key, json::value value);
};

View File

@ -22,7 +22,7 @@ void AirUnit::setState(int newState)
{
if (state != newState)
{
/* Perform any action required when LEAVING a certain state */
/************ Perform any action required when LEAVING a certain state ************/
switch (state) {
case State::IDLE: {
break;
@ -35,6 +35,7 @@ void AirUnit::setState(int newState)
break;
}
case State::FOLLOW: {
setLeaderID(NULL);
break;
}
case State::LAND: {
@ -47,7 +48,7 @@ void AirUnit::setState(int newState)
break;
}
/* Perform any action required when ENTERING a certain state */
/************ Perform any action required when ENTERING a certain state ************/
switch (newState) {
case State::IDLE: {
clearActivePath();
@ -135,23 +136,6 @@ bool AirUnit::setActiveDestination()
}
}
void AirUnit::createHoldingPattern()
{
/* Air units must ALWAYS have a destination or they will RTB and become uncontrollable */
clearActivePath();
Coords point1;
Coords point2;
Coords point3;
Geodesic::WGS84().Direct(latitude, longitude, 45, 10000, point1.lat, point1.lng);
Geodesic::WGS84().Direct(point1.lat, point1.lng, 135, 10000, point2.lat, point2.lng);
Geodesic::WGS84().Direct(point2.lat, point2.lng, 225, 10000, point3.lat, point3.lng);
pushActivePathBack(point1);
pushActivePathBack(point2);
pushActivePathBack(point3);
pushActivePathBack(Coords(latitude, longitude));
log(unitName + L" holding pattern created");
}
bool AirUnit::updateActivePath(bool looping)
{
if (activePath.size() > 0)
@ -282,22 +266,22 @@ void AirUnit::AIloop()
clearActivePath();
activeDestination = Coords(NULL);
/* If the target is not alive (either not set or was destroyed) go back to IDLE */
if (!isTargetAlive()) {
/* If the leader is not alive (either not set or was destroyed) go back to IDLE */
if (!isLeaderAlive()) {
setState(State::IDLE);
break;
}
currentTask = L"Following " + getTargetName();
Unit* target = unitsManager->getUnit(targetID);
Unit* leader = unitsManager->getUnit(leaderID);
if (!hasTask) {
if (target != nullptr && target->getAlive() && formationOffset != NULL)
if (leader != nullptr && leader->getAlive() && formationOffset != NULL)
{
std::wostringstream taskSS;
taskSS << "{"
<< "id = 'FollowUnit'" << ", "
<< "leaderID = " << target->getID() << ","
<< "leaderID = " << leader->getID() << ","
<< "offset = {"
<< "x = " << formationOffset.x << ","
<< "y = " << formationOffset.y << ","
@ -332,5 +316,6 @@ void AirUnit::AIloop()
default:
break;
}
addMeasure(L"currentTask", json::value(currentTask));
}

View File

@ -145,30 +145,30 @@ void Scheduler::handleRequest(wstring key, json::value value)
else if (key.compare(L"followUnit") == 0)
{
int ID = value[L"ID"].as_integer();
int targetID = value[L"targetID"].as_integer();
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();
Unit* unit = unitsManager->getUnit(ID);
Unit* target = unitsManager->getUnit(targetID);
Unit* leader = unitsManager->getUnit(leaderID);
wstring unitName;
wstring targetName;
wstring leaderName;
if (unit != nullptr)
unitName = unit->getUnitName();
else
return;
if (target != nullptr)
targetName = target->getUnitName();
if (leader != nullptr)
leaderName = leader->getUnitName();
else
return;
log(L"Unit " + unitName + L" following unit " + targetName);
log(L"Unit " + unitName + L" following unit " + leaderName);
unit->setFormationOffset(Offset(offsetX, offsetY, offsetZ));
unit->setTargetID(targetID);
unit->setLeaderID(leaderID);
unit->setState(State::FOLLOW);
}
else if (key.compare(L"changeSpeed") == 0)
@ -208,40 +208,6 @@ void Scheduler::handleRequest(wstring key, json::value value)
command = dynamic_cast<Command*>(new Clone(ID, loc));
log(L"Cloning unit " + to_wstring(ID));
}
else if (key.compare(L"setLeader") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsManager->getUnit(ID);
bool isLeader = value[L"isLeader"].as_bool();
if (isLeader)
{
json::value wingmenIDs = value[L"wingmenIDs"];
vector<Unit*> wingmen;
if (unit != nullptr)
{
for (auto itr = wingmenIDs.as_array().begin(); itr != wingmenIDs.as_array().end(); itr++)
{
Unit* wingman = unitsManager->getUnit(itr->as_integer());
if (wingman != nullptr)
wingmen.push_back(wingman);
}
unit->setFormation(L"Line abreast");
unit->setIsLeader(true);
unit->setWingmen(wingmen);
log(L"Setting " + unit->getName() + L" as formation leader");
}
}
else {
unit->setIsLeader(false);
}
}
else if (key.compare(L"setFormation") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsManager->getUnit(ID);
wstring formation = value[L"formation"].as_string();
unit->setFormation(formation);
}
else if (key.compare(L"setROE") == 0)
{
int ID = value[L"ID"].as_integer();

View File

@ -20,6 +20,14 @@ Unit::Unit(json::value json, int ID) :
{
log("Creating unit with ID: " + to_string(ID));
addMeasure(L"currentState", json::value(L"Idle"));
addMeasure(L"TACANChannel", json::value(TACANChannel));
addMeasure(L"TACANXY", json::value(TACANXY));
addMeasure(L"TACANCallsign", json::value(TACANCallsign));
addMeasure(L"radioFrequency", json::value(radioFrequency));
addMeasure(L"radioCallsign", json::value(radioCallsign));
addMeasure(L"radioCallsignNumber", json::value(radioCallsignNumber));
}
Unit::~Unit()
@ -127,10 +135,10 @@ json::value Unit::getData(long long time)
/********** Formation data **********/
json[L"formationData"] = json::value::object();
for (auto key : { L"isLeader", L"isWingman", L"formation", L"wingmenIDs", L"leaderID" })
for (auto key : { L"leaderID" })
{
if (measures.find(key) != measures.end() && measures[key]->getTime() > time)
json[L"missionData"][key] = measures[key]->getValue();
json[L"formationData"][key] = measures[key]->getValue();
}
/********** Task data **********/
@ -154,12 +162,9 @@ json::value Unit::getData(long long time)
void Unit::setActivePath(list<Coords> newPath)
{
if (state != State::WINGMAN && state != State::FOLLOW)
{
activePath = newPath;
resetActiveDestination();
}
activePath = newPath;
resetActiveDestination();
auto path = json::value::object();
if (activePath.size() > 0) {
int count = 1;
@ -223,22 +228,6 @@ int Unit::getCoalitionID()
return 2;
}
void Unit::setLeader(Unit* newLeader)
{
leader = newLeader;
if (leader != nullptr)
addMeasure(L"leaderID", json::value(leader->getID()));
}
void Unit::setWingmen(vector<Unit*> newWingmen) {
wingmen = newWingmen;
auto wingmenIDs = json::value::object();
int i = 0;
for (auto itr = wingmen.begin(); itr != wingmen.end(); itr++)
wingmenIDs[i++] = (*itr)->getID();
addMeasure(L"wingmen", wingmenIDs);
}
wstring Unit::getTargetName()
{
if (isTargetAlive())
@ -262,6 +251,29 @@ bool Unit::isTargetAlive()
return false;
}
wstring Unit::getLeaderName()
{
if (isLeaderAlive())
{
Unit* leader = unitsManager->getUnit(leaderID);
if (leader != nullptr)
return leader->getUnitName();
}
return L"";
}
bool Unit::isLeaderAlive()
{
if (leaderID == NULL)
return false;
Unit* leader = unitsManager->getUnit(leaderID);
if (leader != nullptr)
return leader->alive;
else
return false;
}
void Unit::resetActiveDestination()
{
activeDestination = Coords(NULL);
@ -273,30 +285,6 @@ void Unit::resetTask()
scheduler->appendCommand(command);
}
void Unit::setIsLeader(bool newIsLeader) {
isLeader = newIsLeader;
if (!isLeader) {
for (auto wingman : wingmen)
{
wingman->setFormation(L"");
wingman->setIsWingman(false);
wingman->setLeader(nullptr);
}
}
addMeasure(L"isLeader", json::value(newIsLeader));
}
void Unit::setIsWingman(bool newIsWingman)
{
isWingman = newIsWingman;
if (isWingman)
setState(State::WINGMAN);
else
setState(State::IDLE);
addMeasure(L"isWingman", json::value(isWingman));
}
void Unit::setFormationOffset(Offset newFormationOffset)
{
formationOffset = newFormationOffset;