Completed advanced controls also for units

This commit is contained in:
Pax1601 2023-06-11 18:21:12 +02:00
parent fd00cffa15
commit 1d4ecf5316
28 changed files with 896 additions and 285 deletions

View File

@ -107,7 +107,7 @@ const DEMO_UNIT_DATA = {
["3"]:{
baseData: {
AI: true,
name: "2S6 Tunguska",
name: "M-60",
unitName: "Olympus 1-3",
groupName: "Group 4",
alive: true,

View File

@ -130,7 +130,7 @@
padding: 10px;
position: absolute;
width: fit-content;
z-index: 1000;
z-index: 9999;
}
.aic-enabled #aic-teleprompt {

View File

@ -11,7 +11,7 @@
left: 10px;
position: absolute;
top: 10px;
z-index: 1000;
z-index: 9999;
}
#app-icon>.ol-select-options {
@ -39,7 +39,7 @@
position: absolute;
right: 10px;
width: 180px;
z-index: 1000;
z-index: 9999;
}
#mouse-info-panel {
@ -51,7 +51,7 @@
right: 10px;
row-gap: 10px;
width: 180px;
z-index: 1000;
z-index: 9999;
}
#unit-control-panel {
@ -60,7 +60,7 @@
position: absolute;
top: 80px;
width: 320px;
z-index: 1000;
z-index: 9999;
}
#unit-info-panel {
@ -69,7 +69,7 @@
left: 10px;
position: absolute;
width: fit-content;
z-index: 1000;
z-index: 9999;
padding: 24px 30px;
}

View File

@ -260,7 +260,14 @@
background-image: url("/resources/theme/images/states/idle.svg");
}
[data-object|="unit"][data-state="attack"] .unit-state {
[data-object*="groundunit"][data-state="idle"] .unit-state {
background-image: url(""); /* To avoid clutter, dont show the idle state for non flying units */
}
[data-object|="unit"][data-state="attack"] .unit-state,
[data-object|="unit"][data-state="bombing point"] .unit-state,
[data-object|="unit"][data-state="carpet bombing"] .unit-state,
[data-object|="unit"][data-state="firing at area"] .unit-state {
background-image: url("/resources/theme/images/states/attack.svg");
}
@ -280,6 +287,10 @@
background-image: url("/resources/theme/images/states/dcs.svg");
}
[data-object|="unit"][data-state="no-task"] .unit-state {
background-image: url("/resources/theme/images/states/no-task.svg");
}
/*** Dead unit ***/
[data-object|="unit-aircraft"][data-is-dead] .unit-selected-spotlight,
[data-object|="unit-aircraft"][data-is-dead] .unit-short-label,

View File

@ -34,6 +34,10 @@ body {
width: 100%;
}
.hidden-cursor {
cursor: none !important;
}
a {
text-decoration: none;
}
@ -203,7 +207,7 @@ form>div {
max-height: 0;
overflow: hidden;
position: absolute;
z-index: 1000;
z-index: 9999;
}
.ol-select-options.scrollbar-visible {
@ -709,7 +713,6 @@ nav.ol-panel> :last-child {
position: relative;
row-gap: 10px;
width: 50%;
z-index: 10;
}
#splash-content::after {
@ -860,16 +863,18 @@ nav.ol-panel> :last-child {
}
.ol-destination-preview-icon {
background-color: var(--secondary-yellow);
border-radius: 999px;
cursor: grab;
background-image: url("/resources/theme/images/markers/move.svg");
height: 52px;
pointer-events: none;
width: 52px;
}
.ol-destination-preview {
.ol-target-icon {
background-image: url("/resources/theme/images/markers/target.svg");
height: 52px;
pointer-events: none;
width: 52px;
z-index: 9999;
}
dl.ol-data-grid {
@ -933,7 +938,7 @@ dl.ol-data-grid dd {
color: white;
justify-self: center;
position: absolute;
z-index: 1000;
z-index: 9999;
}
.ol-panel.ol-dialog {

View File

@ -271,7 +271,7 @@
position: absolute;
row-gap: 5px;
width: fit-content;
z-index: 1000;
z-index: 9999;
}
#unit-contextmenu button {
@ -307,6 +307,18 @@
content: url("/resources/theme/images/icons/sword.svg");
}
#bomb::before {
content: url("/resources/theme/images/icons/crosshairs-solid.svg");
}
#carpet-bomb::before {
content: url("/resources/theme/images/icons/explosion-solid.svg");
}
#fire-at-area::before {
content: url("/resources/theme/images/icons/crosshairs-solid.svg");
}
#follow::before {
content: url("/resources/theme/images/icons/follow.svg");
}
@ -393,5 +405,5 @@
position: absolute;
row-gap: 5px;
width: 180px;
z-index: 1000;
z-index: 9999;
}

View File

@ -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="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>

After

Width:  |  Height:  |  Size: 551 B

View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="52"
height="52"
viewBox="0 0 13.758333 13.758333"
version="1.1"
id="svg5"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
sodipodi:docname="move.svg"
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">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
width="52mm"
units="px"
inkscape:zoom="8.3856039"
inkscape:cx="45.73314"
inkscape:cy="10.673054"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
sodipodi:type="star"
style="mix-blend-mode:screen;fill:#247be2;fill-opacity:1;stroke:#ffffff;stroke-width:1.43972;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.6;stroke-opacity:1;paint-order:stroke fill markers"
id="path1252"
inkscape:flatsided="false"
sodipodi:sides="3"
sodipodi:cx="21.080975"
sodipodi:cy="4.8064618"
sodipodi:r1="2.6154003"
sodipodi:r2="1.3077002"
sodipodi:arg1="1.5707963"
sodipodi:arg2="2.6179939"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 21.080975,7.4218621 -1.132502,-1.9615502 -1.132502,-1.9615502 2.265004,0 2.265003,-1e-7 -1.132502,1.9615503 z"
transform="matrix(0.38984496,0.38984496,-0.38984496,0.38984496,2.4850522,-5.1648778)"
inkscape:transform-center-y="-0.18659976"
inkscape:transform-center-x="-0.18659993" />
<path
sodipodi:type="star"
style="mix-blend-mode:screen;fill:#247be2;fill-opacity:1;stroke:#ffffff;stroke-width:1.43972;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.6;stroke-opacity:1;paint-order:stroke fill markers"
id="path1252-3"
inkscape:flatsided="false"
sodipodi:sides="3"
sodipodi:cx="21.080975"
sodipodi:cy="4.8064618"
sodipodi:r1="2.6154003"
sodipodi:r2="1.3077002"
sodipodi:arg1="1.5707963"
sodipodi:arg2="2.6179939"
inkscape:rounded="0"
inkscape:randomized="0"
transform="matrix(-0.38984496,-0.38984496,0.38984496,-0.38984496,11.276723,18.916693)"
inkscape:transform-center-y="0.18660057"
d="m 21.080975,7.4218621 -1.132502,-1.9615502 -1.132502,-1.9615502 2.265004,0 2.265003,-1e-7 -1.132502,1.9615503 z"
inkscape:transform-center-x="0.18660032" />
<path
sodipodi:type="star"
style="mix-blend-mode:screen;fill:#247be2;fill-opacity:1;stroke:#ffffff;stroke-width:1.43972;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.6;stroke-opacity:1;paint-order:stroke fill markers"
id="path1252-9"
inkscape:flatsided="false"
sodipodi:sides="3"
sodipodi:cx="21.080975"
sodipodi:cy="4.8064618"
sodipodi:r1="2.6154003"
sodipodi:r2="1.3077002"
sodipodi:arg1="1.5707963"
sodipodi:arg2="2.6179939"
inkscape:rounded="0"
inkscape:randomized="0"
transform="matrix(-0.38984496,0.38984496,-0.38984496,-0.38984496,18.917375,2.4977722)"
d="m 21.080975,7.4218621 -1.132502,-1.9615502 -1.132502,-1.9615502 2.265004,0 2.265003,-1e-7 -1.132502,1.9615503 z"
inkscape:transform-center-x="-0.1865998"
inkscape:transform-center-y="0.1866003" />
<path
sodipodi:type="star"
style="mix-blend-mode:normal;fill:#247be2;fill-opacity:1;stroke:#ffffff;stroke-width:1.43972;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.6;stroke-opacity:1;paint-order:stroke fill markers"
id="path1252-3-0"
inkscape:flatsided="false"
sodipodi:sides="3"
sodipodi:cx="21.080975"
sodipodi:cy="4.8064618"
sodipodi:r1="2.6154003"
sodipodi:r2="1.3077002"
sodipodi:arg1="1.5707963"
sodipodi:arg2="2.6179939"
inkscape:rounded="0"
inkscape:randomized="0"
transform="matrix(0.38984496,-0.38984496,0.38984496,0.38984496,-5.187759,11.287231)"
d="m 21.080975,7.4218621 -1.132502,-1.9615502 -1.132502,-1.9615502 2.265004,0 2.265003,-1e-7 -1.132502,1.9615503 z"
inkscape:transform-center-x="0.18659972"
inkscape:transform-center-y="-0.18659965" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="52"
height="52"
viewBox="0 0 13.758333 13.758333"
version="1.1"
id="svg5"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
sodipodi:docname="target.svg"
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">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
width="52mm"
units="px"
inkscape:zoom="16.771208"
inkscape:cx="31.989348"
inkscape:cy="27.63665"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2">
<linearGradient
id="linearGradient4717"
inkscape:swatch="solid">
<stop
style="stop-color:#0cffff;stop-opacity:1;"
offset="0"
id="stop4715" />
</linearGradient>
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<ellipse
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1.03188;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.6;stroke-opacity:1;paint-order:stroke fill markers"
id="path5477"
cx="6.8599777"
cy="6.8209338"
rx="1.9410306"
ry="1.948356" />
<rect
style="fill:#ff5858;fill-opacity:1;stroke:#ffffff;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.6;stroke-opacity:1;paint-order:stroke fill markers"
id="rect3980"
width="0.54935014"
height="2.6236348"
x="6.5846372"
y="3.3855996"
rx="0.29515001" />
<rect
style="fill:#ff5858;fill-opacity:1;stroke:#ffffff;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.6;stroke-opacity:1;paint-order:stroke fill markers"
id="rect3980-4"
width="0.54935014"
height="2.6236348"
x="6.5858645"
y="7.6347723"
rx="0.29515001" />
<rect
style="fill:#ff5858;fill-opacity:1;stroke:#ffffff;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.6;stroke-opacity:1;paint-order:stroke fill markers"
id="rect3980-47"
width="0.54935014"
height="2.6236348"
x="6.5517845"
y="-10.252322"
rx="0.29515001"
transform="rotate(90)" />
<rect
style="fill:#ff5858;fill-opacity:1;stroke:#ffffff;stroke-width:0.79375;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.6;stroke-opacity:1;paint-order:stroke fill markers"
id="rect3980-4-3"
width="0.54935014"
height="2.6236348"
x="6.5530119"
y="-6.003149"
rx="0.29515001"
transform="rotate(90)" />
<ellipse
style="fill:none;fill-opacity:1;stroke:#ff5858;stroke-width:0.449792;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.6;stroke-opacity:1;paint-order:stroke fill markers"
id="path5477-2"
cx="6.8613305"
cy="6.8167095"
rx="1.9410306"
ry="1.948356" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 19 15"
version="1.1"
id="svg4"
sodipodi:docname="no-task.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
width="19"
height="15"
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:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="18.384776"
inkscape:cx="5.575265"
inkscape:cy="6.9350858"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
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 9.4628581,2.8509123 c 0.2704795,0 0.5200078,0.14155 0.6571519,0.3736899 l 4.114361,6.9453658 c 0.13905,0.234028 0.13905,0.522789 0.004,0.756817 -0.135238,0.234029 -0.388579,0.379351 -0.660964,0.379351 H 5.3487067 c -0.2723857,0 -0.5257221,-0.145322 -0.6609641,-0.379351 -0.1352419,-0.234028 -0.1333356,-0.524677 0.00402,-0.756817 L 8.8061218,3.2246022 C 8.9432655,2.9924623 9.1927939,2.8509123 9.4632778,2.8509123 Z m 0,2.4157789 c -0.2533409,0 -0.4571502,0.2019433 -0.4571502,0.4529583 v 2.1138038 c 0,0.251015 0.2038093,0.4529582 0.4571502,0.4529582 0.2533365,0 0.4571503,-0.2019432 0.4571503,-0.4529582 V 5.7196495 c 0,-0.251015 -0.2038138,-0.4529583 -0.4571503,-0.4529583 z m 0.6095349,4.2276119 a 0.60953517,0.60394517 0 1 0 -1.21907,0 0.60953517,0.60394517 0 1 0 1.21907,0 z"
id="path2"
style="fill:#ff5858;fill-opacity:1;stroke:#262626;stroke-width:2.24801;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.14086;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:5.6;stroke-opacity:1;paint-order:stroke fill markers"
id="rect1282"
width="2.0395968"
height="5.7258606"
x="8.4665051"
y="4.7092009"
rx="0.021248572" />
<path
d="m 9.4628581,2.8509123 c 0.2704795,0 0.5200078,0.14155 0.6571519,0.3736899 l 4.114361,6.9453658 c 0.13905,0.234028 0.13905,0.522789 0.004,0.756817 -0.135238,0.234029 -0.388579,0.379351 -0.660964,0.379351 H 5.3487067 c -0.2723857,0 -0.5257221,-0.145322 -0.6609641,-0.379351 -0.1352419,-0.234028 -0.1333356,-0.524677 0.00402,-0.756817 L 8.8061218,3.2246022 C 8.9432655,2.9924623 9.1927939,2.8509123 9.4632778,2.8509123 Z m 0,2.4157789 c -0.2533409,0 -0.4571502,0.2019433 -0.4571502,0.4529583 v 2.1138038 c 0,0.251015 0.2038093,0.4529582 0.4571502,0.4529582 0.2533365,0 0.4571503,-0.2019432 0.4571503,-0.4529582 V 5.7196495 c 0,-0.251015 -0.2038138,-0.4529583 -0.4571503,-0.4529583 z m 0.6095349,4.2276119 a 0.60953517,0.60394517 0 1 0 -1.21907,0 0.60953517,0.60394517 0 1 0 1.21907,0 z"
id="path2-9"
style="fill:#ff5858;fill-opacity:1;stroke:none;stroke-width:1.14086;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -40,10 +40,12 @@ interface TaskData {
targetSpeedType: string;
targetAltitude: number;
targetAltitudeType: string;
targetLocation: any;
isTanker: boolean;
isAWACS: boolean;
onOff: boolean;
followRoads: boolean;
targetID: number;
}
interface OptionsData {

View File

@ -13,6 +13,7 @@ import { TemporaryUnitMarker } from "./temporaryunitmarker";
import { ClickableMiniMap } from "./clickableminimap";
import { SVGInjector } from '@tanem/svg-injector'
import { layers as mapLayers, mapBounds, minimapBoundaries } from "../constants/constants";
import { TargetMarker } from "./targetmarker";
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
@ -20,12 +21,16 @@ L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
require("../../public/javascripts/leaflet.nauticscale.js")
/* Map constants */
export const IDLE = "IDLE";
export const UNIT_SELECTED = "MOVE_UNIT";
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"];
export class Map extends L.Map {
#ID: string;
#state: string;
#layer: L.TileLayer | null = null;
#preventLeftClick: boolean = false;
@ -40,8 +45,9 @@ export class Map extends L.Map {
#centerUnit: Unit | null = null;
#miniMap: ClickableMiniMap | null = null;
#miniMapLayerGroup: L.LayerGroup;
#temporaryMarkers: L.Marker[] = [];
#destinationPreviewMarkers: L.Marker[] = [];
#temporaryMarkers: TemporaryUnitMarker[] = [];
#destinationPreviewMarkers: DestinationPreviewMarker[] = [];
#targetMarker: TargetMarker;
#destinationGroupRotation: number = 0;
#computeDestinationRotation: boolean = false;
#destinationRotationCenter: L.LatLng | null = null;
@ -55,10 +61,13 @@ export class Map extends L.Map {
constructor(ID: string) {
/* Init the leaflet map */
//@ts-ignore
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);
this.#ID = ID;
this.setLayer(Object.keys(mapLayers)[0]);
/* Minimap */
@ -122,6 +131,9 @@ 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) {
@ -149,31 +161,20 @@ export class Map extends L.Map {
setState(state: string) {
this.#state = state;
if (this.#state === IDLE) {
/* Remove all the destination preview markers */
this.#destinationPreviewMarkers.forEach((marker: L.Marker) => {
this.removeLayer(marker);
})
this.#destinationPreviewMarkers = [];
this.#destinationGroupRotation = 0;
this.#computeDestinationRotation = false;
this.#destinationRotationCenter = null;
this.#resetDestinationMarkers();
this.#resetTargetMarker();
this.#showCursor();
}
else if (this.#state === UNIT_SELECTED) {
/* Remove all the exising destination preview markers */
this.#destinationPreviewMarkers.forEach((marker: L.Marker) => {
this.removeLayer(marker);
})
this.#destinationPreviewMarkers = [];
if (getUnitsManager().getSelectedUnits({ excludeHumans: true }).length > 1 && getUnitsManager().getSelectedUnits({ excludeHumans: true }).length < 20) {
/* Create the unit destination preview markers */
this.#destinationPreviewMarkers = getUnitsManager().getSelectedUnits({ excludeHumans: true }).map((unit: Unit) => {
var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), {interactive: false});
marker.addTo(this);
return marker;
})
}
else if (this.#state === MOVE_UNIT) {
this.#resetTargetMarker();
this.#createDestinationMarkers();
if (this.#destinationPreviewMarkers.length > 0)
this.#hideCursor();
}
else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) {
this.#resetDestinationMarkers();
this.#createTargetMarker();
this.#hideCursor();
}
document.dispatchEvent(new CustomEvent("mapStateChanged"));
}
@ -363,7 +364,7 @@ export class Map extends L.Map {
if (this.#state === IDLE) {
}
else if (this.#state === UNIT_SELECTED) {
else {
this.setState(IDLE);
getUnitsManager().deselectAllUnits();
}
@ -381,38 +382,26 @@ export class Map extends L.Map {
this.showMapContextMenu(e);
}
}
else if (this.#state === UNIT_SELECTED) {
if (e.originalEvent.shiftKey) {
var options: {[key: string]: {text: string, tooltip: string}} = {};
var selectedUnitTypes = getUnitsManager().getSelectedUnitsTypes();
if (selectedUnitTypes.length === 1 && ["Aircraft"].includes(selectedUnitTypes[0]))
{
options["bomb"] = {text: "Bomb here", tooltip: "Precision bombing of this specific point"};
options["carpet-bomb"] = {text: "Carpet bomb", tooltip: "Carpet bombing around this point"};
options["building-bomb"] = {text: "Bomb building", tooltip: "Precision bombing of the building closest to this point"};
}
if (selectedUnitTypes.length === 1 && ["GroundUnit"].includes(selectedUnitTypes[0]))
options["fire-at-area"] = {text: "Fire at area", tooltip: "Fire at this point"};
if (Object.keys(options).length > 0) {
this.showUnitContextMenu(e);
this.getUnitContextMenu().setOptions(options, (option: string) => {
this.hideUnitContextMenu();
this.#executeAction(e, option);
});
}
} else {
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;
this.#destinationRotationCenter = null;
this.#computeDestinationRotation = false;
else if (this.#state === MOVE_UNIT) {
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;
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());
}
else if (this.#state === CARPET_BOMBING) {
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());
}
}
@ -439,7 +428,7 @@ export class Map extends L.Map {
#onMouseDown(e: any) {
this.hideAllContextMenus();
if (this.#state == UNIT_SELECTED) {
if (this.#state == MOVE_UNIT) {
this.#destinationGroupRotation = 0;
this.#destinationRotationCenter = null;
this.#computeDestinationRotation = false;
@ -457,10 +446,14 @@ export class Map extends L.Map {
this.#lastMousePosition.x = e.originalEvent.x;
this.#lastMousePosition.y = e.originalEvent.y;
if (this.#computeDestinationRotation && this.#destinationRotationCenter != null)
this.#destinationGroupRotation = -bearing(this.#destinationRotationCenter.lat, this.#destinationRotationCenter.lng, this.getMouseCoordinates().lat, this.getMouseCoordinates().lng);
this.#updateDestinationPreview(e);
if (this.#state === MOVE_UNIT){
if (this.#computeDestinationRotation && this.#destinationRotationCenter != null)
this.#destinationGroupRotation = -bearing(this.#destinationRotationCenter.lat, this.#destinationRotationCenter.lng, this.getMouseCoordinates().lat, this.getMouseCoordinates().lng);
this.#updateDestinationPreview(e);
}
else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) {
this.#targetMarker.setLatLng(this.getMouseCoordinates());
}
}
#onZoom(e: any) {
@ -497,5 +490,48 @@ export class Map extends L.Map {
button.setAttribute("data-on-click-params", argument);
return button;
}
#createDestinationMarkers() {
this.#resetDestinationMarkers();
if (getUnitsManager().getSelectedUnits({ excludeHumans: true }).length > 0 && getUnitsManager().getSelectedUnits({ excludeHumans: true }).length < 20) {
/* Create the unit destination preview markers */
this.#destinationPreviewMarkers = getUnitsManager().getSelectedUnits({ excludeHumans: true }).map((unit: Unit) => {
var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), {interactive: false});
marker.addTo(this);
return marker;
})
}
}
#resetDestinationMarkers() {
/* Remove all the destination preview markers */
this.#destinationPreviewMarkers.forEach((marker: L.Marker) => {
this.removeLayer(marker);
})
this.#destinationPreviewMarkers = [];
this.#destinationGroupRotation = 0;
this.#computeDestinationRotation = false;
this.#destinationRotationCenter = null;
}
#createTargetMarker(){
this.#resetTargetMarker();
this.#targetMarker.addTo(this);
}
#resetTargetMarker() {
this.#targetMarker.setLatLng(new L.LatLng(0, 0));
this.removeLayer(this.#targetMarker);
}
#showCursor() {
document.getElementById(this.#ID)?.classList.remove("hidden-cursor");
}
#hideCursor() {
document.getElementById(this.#ID)?.classList.add("hidden-cursor");
}
}

View File

@ -0,0 +1,18 @@
import { DivIcon } from "leaflet";
import { CustomMarker } from "./custommarker";
export class TargetMarker extends CustomMarker {
#interactive: boolean = false;
createIcon() {
this.setIcon(new DivIcon({
iconSize: [52, 52],
iconAnchor: [26, 26],
className: "leaflet-target-marker",
}));
var el = document.createElement("div");
el.classList.add("ol-target-icon");
el.classList.toggle("ol-target-icon-interactive", this.#interactive)
this.getElement()?.appendChild(el);
}
}

View File

@ -127,8 +127,8 @@ export function spawnSmoke(color: string, latlng: LatLng) {
POST(data, () => { });
}
export function spawnExplosion(strength: number, latlng: LatLng) {
var command = { "strength": strength, "location": latlng };
export function spawnExplosion(intensity: number, latlng: LatLng) {
var command = { "intensity": intensity, "location": latlng };
var data = { "explosion": command }
POST(data, () => { });
}

View File

@ -2603,7 +2603,7 @@ export class AircraftDatabase extends UnitDatabase {
}
],
"roles": [
"Recon"
"Reconnaissance"
],
"code": "R-60M*2",
"name": "Heavy / Fox 2 / Long Range"

View File

@ -7,6 +7,8 @@ import { groundUnitsDatabase } from './groundunitsdatabase';
import { CustomMarker } from '../map/custommarker';
import { SVGInjector } from '@tanem/svg-injector';
import { UnitDatabase } from './unitdatabase';
import { BOMBING, CARPET_BOMBING, FIRE_AT_AREA, IDLE, MOVE_UNIT } from '../map/map';
import { TargetMarker } from '../map/targetmarker';
var pathIcon = new Icon({
iconUrl: '/resources/theme/images/markers/marker-icon.png',
@ -52,10 +54,12 @@ export class Unit extends CustomMarker {
targetSpeedType: "GS",
targetAltitude: 0,
targetAltitudeType: "AGL",
targetLocation: {},
isTanker: false,
isAWACS: false,
onOff: true,
followRoads: false
followRoads: false,
targetID: 0
},
optionsData: {
ROE: "",
@ -78,6 +82,8 @@ export class Unit extends CustomMarker {
#pathPolyline: Polyline;
#targetsPolylines: Polyline[];
#miniMapMarker: CircleMarker | null = null;
#targetLocationMarker: TargetMarker;
#targetLocationPolyline: Polyline;
#timer: number = 0;
@ -109,6 +115,9 @@ export class Unit extends CustomMarker {
this.#pathPolyline.addTo(getMap());
this.#targetsPolylines = [];
this.#targetLocationMarker = new TargetMarker(new LatLng(0, 0));
this.#targetLocationPolyline = new Polyline([], { color: '#FF0000', weight: 3, opacity: 0.5, smoothFactor: 1 });
/* Deselect units if they are hidden */
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300);
@ -152,11 +161,16 @@ export class Unit extends CustomMarker {
if ((this.getBaseData().alive || !selected) && this.getSelectable() && this.getSelected() != selected) {
this.#selected = selected;
this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-selected", selected);
if (selected)
if (selected) {
document.dispatchEvent(new CustomEvent("unitSelection", { detail: this }));
else
this.#updateMarker();
}
else {
document.dispatchEvent(new CustomEvent("unitDeselection", { detail: this }));
this.getGroupMembers().forEach((unit: Unit) => unit.setSelected(selected));
this.#clearDetectedUnits();
this.#clearPath();
this.#clearTarget();
}
}
}
@ -228,13 +242,17 @@ export class Unit extends CustomMarker {
if (updateMarker)
this.#updateMarker();
this.#clearTargets();
this.#clearDetectedUnits();
if (this.getSelected()) {
this.#drawPath();
this.#drawTargets();
this.#drawDetectedUnits();
this.#drawTarget();
}
else
else {
this.#clearPath();
this.#clearTarget();
}
document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this }));
}
@ -404,6 +422,15 @@ export class Unit extends CustomMarker {
return getUnitsManager().getUnitByID(this.getFormationData().leaderID);
}
canRole(roles: string | string[]) {
if (typeof(roles) === "string")
roles = [roles];
return this.getDatabase()?.getByName(this.getBaseData().name)?.loadouts.some((loadout: LoadoutBlueprint) => {
return (roles as string[]).some((role: string) => {return loadout.roles.includes(role)});
});
}
/********************** Unit commands *************************/
addDestination(latlng: L.LatLng) {
if (!this.getMissionData().flags.Human) {
@ -543,10 +570,9 @@ export class Unit extends CustomMarker {
/***********************************************/
#onClick(e: any) {
if (!this.#preventClick) {
if (getMap().getState() === 'IDLE' || getMap().getState() === 'MOVE_UNIT' || e.originalEvent.ctrlKey) {
if (!e.originalEvent.ctrlKey) {
if (getMap().getState() === IDLE || getMap().getState() === MOVE_UNIT || e.originalEvent.ctrlKey) {
if (!e.originalEvent.ctrlKey)
getUnitsManager().deselectAllUnits();
}
this.setSelected(!this.getSelected());
}
}
@ -563,20 +589,35 @@ export class Unit extends CustomMarker {
#onContextMenu(e: any) {
var options: {[key: string]: {text: string, tooltip: string}} = {};
const selectedUnits = getUnitsManager().getSelectedUnits();
const selectedUnitTypes = getUnitsManager().getSelectedUnitsTypes();
options["center-map"] = {text: "Center map", tooltip: "Center the map on the unit and follow it"};
if (getUnitsManager().getSelectedUnits().length > 0 && !(getUnitsManager().getSelectedUnits().length == 1 && (getUnitsManager().getSelectedUnits().includes(this)))) {
if (selectedUnits.length > 0 && !(selectedUnits.length == 1 && (selectedUnits.includes(this)))) {
options["attack"] = {text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons"};
if (getUnitsManager().getSelectedUnitsTypes().length == 1 && getUnitsManager().getSelectedUnitsTypes()[0] === "Aircraft")
options["follow"] = {text: "Follow", tooltip: "Follow the unit at a user defined distance and position"};;
}
else if ((getUnitsManager().getSelectedUnits().length > 0 && (getUnitsManager().getSelectedUnits().includes(this))) || getUnitsManager().getSelectedUnits().length == 0) {
else if ((selectedUnits.length > 0 && (selectedUnits.includes(this))) || selectedUnits.length == 0) {
if (this.getBaseData().category == "Aircraft") {
options["refuel"] = {text: "Air to air refuel", tooltip: "Refuel unit at the nearest AAR Tanker. If no tanker is available the unit will RTB."}; // TODO Add some way of knowing which aircraft can AAR
}
}
if ((selectedUnits.length === 0 && this.getBaseData().category == "Aircraft") || (selectedUnitTypes.length === 1 && ["Aircraft"].includes(selectedUnitTypes[0])))
{
if (selectedUnits.concat([this]).every((unit: Unit) => {return unit.canRole(["CAS", "Strike"])})) {
options["bomb"] = {text: "Precision bombing", tooltip: "Precision bombing of a specific point"};
options["carpet-bomb"] = {text: "Carpet bombing", tooltip: "Carpet bombing close to a point"};
}
}
if ((selectedUnits.length === 0 && this.getBaseData().category == "GroundUnit") || selectedUnitTypes.length === 1 && ["GroundUnit"].includes(selectedUnitTypes[0])) {
if (selectedUnits.concat([this]).every((unit: Unit) => {return unit.canRole(["Gun Artillery", "Rocket Artillery", "Infantry", "IFV", "Tank"])}))
options["fire-at-area"] = {text: "Fire at area", tooltip: "Fire at a large area"};
}
if (Object.keys(options).length > 0) {
getMap().showUnitContextMenu(e);
getMap().getUnitContextMenu().setOptions(options, (option: string) => {
@ -595,6 +636,12 @@ export class Unit extends CustomMarker {
getUnitsManager().selectedUnitsRefuel();
else if (action === "follow")
this.#showFollowOptions(e);
else if (action === "bomb")
getMap().setState(BOMBING);
else if (action === "carpet-bomb")
getMap().setState(CARPET_BOMBING);
else if (action === "fire-at-area")
getMap().setState(FIRE_AT_AREA);
}
#showFollowOptions(e: any) {
@ -679,6 +726,8 @@ export class Unit extends CustomMarker {
element.querySelector(".unit")?.setAttribute("data-state", "human");
else if (!this.getBaseData().AI) // Unit is under DCS control (not Olympus)
element.querySelector(".unit")?.setAttribute("data-state", "dcs");
else if ((this.getBaseData().category == "Aircraft" || this.getBaseData().category == "Helicopter") && !this.getMissionData().hasTask)
element.querySelector(".unit")?.setAttribute("data-state", "no-task");
else // Unit is under Olympus control
element.querySelector(".unit")?.setAttribute("data-state", this.getTaskData().currentState.toLowerCase());
@ -777,7 +826,7 @@ export class Unit extends CustomMarker {
this.#pathPolyline.setLatLngs([]);
}
#drawTargets() {
#drawDetectedUnits() {
for (let index in this.getMissionData().targets) {
var targetData = this.getMissionData().targets[index];
if (targetData.object != undefined){
@ -795,7 +844,7 @@ export class Unit extends CustomMarker {
color = "#00FF00";
else
color = "#FFFFFF";
var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1 });
var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1, dashArray: "4, 8" });
targetPolyline.addTo(getMap());
this.#targetsPolylines.push(targetPolyline)
}
@ -803,11 +852,48 @@ export class Unit extends CustomMarker {
}
}
#clearTargets() {
#clearDetectedUnits() {
for (let index in this.#targetsPolylines) {
getMap().removeLayer(this.#targetsPolylines[index])
}
}
#drawTarget() {
const targetLocation = this.getTaskData().targetLocation;
if (targetLocation.latitude && targetLocation.longitude && targetLocation.latitude != 0 && targetLocation.longitude != 0) {
const lat = targetLocation.latitude;
const lng = targetLocation.longitude;
if (lat && lng)
this.#drawTargetLocation(new LatLng(lat, lng));
}
else if (this.getTaskData().targetID != 0 && getUnitsManager().getUnitByID(this.getTaskData().targetID)) {
const flightData = getUnitsManager().getUnitByID(this.getTaskData().targetID)?.getFlightData();
const lat = flightData?.latitude;
const lng = flightData?.longitude;
if (lat && lng)
this.#drawTargetLocation(new LatLng(lat, lng));
}
else
this.#clearTarget();
}
#drawTargetLocation(targetLocation: LatLng) {
if (!getMap().hasLayer(this.#targetLocationMarker))
this.#targetLocationMarker.addTo(getMap());
if (!getMap().hasLayer(this.#targetLocationPolyline))
this.#targetLocationPolyline.addTo(getMap());
this.#targetLocationMarker.setLatLng(new LatLng(targetLocation.lat, targetLocation.lng));
this.#targetLocationPolyline.setLatLngs([new LatLng(this.getFlightData().latitude, this.getFlightData().longitude), new LatLng(targetLocation.lat, targetLocation.lng)])
}
#clearTarget() {
if (getMap().hasLayer(this.#targetLocationMarker))
this.#targetLocationMarker.removeFrom(getMap());
if (getMap().hasLayer(this.#targetLocationPolyline))
this.#targetLocationPolyline.removeFrom(getMap());
}
}
export class AirUnit extends Unit {

View File

@ -3,7 +3,7 @@ import { getHotgroupPanel, getInfoPopup, getMap, getUnitDataTable } from "..";
import { Unit } from "./unit";
import { cloneUnit } from "../server/server";
import { deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots } from "../other/utils";
import { IDLE, UNIT_SELECTED } from "../map/map";
import { IDLE, MOVE_UNIT } from "../map/map";
export class UnitsManager {
#units: { [ID: number]: Unit };
@ -52,10 +52,12 @@ export class UnitsManager {
}
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);
if (constructor != undefined) {
this.#units[ID] = new constructor(ID, data);
var constructor = Unit.getConstructor(data.baseData.category);
if (constructor != undefined) {
this.#units[ID] = new constructor(ID, data);
}
}
}
@ -498,7 +500,7 @@ export class UnitsManager {
#onUnitSelection(unit: Unit) {
if (this.getSelectedUnits().length > 0) {
getMap().setState(UNIT_SELECTED);
getMap().setState(MOVE_UNIT);
/* Disable the firing of the selection event for a certain amount of time. This avoids firing many events if many units are selected */
if (!this.#selectionEventDisabled) {
window.setTimeout(() => {

View File

@ -84,10 +84,10 @@
<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">
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "strength": 1 }'>Small explosion</button>
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "strength": 2 }'>Medium explosion</button>
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "strength": 3 }'>Big explosion</button>
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "strength": 4 }'>Huge explosion</button>
<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>
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "strength": 400 }'>Huge explosion</button>
</div>
</div>

View File

@ -140,50 +140,57 @@ function Olympus.buildTask(options)
}
}
elseif options['id'] == 'Bombing' and options['lat'] and options['lng'] then
local point = coord.LLtoLO(options['lat'], options['lng'], 0)
task = {
id = 'Bombing',
params = {
point = coord.LLtoLO(options['lat'], options['lng'], 0),
point = {x = point.x, y = point.z},
attackQty = 1
}
}
elseif options['id'] == 'CarpetBombing' and options['lat'] and options['lng'] then
local point = coord.LLtoLO(options['lat'], options['lng'], 0)
task = {
id = 'CarpetBombing',
params = {
point = coord.LLtoLO(options['lat'], options['lng'], 0),
attackQty = 1,
x = point.x,
y = point.z,
carpetLength = 1000,
attackType = 'Carpet'
attackType = 'Carpet',
expend = "All",
attackQty = 1,
attackQtyLimit = true
}
}
elseif options['id'] == 'AttackMapObject' and options['lat'] and options['lng'] then
local point = coord.LLtoLO(options['lat'], options['lng'], 0)
task = {
id = 'AttackMapObject',
params = {
point = coord.LLtoLO(options['lat'], options['lng'], 0),
point = {x = point.x, y = point.z}
}
}
end
elseif options['id'] == 'FireAtPoint' and options['lat'] and options['lng'] and options['radius'] then
local point = coord.LLtoLO(options['lat'], options['lng'], 0)
task = {
id = 'AttackMapObject',
id = 'FireAtPoint',
params = {
point = coord.LLtoLO(options['lat'], options['lng'], 0),
point = {x = point.x, y = point.z},
radius = options['radius']
}
}
end
end
return task
end
-- Move a unit. Since many tasks in DCS are Enroute tasks, this function is an important way to control the unit AI
function Olympus.move(ID, lat, lng, altitude, altitudeType, speed, speedType, category, taskOptions)
Olympus.debug("Olympus.move " .. ID .. " (" .. lat .. ", " .. lng ..") " .. altitude .. "m " .. altitudeType .. " ".. speed .. "m/s " .. category .. " " .. Olympus.serializeTable(taskOptions), 2)
local unit = Olympus.getUnitByID(ID)
if unit then
function Olympus.move(groupName, lat, lng, altitude, altitudeType, speed, speedType, category, taskOptions)
Olympus.debug("Olympus.move " .. groupName .. " (" .. lat .. ", " .. lng ..") " .. altitude .. "m " .. altitudeType .. " ".. speed .. "m/s " .. category .. " " .. Olympus.serializeTable(taskOptions), 2)
local group = Group.getByName(groupName)
if group then
if category == "Aircraft" then
local startPoint = mist.getLeadPos(unit:getGroup())
local startPoint = mist.getLeadPos(group)
local endPoint = coord.LLtoLO(lat, lng, 0)
if altitudeType == "AGL" then
@ -221,7 +228,6 @@ function Olympus.move(ID, lat, lng, altitude, altitudeType, speed, speedType, ca
},
},
}
group = unit:getGroup()
local groupCon = group:getController()
if groupCon then
groupCon:setTask(missionTask)
@ -230,7 +236,7 @@ function Olympus.move(ID, lat, lng, altitude, altitudeType, speed, speedType, ca
elseif category == "GroundUnit" then
vars =
{
group = unit:getGroup(),
group = group,
point = coord.LLtoLO(lat, lng, 0),
heading = 0,
speed = speed
@ -249,7 +255,7 @@ function Olympus.move(ID, lat, lng, altitude, altitudeType, speed, speedType, ca
Olympus.debug("Olympus.move not implemented yet for " .. category, 2)
end
else
Olympus.debug("Error in Olympus.move " .. ID, 2)
Olympus.debug("Error in Olympus.move " .. groupName, 2)
end
end
@ -408,8 +414,61 @@ function Olympus.spawnAircraft(coalition, unitType, lat, lng, alt, spawnOptions)
},
}
end
else
route = {
["points"] =
{
[1] =
{
["alt"] = alt,
["alt_type"] = "BARO",
["task"] =
{
["id"] = "ComboTask",
["params"] =
{
["tasks"] =
{
[1] =
{
["number"] = 1,
["auto"] = true,
["id"] = "WrappedAction",
["enabled"] = true,
["params"] =
{
["action"] =
{
["id"] = "EPLRS",
["params"] =
{
["value"] = true
},
},
},
},
[2] =
{
["number"] = 2,
["auto"] = false,
["id"] = "Orbit",
["enabled"] = true,
["params"] =
{
["pattern"] = "Circle"
},
},
},
},
},
["type"] = "Turning Point",
["x"] = spawnLocation.x,
["y"] = spawnLocation.z,
}, -- end of [1]
}, -- end of ["points"]
} -- end of ["route"]
end
local vars =
{
units = unitTable,
@ -461,51 +520,51 @@ function Olympus.delete(ID, explosion)
end
end
function Olympus.setTask(ID, taskOptions)
Olympus.debug("Olympus.setTask " .. ID .. " " .. Olympus.serializeTable(taskOptions), 2)
local unit = Olympus.getUnitByID(ID)
if unit then
function Olympus.setTask(groupName, taskOptions)
Olympus.debug("Olympus.setTask " .. groupName .. " " .. Olympus.serializeTable(taskOptions), 2)
local group = Group.getByName(groupName)
if group then
local task = Olympus.buildTask(taskOptions);
Olympus.debug("Olympus.setTask " .. Olympus.serializeTable(task), 20)
if task then
unit:getGroup():getController():setTask(task)
group:getController():setTask(task)
Olympus.debug("Olympus.setTask completed successfully", 2)
end
end
end
function Olympus.resetTask(ID)
Olympus.debug("Olympus.resetTask " .. ID, 2)
local unit = Olympus.getUnitByID(ID)
if unit then
unit:getGroup():getController():resetTask()
function Olympus.resetTask(groupName)
Olympus.debug("Olympus.resetTask " .. groupName, 2)
local group = Group.getByName(groupName)
if group then
group:getController():resetTask()
Olympus.debug("Olympus.resetTask completed successfully", 2)
end
end
function Olympus.setCommand(ID, command)
Olympus.debug("Olympus.setCommand " .. ID .. " " .. Olympus.serializeTable(command), 2)
local unit = Olympus.getUnitByID(ID)
if unit then
unit:getGroup():getController():setCommand(command)
function Olympus.setCommand(groupName, command)
Olympus.debug("Olympus.setCommand " .. groupName .. " " .. Olympus.serializeTable(command), 2)
local group = Group.getByName(groupName)
if group then
group:getController():setCommand(command)
Olympus.debug("Olympus.setCommand completed successfully", 2)
end
end
function Olympus.setOption(ID, optionID, optionValue)
Olympus.debug("Olympus.setOption " .. ID .. " " .. optionID .. " " .. tostring(optionValue), 2)
local unit = Olympus.getUnitByID(ID)
if unit then
unit:getGroup():getController():setOption(optionID, optionValue)
function Olympus.setOption(groupName, optionID, optionValue)
Olympus.debug("Olympus.setOption " .. groupName .. " " .. optionID .. " " .. tostring(optionValue), 2)
local group = Group.getByName(groupName)
if group then
group:getController():setOption(optionID, optionValue)
Olympus.debug("Olympus.setOption completed successfully", 2)
end
end
function Olympus.setOnOff(ID, onOff)
Olympus.debug("Olympus.setOnOff " .. ID .. " " .. tostring(onOff), 2)
local unit = Olympus.getUnitByID(ID)
if unit then
unit:getGroup():getController():setOnOff(onOff)
function Olympus.setOnOff(groupName, onOff)
Olympus.debug("Olympus.setOnOff " .. groupName .. " " .. tostring(onOff), 2)
local group = Group.getByName(groupName)
if group then
group:getController():setOnOff(onOff)
Olympus.debug("Olympus.setOnOff completed successfully", 2)
end
end

View File

@ -97,14 +97,15 @@ protected:
class Move : public Command
{
public:
Move(int ID, Coords destination, double speed, wstring speedType, double altitude, wstring altitudeType, wstring taskOptions):
ID(ID),
Move(wstring groupName, Coords destination, double speed, wstring speedType, double altitude, wstring altitudeType, wstring taskOptions, wstring category):
groupName(groupName),
destination(destination),
speed(speed),
speedType(speedType),
altitude(altitude),
altitudeType(altitudeType),
taskOptions(taskOptions)
taskOptions(taskOptions),
category(category)
{
priority = CommandPriority::HIGH;
};
@ -112,13 +113,14 @@ public:
virtual int getLoad() { return 5; }
private:
const int ID;
const wstring groupName;
const Coords destination;
const double speed;
const wstring speedType;
const double altitude;
const wstring altitudeType;
const wstring taskOptions;
const wstring category;
};
/* Smoke command */
@ -223,8 +225,8 @@ private:
class SetTask : public Command
{
public:
SetTask(int ID, wstring task) :
ID(ID),
SetTask(wstring groupName, wstring task) :
groupName(groupName),
task(task)
{
priority = CommandPriority::MEDIUM;
@ -233,7 +235,7 @@ public:
virtual int getLoad() { return 10; }
private:
const int ID;
const wstring groupName;
const wstring task;
};
@ -241,8 +243,8 @@ private:
class ResetTask : public Command
{
public:
ResetTask(int ID) :
ID(ID)
ResetTask(wstring groupName) :
groupName(groupName)
{
priority = CommandPriority::HIGH;
};
@ -250,15 +252,15 @@ public:
virtual int getLoad() { return 10; }
private:
const int ID;
const wstring groupName;
};
/* Set command */
class SetCommand : public Command
{
public:
SetCommand(int ID, wstring command) :
ID(ID),
SetCommand(wstring groupName, wstring command) :
groupName(groupName),
command(command)
{
priority = CommandPriority::HIGH;
@ -267,7 +269,7 @@ public:
virtual int getLoad() { return 10; }
private:
const int ID;
const wstring groupName;
const wstring command;
};
@ -275,8 +277,8 @@ private:
class SetOption : public Command
{
public:
SetOption(int ID, int optionID, int optionValue) :
ID(ID),
SetOption(wstring groupName, int optionID, int optionValue) :
groupName(groupName),
optionID(optionID),
optionValue(optionValue),
optionBool(false),
@ -285,8 +287,8 @@ public:
priority = CommandPriority::HIGH;
};
SetOption(int ID, int optionID, bool optionBool) :
ID(ID),
SetOption(wstring groupName, int optionID, bool optionBool) :
groupName(groupName),
optionID(optionID),
optionValue(0),
optionBool(optionBool),
@ -298,7 +300,7 @@ public:
virtual int getLoad() { return 10; }
private:
const int ID;
const wstring groupName;
const int optionID;
const int optionValue;
const bool optionBool;
@ -309,8 +311,8 @@ private:
class SetOnOff : public Command
{
public:
SetOnOff(int ID, bool onOff) :
ID(ID),
SetOnOff(wstring groupName, bool onOff) :
groupName(groupName),
onOff(onOff)
{
priority = CommandPriority::HIGH;
@ -319,7 +321,7 @@ public:
virtual int getLoad() { return 10; }
private:
const int ID;
const wstring groupName;
const bool onOff;
};

View File

@ -6,6 +6,8 @@
#include "measure.h"
#include "logger.h"
#define TASK_CHECK_INIT_VALUE 10
namespace State
{
enum States
@ -63,7 +65,7 @@ public:
int getID() { return ID; }
void updateExportData(json::value json);
void updateMissionData(json::value json);
json::value getData(long long time);
json::value getData(long long time, bool getAll = false);
virtual wstring getCategory() { return L"No category"; };
/********** Base data **********/
@ -100,7 +102,7 @@ public:
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 setTargets(json::value newTargets) {targets = newTargets; addMeasure(L"targets", json::value(newTargets));}
void setHasTask(bool newHasTask) { hasTask = newHasTask; addMeasure(L"hasTask", json::value(newHasTask)); }
void setHasTask(bool newHasTask);
void setCoalitionID(int newCoalitionID);
void setFlags(json::value newFlags) { flags = newFlags; addMeasure(L"flags", json::value(newFlags));}
@ -181,6 +183,7 @@ protected:
int ID;
map<wstring, Measure*> measures;
int taskCheckCounter = 0;
/********** Base data **********/
bool AI = false;
@ -252,4 +255,6 @@ protected:
bool setActiveDestination();
bool updateActivePath(bool looping);
void goToDestination(wstring enrouteTask = L"nil");
bool checkTaskFailed();
void resetTaskFailedCounter();
};

View File

@ -11,6 +11,11 @@ public:
~UnitsManager();
Unit* getUnit(int ID);
bool isUnitInGroup(Unit* unit);
bool isUnitGroupLeader(Unit* unit);
Unit* getGroupLeader(int ID);
Unit* getGroupLeader(Unit* unit);
vector<Unit*> getGroupMembers(wstring groupName);
void updateExportData(lua_State* L);
void updateMissionData(json::value missionData);
void getData(json::value& answer, long long time);

View File

@ -143,7 +143,7 @@ void AirUnit::AIloop()
else {
taskSS << "{ id = 'Orbit', pattern = 'Circle' }";
}
Command* command = dynamic_cast<Command*>(new SetTask(ID, taskSS.str()));
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str()));
scheduler->appendCommand(command);
setHasTask(true);
}
@ -248,7 +248,7 @@ void AirUnit::AIloop()
<< "z = " << formationOffset.z
<< "},"
<< "}";
Command* command = dynamic_cast<Command*>(new SetTask(ID, taskSS.str()));
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str()));
scheduler->appendCommand(command);
setHasTask(true);
}
@ -264,7 +264,7 @@ void AirUnit::AIloop()
taskSS << "{"
<< "id = 'Refuel'"
<< "}";
Command* command = dynamic_cast<Command*>(new SetTask(ID, taskSS.str()));
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str()));
scheduler->appendCommand(command);
setHasTask(true);
}
@ -279,7 +279,7 @@ void AirUnit::AIloop()
if (!getHasTask()) {
std::wostringstream taskSS;
taskSS << "{id = 'Bombing', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << "}";
Command* command = dynamic_cast<Command*>(new SetTask(ID, taskSS.str()));
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str()));
scheduler->appendCommand(command);
setHasTask(true);
}
@ -290,10 +290,11 @@ void AirUnit::AIloop()
if (!getHasTask()) {
std::wostringstream taskSS;
taskSS << "{id = 'CarpetBombing', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << "}";
Command* command = dynamic_cast<Command*>(new SetTask(ID, taskSS.str()));
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str()));
scheduler->appendCommand(command);
setHasTask(true);
}
break;
}
case State::BOMB_BUILDING: {
currentTask = L"Bombing building";
@ -301,14 +302,16 @@ void AirUnit::AIloop()
if (!getHasTask()) {
std::wostringstream taskSS;
taskSS << "{id = 'AttackMapObject', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << "}";
Command* command = dynamic_cast<Command*>(new SetTask(ID, taskSS.str()));
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str()));
scheduler->appendCommand(command);
setHasTask(true);
}
break;
}
default:
break;
}
addMeasure(L"currentTask", json::value(currentTask));
}

View File

@ -9,27 +9,20 @@ extern UnitsManager* unitsManager;
/* Move command */
wstring Move::getString(lua_State* L)
{
Unit* unit = unitsManager->getUnit(ID);
if (unit != nullptr)
{
std::wostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.move, "
<< ID << ", "
<< destination.lat << ", "
<< destination.lng << ", "
<< altitude << ", "
<< "\"" << altitudeType << "\"" << ", "
<< speed << ", "
<< "\"" << speedType << "\"" << ", "
<< "\"" << unit->getCategory() << "\"" << ", "
<< taskOptions;
return commandSS.str();
}
else
{
return L"";
}
std::wostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.move, "
<< "\"" << groupName << "\"" << ", "
<< destination.lat << ", "
<< destination.lng << ", "
<< altitude << ", "
<< "\"" << altitudeType << "\"" << ", "
<< speed << ", "
<< "\"" << speedType << "\"" << ", "
<< "\"" << category << "\"" << ", "
<< taskOptions;
return commandSS.str();
}
/* Smoke command */
@ -117,7 +110,7 @@ wstring SetTask::getString(lua_State* L)
std::wostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.setTask, "
<< ID << ", "
<< "\"" << groupName << "\"" << ", "
<< task;
return commandSS.str();
@ -129,7 +122,7 @@ wstring ResetTask::getString(lua_State* L)
std::wostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.resetTask, "
<< ID;
<< "\"" << groupName << "\"";
return commandSS.str();
}
@ -140,7 +133,7 @@ wstring SetCommand::getString(lua_State* L)
std::wostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.setCommand, "
<< ID << ", "
<< "\"" << groupName << "\"" << ", "
<< command;
return commandSS.str();
@ -154,12 +147,12 @@ wstring SetOption::getString(lua_State* L)
if (!isBoolean) {
commandSS << "Olympus.setOption, "
<< ID << ", "
<< "\"" << groupName << "\"" << ", "
<< optionID << ", "
<< optionValue;
} else {
commandSS << "Olympus.setOption, "
<< ID << ", "
<< "\"" << groupName << "\"" << ", "
<< optionID << ", "
<< (optionBool? "true": "false");
}
@ -173,7 +166,7 @@ wstring SetOnOff::getString(lua_State* L)
commandSS.precision(10);
commandSS << "Olympus.setOnOff, "
<< ID << ", "
<< "\"" << groupName << "\"" << ", "
<< (onOff ? "true" : "false");
return commandSS.str();

View File

@ -73,7 +73,6 @@ void GroundUnit::setState(int newState)
void GroundUnit::AIloop()
{
switch (state) {
case State::IDLE: {
currentTask = L"Idle";
@ -112,7 +111,7 @@ void GroundUnit::AIloop()
if (!getHasTask()) {
std::wostringstream taskSS;
taskSS << "{id = 'FireAtPoint', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << ", radius = 1000}";
Command* command = dynamic_cast<Command*>(new SetTask(ID, taskSS.str()));
Command* command = dynamic_cast<Command*>(new SetTask(groupName, taskSS.str()));
scheduler->appendCommand(command);
setHasTask(true);
}
@ -140,7 +139,7 @@ void GroundUnit::changeSpeed(wstring change)
void GroundUnit::setOnOff(bool newOnOff)
{
Unit::setOnOff(newOnOff);
Command* command = dynamic_cast<Command*>(new SetOnOff(ID, onOff));
Command* command = dynamic_cast<Command*>(new SetOnOff(groupName, onOff));
scheduler->appendCommand(command);
}

View File

@ -59,7 +59,8 @@ void Scheduler::handleRequest(wstring key, json::value value)
if (key.compare(L"setPath") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsManager->getUnit(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr)
{
wstring unitName = unit->getUnitName();
@ -75,15 +76,9 @@ void Scheduler::handleRequest(wstring key, json::value value)
newPath.push_back(dest);
}
Unit* unit = unitsManager->getUnit(ID);
if (unit != nullptr)
{
unit->setActivePath(newPath);
unit->setState(State::REACH_DESTINATION);
log(unitName + L" new path set successfully");
}
else
log(unitName + L" not found, request will be discarded");
unit->setActivePath(newPath);
unit->setState(State::REACH_DESTINATION);
log(unitName + L" new path set successfully");
}
}
else if (key.compare(L"smoke") == 0)
@ -123,7 +118,7 @@ void Scheduler::handleRequest(wstring key, json::value value)
int ID = value[L"ID"].as_integer();
int targetID = value[L"targetID"].as_integer();
Unit* unit = unitsManager->getUnit(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
Unit* target = unitsManager->getUnit(targetID);
wstring unitName;
@ -151,7 +146,7 @@ void Scheduler::handleRequest(wstring key, json::value value)
int offsetY = value[L"offsetY"].as_integer();
int offsetZ = value[L"offsetZ"].as_integer();
Unit* unit = unitsManager->getUnit(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
Unit* leader = unitsManager->getUnit(leaderID);
wstring unitName;
@ -175,42 +170,42 @@ void Scheduler::handleRequest(wstring key, json::value value)
else if (key.compare(L"changeSpeed") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsManager->getUnit(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr)
unit->changeSpeed(value[L"change"].as_string());
}
else if (key.compare(L"changeAltitude") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsManager->getUnit(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr)
unit->changeAltitude(value[L"change"].as_string());
}
else if (key.compare(L"setSpeed") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsManager->getUnit(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr)
unit->setTargetSpeed(value[L"speed"].as_double());
}
else if (key.compare(L"setSpeedType") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsManager->getUnit(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr)
unit->setTargetSpeedType(value[L"speedType"].as_string());
}
else if (key.compare(L"setAltitude") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsManager->getUnit(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr)
unit->setTargetAltitude(value[L"altitude"].as_double());
}
else if (key.compare(L"setAltitudeType") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsManager->getUnit(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr)
unit->setTargetAltitudeType(value[L"altitudeType"].as_string());
}
@ -226,28 +221,28 @@ void Scheduler::handleRequest(wstring key, json::value value)
else if (key.compare(L"setROE") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsManager->getUnit(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
wstring ROE = value[L"ROE"].as_string();
unit->setROE(ROE);
}
else if (key.compare(L"setReactionToThreat") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsManager->getUnit(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
wstring reactionToThreat = value[L"reactionToThreat"].as_string();
unit->setReactionToThreat(reactionToThreat);
}
else if (key.compare(L"setEmissionsCountermeasures") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsManager->getUnit(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
wstring emissionsCountermeasures = value[L"emissionsCountermeasures"].as_string();
unit->setEmissionsCountermeasures(emissionsCountermeasures);
}
else if (key.compare(L"landAt") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsManager->getUnit(ID);
Unit* unit = unitsManager->getGroupLeader(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;
@ -262,13 +257,13 @@ void Scheduler::handleRequest(wstring key, json::value value)
else if (key.compare(L"refuel") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsManager->getUnit(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
unit->setState(State::REFUEL);
}
else if (key.compare(L"setAdvancedOptions") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsManager->getUnit(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
if (unit != nullptr)
{
/* Advanced tasking */
@ -306,14 +301,14 @@ void Scheduler::handleRequest(wstring key, json::value value)
{
int ID = value[L"ID"].as_integer();
bool followRoads = value[L"followRoads"].as_bool();
Unit* unit = unitsManager->getUnit(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
unit->setFollowRoads(followRoads);
}
else if (key.compare(L"setOnOff") == 0)
{
int ID = value[L"ID"].as_integer();
bool onOff = value[L"onOff"].as_bool();
Unit* unit = unitsManager->getUnit(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
unit->setOnOff(onOff);
}
else if (key.compare(L"explosion") == 0)
@ -331,8 +326,9 @@ void Scheduler::handleRequest(wstring key, json::value value)
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->getUnit(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
unit->setState(State::BOMB_POINT);
unit->setTargetLocation(loc);
}
else if (key.compare(L"carpetBomb") == 0)
{
@ -340,8 +336,9 @@ void Scheduler::handleRequest(wstring key, json::value value)
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->getUnit(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
unit->setState(State::CARPET_BOMB);
unit->setTargetLocation(loc);
}
else if (key.compare(L"bombBuilding") == 0)
{
@ -349,8 +346,9 @@ void Scheduler::handleRequest(wstring key, json::value value)
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->getUnit(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
unit->setState(State::BOMB_BUILDING);
unit->setTargetLocation(loc);
}
else if (key.compare(L"fireAtArea") == 0)
{
@ -358,8 +356,9 @@ void Scheduler::handleRequest(wstring key, json::value value)
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->getUnit(ID);
Unit* unit = unitsManager->getGroupLeader(ID);
unit->setState(State::FIRE_AT_AREA);
unit->setTargetLocation(loc);
}
else
{

View File

@ -116,8 +116,21 @@ void Unit::updateExportData(json::value json)
setAI(getUnitName().find(L"Olympus") != wstring::npos);
/* 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) */
if (getAI() && getAlive() && getFlags()[L"Human"].as_bool() == false)
// TODO at the moment groups will stop moving correctly if the leader dies
const bool isUnitControlledByOlympus = getAI();
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();
// 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);
AIloop();
}
}
void Unit::updateMissionData(json::value json)
@ -132,10 +145,14 @@ void Unit::updateMissionData(json::value json)
setHasTask(json[L"hasTask"].as_bool());
}
json::value Unit::getData(long long time)
json::value Unit::getData(long long time, bool sendAll)
{
auto json = json::value::object();
/* 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"AI", L"name", L"unitName", L"groupName", L"alive", L"category"})
@ -146,7 +163,7 @@ json::value Unit::getData(long long time)
if (json[L"baseData"].size() == 0)
json.erase(L"baseData");
if (alive) {
if (alive || sendAll) {
/********** Flight data **********/
json[L"flightData"] = json::value::object();
for (auto key : { L"latitude", L"longitude", L"altitude", L"speed", L"heading" })
@ -177,25 +194,28 @@ json::value Unit::getData(long long time)
if (json[L"formationData"].size() == 0)
json.erase(L"formationData");
/********** Task data **********/
json[L"taskData"] = json::value::object();
for (auto key : { L"currentState", L"currentTask", L"targetSpeed", L"targetAltitude", L"targetSpeedType", L"targetAltitudeType", 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");
/* 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"targetSpeed", L"targetAltitude", L"targetSpeedType", L"targetAltitudeType", 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();
/********** 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");
}
if (json[L"optionsData"].size() == 0)
json.erase(L"optionsData");
}
return json;
@ -322,9 +342,10 @@ void Unit::resetActiveDestination()
void Unit::resetTask()
{
Command* command = dynamic_cast<Command*>(new ResetTask(ID));
Command* command = dynamic_cast<Command*>(new ResetTask(groupName));
scheduler->appendCommand(command);
setHasTask(false);
resetTaskFailedCounter();
}
void Unit::setFormationOffset(Offset newFormationOffset)
@ -353,7 +374,7 @@ void Unit::setROE(wstring newROE) {
else
return;
Command* command = dynamic_cast<Command*>(new SetOption(ID, SetCommandType::ROE, ROEEnum));
Command* command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::ROE, ROEEnum));
scheduler->appendCommand(command);
}
}
@ -378,7 +399,7 @@ void Unit::setReactionToThreat(wstring newReactionToThreat) {
else
return;
Command* command = dynamic_cast<Command*>(new SetOption(ID, SetCommandType::REACTION_ON_THREAT, reactionToThreatEnum));
Command* command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::REACTION_ON_THREAT, reactionToThreatEnum));
scheduler->appendCommand(command);
}
}
@ -421,13 +442,13 @@ void Unit::setEmissionsCountermeasures(wstring newEmissionsCountermeasures) {
Command* command;
command = dynamic_cast<Command*>(new SetOption(ID, SetCommandType::RADAR_USING, radarEnum));
command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::RADAR_USING, radarEnum));
scheduler->appendCommand(command);
command = dynamic_cast<Command*>(new SetOption(ID, SetCommandType::FLARE_USING, flareEnum));
command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::FLARE_USING, flareEnum));
scheduler->appendCommand(command);
command = dynamic_cast<Command*>(new SetOption(ID, SetCommandType::ECM_USING, ECMEnum));
command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::ECM_USING, ECMEnum));
scheduler->appendCommand(command);
}
}
@ -474,7 +495,7 @@ void Unit::setTACAN(Options::TACAN newTACAN) {
<< "frequency = " << TACANChannelToFrequency(TACAN.channel, TACAN.XY) << ","
<< "}"
<< "}";
Command* command = dynamic_cast<Command*>(new SetCommand(ID, commandSS.str()));
Command* command = dynamic_cast<Command*>(new SetCommand(groupName, commandSS.str()));
scheduler->appendCommand(command);
}
else {
@ -484,7 +505,7 @@ void Unit::setTACAN(Options::TACAN newTACAN) {
<< "params = {"
<< "}"
<< "}";
Command* command = dynamic_cast<Command*>(new SetCommand(ID, commandSS.str()));
Command* command = dynamic_cast<Command*>(new SetCommand(groupName, commandSS.str()));
scheduler->appendCommand(command);
}
}
@ -512,7 +533,7 @@ void Unit::setRadio(Options::Radio newRadio) {
<< "frequency = " << radio.frequency << ","
<< "}"
<< "}";
command = dynamic_cast<Command*>(new SetCommand(ID, commandSS.str()));
command = dynamic_cast<Command*>(new SetCommand(groupName, commandSS.str()));
scheduler->appendCommand(command);
// Clear the stringstream
@ -525,7 +546,7 @@ void Unit::setRadio(Options::Radio newRadio) {
<< "number = " << radio.callsignNumber << ","
<< "}"
<< "}";
command = dynamic_cast<Command*>(new SetCommand(ID, commandSS.str()));
command = dynamic_cast<Command*>(new SetCommand(groupName, commandSS.str()));
scheduler->appendCommand(command);
}
}
@ -564,15 +585,15 @@ void Unit::setGeneralSettings(Options::GeneralSettings newGeneralSettings) {
generalSettings = newGeneralSettings;
Command* command;
command = dynamic_cast<Command*>(new SetOption(ID, SetCommandType::PROHIBIT_AA, generalSettings.prohibitAA));
command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::PROHIBIT_AA, generalSettings.prohibitAA));
scheduler->appendCommand(command);
command = dynamic_cast<Command*>(new SetOption(ID, SetCommandType::PROHIBIT_AG, generalSettings.prohibitAG));
command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::PROHIBIT_AG, generalSettings.prohibitAG));
scheduler->appendCommand(command);
command = dynamic_cast<Command*>(new SetOption(ID, SetCommandType::PROHIBIT_JETT, generalSettings.prohibitJettison));
command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::PROHIBIT_JETT, generalSettings.prohibitJettison));
scheduler->appendCommand(command);
command = dynamic_cast<Command*>(new SetOption(ID, SetCommandType::PROHIBIT_AB, generalSettings.prohibitAfterburner));
command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::PROHIBIT_AB, generalSettings.prohibitAfterburner));
scheduler->appendCommand(command);
command = dynamic_cast<Command*>(new SetOption(ID, SetCommandType::ENGAGE_AIR_WEAPONS, !generalSettings.prohibitAirWpn));
command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::ENGAGE_AIR_WEAPONS, !generalSettings.prohibitAirWpn));
scheduler->appendCommand(command);
}
}
@ -605,7 +626,7 @@ void Unit::goToDestination(wstring enrouteTask)
{
if (activeDestination != NULL)
{
Command* command = dynamic_cast<Command*>(new Move(ID, activeDestination, getTargetSpeed(), getTargetSpeedType(), getTargetAltitude(), getTargetAltitudeType(), enrouteTask));
Command* command = dynamic_cast<Command*>(new Move(groupName, activeDestination, getTargetSpeed(), getTargetSpeedType(), getTargetAltitude(), getTargetAltitudeType(), enrouteTask, getCategory()));
scheduler->appendCommand(command);
setHasTask(true);
}
@ -615,16 +636,20 @@ bool Unit::isDestinationReached(double threshold)
{
if (activeDestination != NULL)
{
double dist = 0;
Geodesic::WGS84().Inverse(latitude, longitude, activeDestination.lat, activeDestination.lng, dist);
if (dist < threshold)
/* Check if any unit in the group has reached the point */
for (auto const& p: unitsManager->getGroupMembers(groupName))
{
log(unitName + L" destination reached");
return true;
}
else {
return false;
}
double dist = 0;
Geodesic::WGS84().Inverse(p->getLatitude(), p->getLongitude(), activeDestination.lat, activeDestination.lng, dist);
if (dist < threshold)
{
log(unitName + L" destination reached");
return true;
}
else {
return false;
}
}
}
else
return true;
@ -668,4 +693,23 @@ void Unit::setTargetLocation(Coords newTargetLocation) {
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())
return false;
else {
if (taskCheckCounter > 0)
taskCheckCounter--;
return taskCheckCounter == 0;
}
}
void Unit::resetTaskFailedCounter() {
taskCheckCounter = TASK_CHECK_INIT_VALUE;
}
void Unit::setHasTask(bool newHasTask) {
hasTask = newHasTask;
addMeasure(L"hasTask", json::value(newHasTask));
}

View File

@ -32,6 +32,67 @@ Unit* UnitsManager::getUnit(int ID)
}
}
bool UnitsManager::isUnitInGroup(Unit* unit)
{
if (unit != nullptr) {
wstring groupName = unit->getGroupName();
for (auto const& p : units)
{
if (p.second->getGroupName().compare(groupName) == 0 && p.second != unit)
return true;
}
}
return false;
}
bool UnitsManager::isUnitGroupLeader(Unit* unit)
{
if (unit != nullptr)
return unit == getGroupLeader(unit);
else
return false;
}
// The group leader is the unit with the lowest ID that is part of the group. This is different from DCS's concept of leader, which will change if the leader is destroyed
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());
/* Find the first unit that has the same groupName */
for (auto const& tempID : keys)
{
Unit* tempUnit = getUnit(tempID);
if (tempUnit != nullptr && tempUnit->getGroupName().compare(groupName) == 0)
return tempUnit;
}
}
return nullptr;
}
vector<Unit*> UnitsManager::getGroupMembers(wstring groupName)
{
vector<Unit*> members;
for (auto const& p : units)
{
if (p.second->getGroupName().compare(groupName) == 0)
members.push_back(p.second);
}
return members;
}
Unit* UnitsManager::getGroupLeader(int ID)
{
Unit* unit = getUnit(ID);
return getGroupLeader(unit);
}
void UnitsManager::updateExportData(lua_State* L)
{
map<int, json::value> unitJSONs = getAllUnits(L);