Fixed conflicts, ordered maps.

This commit is contained in:
PeekabooSteam 2023-02-10 14:07:13 +00:00
commit 177428aa84
77 changed files with 5700 additions and 940 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ server
node_modules
/client/TODO.txt
/client/public/javascripts/bundle.js
!client/bin

View File

@ -1,11 +1,9 @@
Change cursor when in moving mode
Show airfields and enable airfield spawn
RTB
tanker
scenario dropdown
explosion
wrong name for ground units
ground units don't move
improve map zIndex
fuel is wrong (either 0 or 1, its is casting it to int somewhere)
weapons should not be selected
weapons should not be selectable
human symbol if user

265
client/package-lock.json generated
View File

@ -1,11 +1,11 @@
{
"name": "DCS Olympus",
"name": "DCSOlympus",
"version": "0.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "DCS Olympus",
"name": "DCSOlympus",
"version": "0.0.0",
"dependencies": {
"@types/geojson": "^7946.0.10",
@ -23,6 +23,7 @@
"@types/gtag.js": "^0.0.12",
"browserify": "^17.0.0",
"concurrently": "^7.6.0",
"nodemon": "^2.0.20",
"tsify": "^5.0.4",
"typescript": "^4.9.4",
"watchify": "^4.0.0"
@ -47,6 +48,12 @@
"@types/geojson": "*"
}
},
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"dev": true
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@ -1515,6 +1522,12 @@
}
]
},
"node_modules/ignore-by-default": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
"dev": true
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@ -1979,6 +1992,94 @@
"node": ">= 0.6"
}
},
"node_modules/nodemon": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz",
"integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==",
"dev": true,
"dependencies": {
"chokidar": "^3.5.2",
"debug": "^3.2.7",
"ignore-by-default": "^1.0.1",
"minimatch": "^3.1.2",
"pstree.remy": "^1.1.8",
"semver": "^5.7.1",
"simple-update-notifier": "^1.0.7",
"supports-color": "^5.5.0",
"touch": "^3.1.0",
"undefsafe": "^2.0.5"
},
"bin": {
"nodemon": "bin/nodemon.js"
},
"engines": {
"node": ">=8.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/nodemon"
}
},
"node_modules/nodemon/node_modules/debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dev": true,
"dependencies": {
"ms": "^2.1.1"
}
},
"node_modules/nodemon/node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/nodemon/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
},
"node_modules/nodemon/node_modules/semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true,
"bin": {
"semver": "bin/semver"
}
},
"node_modules/nodemon/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/nopt": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
"integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
"dev": true,
"dependencies": {
"abbrev": "1"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": "*"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@ -2186,6 +2287,12 @@
"node": ">= 0.10"
}
},
"node_modules/pstree.remy": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
"integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
"dev": true
},
"node_modules/public-encrypt": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
@ -2493,6 +2600,27 @@
}
]
},
"node_modules/simple-update-notifier": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz",
"integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==",
"dev": true,
"dependencies": {
"semver": "~7.0.0"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/simple-update-notifier/node_modules/semver": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
"integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
"dev": true,
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@ -2774,6 +2902,18 @@
"node": ">=8.0"
}
},
"node_modules/touch": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
"integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
"dev": true,
"dependencies": {
"nopt": "~1.0.10"
},
"bin": {
"nodetouch": "bin/nodetouch.js"
}
},
"node_modules/tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
@ -2884,6 +3024,12 @@
"undeclared-identifiers": "bin.js"
}
},
"node_modules/undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
"dev": true
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@ -3102,6 +3248,12 @@
"@types/geojson": "*"
}
},
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"dev": true
},
"accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@ -4294,6 +4446,12 @@
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"dev": true
},
"ignore-by-default": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
"dev": true
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@ -4661,6 +4819,71 @@
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
},
"nodemon": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.20.tgz",
"integrity": "sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==",
"dev": true,
"requires": {
"chokidar": "^3.5.2",
"debug": "^3.2.7",
"ignore-by-default": "^1.0.1",
"minimatch": "^3.1.2",
"pstree.remy": "^1.1.8",
"semver": "^5.7.1",
"simple-update-notifier": "^1.0.7",
"supports-color": "^5.5.0",
"touch": "^3.1.0",
"undefsafe": "^2.0.5"
},
"dependencies": {
"debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true
},
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"nopt": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
"integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
"dev": true,
"requires": {
"abbrev": "1"
}
},
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@ -4829,6 +5052,12 @@
"ipaddr.js": "1.9.1"
}
},
"pstree.remy": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
"integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
"dev": true
},
"public-encrypt": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
@ -5083,6 +5312,23 @@
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
"dev": true
},
"simple-update-notifier": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz",
"integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==",
"dev": true,
"requires": {
"semver": "~7.0.0"
},
"dependencies": {
"semver": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
"integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
"dev": true
}
}
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@ -5314,6 +5560,15 @@
"is-number": "^7.0.0"
}
},
"touch": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
"integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
"dev": true,
"requires": {
"nopt": "~1.0.10"
}
},
"tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
@ -5398,6 +5653,12 @@
"xtend": "^4.0.1"
}
},
"undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
"dev": true
},
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",

View File

@ -1,5 +1,5 @@
{
"name": "DCS Olympus",
"name": "DCSOlympus",
"node-main": "./bin/www",
"main": "http://localhost:3000",
"version": "0.0.0",
@ -25,6 +25,7 @@
"@types/gtag.js": "^0.0.12",
"browserify": "^17.0.0",
"concurrently": "^7.6.0",
"nodemon": "^2.0.20",
"tsify": "^5.0.4",
"typescript": "^4.9.4",
"watchify": "^4.0.0"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm144 276c0 6.6-5.4 12-12 12h-92v92c0 6.6-5.4 12-12 12h-56c-6.6 0-12-5.4-12-12v-92h-92c-6.6 0-12-5.4-12-12v-56c0-6.6 5.4-12 12-12h92v-92c0-6.6 5.4-12 12-12h56c6.6 0 12 5.4 12 12v92h92c6.6 0 12 5.4 12 12v56z"/></svg>

After

Width:  |  Height:  |  Size: 491 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M497.941 273.941c18.745-18.745 18.745-49.137 0-67.882l-160-160c-18.745-18.745-49.136-18.746-67.883 0l-256 256c-18.745 18.745-18.745 49.137 0 67.882l96 96A48.004 48.004 0 0 0 144 480h356c6.627 0 12-5.373 12-12v-40c0-6.627-5.373-12-12-12H355.883l142.058-142.059zm-302.627-62.627l137.373 137.373L265.373 416H150.628l-80-80 124.686-124.686z"/></svg>

After

Width:  |  Height:  |  Size: 553 B

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="36"
height="36"
viewBox="0 0 9.5249998 9.5250003"
version="1.1"
id="svg5"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
sodipodi:docname="formation-end.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"
units="px"
width="36px"
inkscape:zoom="16.000001"
inkscape:cx="14.406249"
inkscape:cy="20.906249"
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
style="fill:none;stroke:#000000;stroke-width:0.529167;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.05833, 0.529167;stroke-dashoffset:1.48167;stroke-opacity:1"
d="M 7.509551,4.8476603 H 4.2913461"
id="path1369"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.529167;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.05833, 0.529167;stroke-dashoffset:1.48167;stroke-opacity:1"
d="M 4.2913461,0.24555387 V 4.8476603"
id="path2640"
sodipodi:nodetypes="cc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="36"
height="36"
viewBox="0 0 9.5249998 9.5250003"
version="1.1"
id="svg5"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
sodipodi:docname="formation-middle.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"
units="px"
width="36px"
inkscape:zoom="16.000001"
inkscape:cx="14.406249"
inkscape:cy="20.906249"
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
style="fill:none;stroke:#000000;stroke-width:0.529167;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.05833, 0.529167;stroke-dashoffset:1.48167;stroke-opacity:1"
d="M 7.509551,4.8476603 H 4.2913461"
id="path1369"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:0.529167;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.05833, 0.529167;stroke-dashoffset:1.48167;stroke-opacity:1"
d="m 4.2913461,0.24555387 0,9.06695013"
id="path2640"
sodipodi:nodetypes="cc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M635.7 167.2L556.1 31.7c-8.8-15-28.3-20.1-43.5-11.5l-69 39.1L503.3 161c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L416 75l-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L333.2 122 278 153.3 337.8 255c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-59.7-101.7-55.2 31.3 27.9 47.4c2.2 3.8.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9l-27.9-47.5-55.2 31.3 59.7 101.7c2.2 3.7.9 8.5-2.9 10.7l-13.8 7.8c-3.8 2.2-8.7.9-10.9-2.9L84.9 262.9l-69 39.1C.7 310.7-4.6 329.8 4.2 344.8l79.6 135.6c8.8 15 28.3 20.1 43.5 11.5L624.1 210c15.2-8.6 20.4-27.8 11.6-42.8z"/></svg>

After

Width:  |  Height:  |  Size: 821 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 288 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M112 316.94v156.69l22.02 33.02c4.75 7.12 15.22 7.12 19.97 0L176 473.63V316.94c-10.39 1.92-21.06 3.06-32 3.06s-21.61-1.14-32-3.06zM144 0C64.47 0 0 64.47 0 144s64.47 144 144 144 144-64.47 144-144S223.53 0 144 0zm0 76c-37.5 0-68 30.5-68 68 0 6.62-5.38 12-12 12s-12-5.38-12-12c0-50.73 41.28-92 92-92 6.62 0 12 5.38 12 12s-5.38 12-12 12z"/></svg>

After

Width:  |  Height:  |  Size: 549 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

View File

@ -56,16 +56,15 @@ body {
}
#unit-control-buttons {
position: absolute;
position: fixed;
top: 10px;
height: fit-content;
width: fit-content;
right: 10px;
right: 270px;
z-index: 1000;
}
#unit-control-panel {
display: none;
position: absolute;
top: 10px;
height: fit-content;
@ -77,12 +76,21 @@ body {
#connection-status-panel {
position: absolute;
height: 30px;
width: 140px;
width: 160px;
bottom: 10px;
right: 10px;
z-index: 1000;
}
#mouse-info-panel {
position: absolute;
height: fit-content;
width: 160px;
bottom: 50px;
right: 10px;
z-index: 1000;
}
@media only screen and (max-width: 1000px) {
#unit-control-buttons {
top: 50px;

View File

@ -0,0 +1,31 @@
#mouse-info-panel {
display: flex;
flex-direction: column;
padding: 10px;
row-gap: 5px;
}
#mouse-info-panel .rectangular-container{
width: 100%;
font-weight: 600;
font-size: 12px;
display: flex;
align-items: center;
justify-content: space-between;
padding-left: 10px;
padding-right: 10px;
border-radius: 10px;
background-color: #FFF3;
}
#mouse-info-panel img {
height: 24px;
}
#measure-position-container{
display: none;
}
#unit-position-container{
display: none;
}

View File

@ -1,20 +1,40 @@
.olympus-selection-scroll-container {
position: fixed;
background-color: var(--background-color-dark);
position: absolute;
font-size: 12px;
transition: bottom 0.2s;
border-radius: 15px;
box-shadow: 0px 2px 5px #000A;
width: 180px;
border-radius: 5px;
width: 220px;
height: fit-content;
z-index: 1000;
z-index: 2000;
max-height: 400px;
padding: 10px;
padding: 8px;
display: flex;
flex-direction: column;
row-gap: 5px;
align-items: center;
}
#olympus-selection-scroll-top-bar {
color: white;
font-size: 14px;
opacity: 1;
border-radius: 5px;
padding: 5px;
background-color: #333D;
width: 100%;
text-align: center;
display: flex;
align-items: center;
justify-content: space-between;
height: 40px;
padding-left: 15px;
padding-right: 15px;
}
.olympus-selection-scroll {
overflow-x: hidden;
overflow-y: auto;
max-height: calc(400px - 60px);
height: 100%;
width: 100%;
}
.olympus-selection-scroll::-webkit-scrollbar {
@ -34,48 +54,51 @@
}
.olympus-selection-scroll-element {
margin: 2px;
border-bottom: 1px solid #FFF5;
color: white;
cursor: pointer;
font-size: 14px;
font-size: 13px;
opacity: 1;
border-radius: 5px;
margin: 5px;
padding: 5px;
padding-top: 10px;
padding-bottom: 10px;
padding-left: 15px;
background-color: var(--background-color-dark);
font-weight: 600;
}
.olympus-selection-scroll-element:hover {
background-color: var(--highlight-color);
.olympus-selection-scroll:last-child {
border-radius: 5px;
border-bottom: 0px transparent !important;
}
.olympus-selection-scroll-container label {
width: 0;
height: 0;
display: inline-block;
width: 40px;
height: 24px;
}
.olympus-selection-scroll-container input {
display: inline-block;
width: 0;
height: 0;
margin: 0px;
}
.olympus-selection-scroll-switch {
position: relative;
display: inline-block;
left: calc(50% - 10px);
width: 60px;
height: 34px;
width: 40px;
height: 24px;
background-color: var(--active-coalition-color);
border-radius: 999px;
cursor: pointer;
margin-left: -30px;
}
.olympus-selection-scroll-switch:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
height: 16px;
width: 16px;
left: 4px;
bottom: 4px;
background-color: white;
@ -85,7 +108,12 @@
}
input:checked+.olympus-selection-scroll-switch:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
-webkit-transform: translateX(16px);
-ms-transform: translateX(16px);
transform: translateX(16px);
}
.olympus-selection-scroll-title {
font-size: 11px;
font-weight: 600;
}

View File

@ -0,0 +1,47 @@
.slider-container {
width: 100%;
}
.slider {
width: 100%;
-webkit-appearance: none;
appearance: none;
height: 2px;
background: #d3d3d3;
outline: none;
opacity: 0.7;
-webkit-transition: .2s;
transition: opacity .2s;
margin-top: 10px;
margin-bottom: 10px;
}
.slider:hover {
opacity: 1;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
background: gray;
cursor: pointer;
border-radius: 999px;
}
.active .slider::-webkit-slider-thumb {
background: #5ca7ff;
}
.slider::-moz-range-thumb {
width: 20px;
height: 20px;
background: gray;
cursor: pointer;
border-radius: 999px;
}
.active .slider::-moz-range-thumb {
background: #5ca7ff;
}

View File

@ -1,4 +1,5 @@
@import url("button.css");
@import url("slider.css");
@import url("dropdown.css");
@import url("selectionwheel.css");
@ -12,6 +13,7 @@
@import url("unitcontrolpanel.css");
@import url("visibilitycontrolpanel.css");
@import url("unitinfopanel.css");
@import url("mouseinfopanel.css");
@import url("layout.css");
@ -19,14 +21,14 @@
/* Variables definitions */
:root {
--background-color-dark: #202831;
--background-color-light: #aaaaaa;
--background-color-light: #AAA;
--title-color: #d3e9ff;
--text-color: white;
--blue-coalition-color: #2196F3;
--blue-coalition-color: #247be2;
--red-coalition-color: #f32121;
--neutral-coalition-color: #AAAAAA;
--neutral-coalition-color: #202831;
--active-coalition-color: var(--blue-coalition-color);
--highlight-color: #FFFFFFAA;
--highlight-color: #FFF5;
}
* {
@ -69,15 +71,90 @@ html {
}
.rounded-container {
position: relative;
padding: 0.5em;
background-color: #247be2;
border-radius: 10px;
width: fit-content;
height: fit-content;
text-align: center;
color: white;
font-size: 12px;
border-radius: 9999px;
background-color: gray;
}
.rounded-container.blue {
background-color: var(--blue-coalition-color);
border: 1px solid var(--blue-coalition-color);
}
.rounded-container.red {
background-color: var(--red-coalition-color);
border: 1px solid var(--red-coalition-color);
}
.rounded-container.neutral {
background-color: var(--neutral-coalition-color);
}
.rounded-container-small {
padding: 0.2em;
width: fit-content;
height: fit-content;
text-align: center;
color: black;
font-weight: 600;
background-color: #FFFA;
font-size: 11px;
border-radius: 9999px;
padding-left: 5px;
padding-right: 5px;
}
.rectangular-button {
position: relative;
padding: 0.5em;
width: fit-content;
height: fit-content;
text-align: center;
color: var(--highlight-color);
font-size: 12px;
border-radius: 5px;
background-color: transparent;
border: 1px solid var(--highlight-color);
cursor: pointer;
font-weight: 600;
display: flex;
align-items: center;
column-gap: 5px;
}
.rectangular-button.blue {
border: 1px solid var(--blue-coalition-color);
color: var(--blue-coalition-color);
}
.rectangular-button.red {
border: 1px solid var(--red-coalition-color);
color: var(--red-coalition-color);
}
.rectangular-button.white {
border: 1px solid white;
color: white;
}
.rectangular-button.white>img {
filter: invert(100%);
}
.rectangular-button>img {
display: inline-block;
height: 18px;
width: 18px;
}
.rectangular-button.red {
border: 1px solid var(--red-coalition-color);
}
.vl {
@ -85,3 +162,26 @@ html {
width: 1px !important;
display: inline-block;
}
.hl {
border-top: 1px solid #555;
height: 1px !important;
display: inline-block;
}
.measure-box {
position: absolute;
padding-left: 0.5em;
padding-right: 0.5em;
padding-top: 0.2em;
padding-bottom: 0.2em;
background-color: #151b20;
border-radius: 5px;
width: fit-content;
height: fit-content;
text-align: center;
color: white;
font-size: 12px;
z-index: 2000;
font-weight: 600;
}

View File

@ -13,24 +13,86 @@
#unit-control-panel {
display: flex;
flex-direction: column;
flex-wrap: wrap;
justify-content: space-between;
align-content: flex-start;
row-gap: 5px;
row-gap: 10px;
padding-left: 30px;
padding-right: 30px;
padding-top: 20px;
padding-bottom: 20px;
}
/* Common */
#unit-info-panel>div {
height: 100%;
#selected-units-container {
display: flex;
flex-direction: column;
row-gap: 5px;
width: 100%;
height: 100%;
}
#unit-control-panel .rounded-container {
width: 100%;
#formation-buttons-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
row-gap: 5px;
column-gap: 5px;
width: 100%;
height: 100%;
}
#roe-buttons-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
row-gap: 5px;
column-gap: 5px;
width: 100%;
height: 100%;
}
#reaction-to-threat-buttons-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
row-gap: 5px;
column-gap: 5px;
width: 100%;
height: 100%;
}
#selected-units-container .rounded-container {
width: calc(100% - 25px);
cursor: pointer;
margin-left: 25px;
}
#selected-units-container .rounded-container.not-selected {
background-color: transparent;
}
#selected-units-container .rounded-container .rounded-container-small {
display: inline-block;
position: absolute;
left: 5px;
top: 5px;
}
#selected-units-container img {
height: calc(100% + 6px);
display: inline-block;
position: absolute;
left: -32px;
top: -3px;
}
#selected-units-container img.blue {
filter: invert(81%) sepia(6%) saturate(1685%) hue-rotate(181deg) brightness(103%) contrast(92%);
}
#selected-units-container img.red {
filter: invert(93%) sepia(97%) saturate(1174%) hue-rotate(291deg) brightness(105%) contrast(97%);
}
#unit-control-panel #title-label {
@ -38,4 +100,31 @@
font-size: 14px;
width: 100%;
font-weight: 600;
}
}
#unit-control-panel #section-label {
color: white;
font-size: 13px;
width: 100%;
}
.flight-control-slider {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.flight-control-title {
font-size: 13px;
color: white;
}
.flight-control-value {
font-size: 14px;
font-weight: 600;
color: gray;
}
.active .flight-control-value {
color: #5ca7ff;
}

View File

@ -1,5 +1,5 @@
import { LatLng } from "leaflet";
import { setActiveCoalition } from "..";
import { getActiveCoalition, setActiveCoalition } from "..";
export class SelectionScroll {
#container: HTMLElement | null;
@ -15,14 +15,17 @@ export class SelectionScroll {
}
}
show(x: number, y: number, options: any, callback: CallableFunction, showCoalition: boolean) {
show(x: number, y: number, title: string, options: any, callback: CallableFunction, showCoalition: boolean) {
/* Hide to remove buttons, if present */
this.hide();
if (this.#container != null && options.length >= 1) {
var titleDiv = this.#container.querySelector("#olympus-selection-scroll-top-bar")?.querySelector(".olympus-selection-scroll-title");
if (titleDiv)
titleDiv.innerHTML = title;
this.#container.style.display = this.#display;
this.#container.style.left = x - 110 + "px";
this.#container.style.top = y - 110 + "px";
this.#container.style.left = x - this.#container.offsetWidth / 2 + "px";
this.#container.style.top = y - 20 + "px";
var scroll = this.#container.querySelector(".olympus-selection-scroll");
if (scroll != null)
{
@ -40,6 +43,20 @@ export class SelectionScroll {
scroll.appendChild(node);
}
}
/* Hide the coalition switch if required */
var switchContainer = <HTMLElement>this.#container.querySelector("#olympus-selection-scroll-top-bar")?.querySelector("#coalition-switch-container");
if (showCoalition == false) {
switchContainer.style.display = "none";
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--neutral-coalition-color"));
}
else {
switchContainer.style.display = "block";
if (getActiveCoalition() == "blue")
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--blue-coalition-color"));
else
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--red-coalition-color"));
}
}
}

View File

@ -0,0 +1,83 @@
export class Slider {
#container: HTMLElement | null;
#callback: CallableFunction;
#slider: HTMLInputElement | null = null;
#value: HTMLElement | null = null;
#minValue: number;
#maxValue: number;
#minValueDiv: HTMLElement | null = null;
#maxValueDiv: HTMLElement | null = null;
#unit: string;
#display: string = "";
constructor(ID: string, minValue: number, maxValue: number, unit: string, callback: CallableFunction) {
this.#container = document.getElementById(ID);
this.#callback = callback;
this.#minValue = minValue;
this.#maxValue = maxValue;
this.#unit = unit;
if (this.#container != null) {
this.#display = this.#container.style.display;
this.#slider = <HTMLInputElement>this.#container.querySelector("input");
if (this.#slider != null)
{
this.#slider.addEventListener("input", (e: any) => this.#onInput());
this.#slider.addEventListener("mouseup", (e: any) => this.#onFinalize());
}
this.#value = <HTMLElement>this.#container.querySelector("#value");
}
}
#onValue()
{
if (this.#value != null && this.#slider != null)
this.#value.innerHTML = this.#minValue + Math.round(parseFloat(this.#slider.value) / 100 * (this.#maxValue - this.#minValue)) + this.#unit
this.setActive(true);
}
#onInput()
{
this.#onValue();
}
#onFinalize()
{
if (this.#slider != null)
this.#callback(this.#minValue + parseFloat(this.#slider.value) / 100 * (this.#maxValue - this.#minValue));
}
show()
{
if (this.#container != null)
this.#container.style.display = this.#display;
}
hide()
{
if (this.#container != null)
this.#container.style.display = 'none';
}
setActive(newActive: boolean)
{
if (this.#container)
{
this.#container.classList.toggle("active", newActive);
if (!newActive && this.#value != null)
this.#value.innerHTML = "Mixed values"
}
}
setMinMax(newMinValue: number, newMaxValue: number)
{
this.#minValue = newMinValue;
this.#maxValue = newMaxValue;
}
setValue(newValue: number)
{
if (this.#slider != null)
this.#slider.value = String((newValue - this.#minValue) / (this.#maxValue - this.#minValue) * 100);
this.#onValue()
}
}

View File

@ -89,7 +89,7 @@ export function attackUnit(ID: number, targetID: number) {
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
console.log("Unit " + getUnitsManager().getUnitByID(ID).unitName + " attack " + getUnitsManager().getUnitByID(targetID).unitName);
//console.log("Unit " + getUnitsManager().getUnitByID(ID).unitName + " attack " + getUnitsManager().getUnitByID(targetID).unitName);
}
};
@ -99,29 +99,45 @@ export function attackUnit(ID: number, targetID: number) {
xhr.send(JSON.stringify(data));
}
export function cloneUnit(ID: number) {
export function cloneUnit(ID: number, latlng: L.LatLng) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
console.log("Unit " + getUnitsManager().getUnitByID(ID).unitName + " cloned");
//console.log("Unit " + getUnitsManager().getUnitByID(ID).unitName + " cloned");
}
};
var command = { "ID": ID };
var command = { "ID": ID, "location": latlng };
var data = { "cloneUnit": command }
xhr.send(JSON.stringify(data));
}
export function landAt(ID: number, latlng: L.LatLng) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
//console.log("Unit " + getUnitsManager().getUnitByID(ID).unitName + " cloned");
}
};
var command = { "ID": ID, "location": latlng };
var data = { "landAt": command }
xhr.send(JSON.stringify(data));
}
export function changeSpeed(ID: number, speedChange: string) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
console.log(getUnitsManager().getUnitByID(ID).unitName + " speed change request: " + speedChange);
//console.log(getUnitsManager().getUnitByID(ID).unitName + " speed change request: " + speedChange);
}
};
@ -131,18 +147,98 @@ export function changeSpeed(ID: number, speedChange: string) {
xhr.send(JSON.stringify(data));
}
export function setSpeed(ID: number, speed: number) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
//console.log(getUnitsManager().getUnitByID(ID).unitName + " speed change request: " + speedChange);
}
};
var command = {"ID": ID, "speed": speed}
var data = {"setSpeed": command}
xhr.send(JSON.stringify(data));
}
export function changeAltitude(ID: number, altitudeChange: string) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
console.log(getUnitsManager().getUnitByID(ID).unitName + " altitude change request: " + altitudeChange);
//console.log(getUnitsManager().getUnitByID(ID).unitName + " altitude change request: " + altitudeChange);
}
};
var command = {"ID": ID, "change": altitudeChange}
var data = {"changeAltitude": command}
xhr.send(JSON.stringify(data));
}
export function setAltitude(ID: number, altitude: number) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
//console.log(getUnitsManager().getUnitByID(ID).unitName + " speed change request: " + speedChange);
}
};
var command = {"ID": ID, "altitude": altitude}
var data = {"setAltitude": command}
xhr.send(JSON.stringify(data));
}
export function createFormation(ID: number, isLeader: boolean, wingmenIDs: number[]) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
//console.log(getUnitsManager().getUnitByID(ID).unitName + " created formation with: " + wingmenIDs);
}
};
var command = {"ID": ID, "wingmenIDs": wingmenIDs, "isLeader": isLeader}
var data = {"setLeader": command}
xhr.send(JSON.stringify(data));
}
export function setROE(ID: number, ROE: string) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
//console.log(getUnitsManager().getUnitByID(ID).unitName + " speed change request: " + speedChange);
}
};
var command = {"ID": ID, "ROE": ROE}
var data = {"setROE": command}
xhr.send(JSON.stringify(data));
}
export function setReactionToThreat(ID: number, reactionToThreat: string) {
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
//console.log(getUnitsManager().getUnitByID(ID).unitName + " speed change request: " + speedChange);
}
};
var command = {"ID": ID, "reactionToThreat": reactionToThreat}
var data = {"setReactionToThreat": command}
xhr.send(JSON.stringify(data));
}

View File

@ -8,19 +8,25 @@ import { Dropdown } from "./controls/dropdown";
import { ConnectionStatusPanel } from "./panels/connectionstatuspanel";
import { Button } from "./controls/button";
import { MissionData } from "./missiondata/missiondata";
import { UnitControlPanel } from "./panels/unitcontrolpanel";
import { MouseInfoPanel } from "./panels/mouseInfoPanel";
import { Slider } from "./controls/slider";
/* TODO: should this be a class? */
var map: Map;
var selectionWheel: SelectionWheel;
var selectionScroll: SelectionScroll;
var unitsManager: UnitsManager;
var missionData: MissionData;
var unitInfoPanel: UnitInfoPanel;
var activeCoalition: string;
var connectionStatusPanel: ConnectionStatusPanel;
var unitControlPanel: UnitControlPanel;
var mouseInfoPanel: MouseInfoPanel;
var scenarioDropdown: Dropdown;
var mapSourceDropdown: Dropdown;
var connected: boolean;
var connectionStatusPanel: ConnectionStatusPanel;
var missionData: MissionData;
var slowButton: Button;
var fastButton: Button;
@ -31,6 +37,12 @@ var aiVisibilityButton: Button;
var weaponVisibilityButton: Button;
var deadVisibilityButton: Button;
var altitudeSlider: Slider;
var airspeedSlider: Slider;
var connected: boolean;
var activeCoalition: string;
function setup() {
/* Initialize */
map = new Map('map-container');
@ -38,9 +50,11 @@ function setup() {
selectionScroll = new SelectionScroll("selection-scroll");
unitsManager = new UnitsManager();
unitInfoPanel = new UnitInfoPanel("unit-info-panel");
unitControlPanel = new UnitControlPanel("unit-control-panel");
scenarioDropdown = new Dropdown("scenario-dropdown", ["Caucasus", "Marianas", "Nevada", "South Atlantic", "Syria", "The Channel"], () => { });
mapSourceDropdown = new Dropdown("map-source-dropdown", map.getLayers(), (option: string) => map.setLayer(option));
connectionStatusPanel = new ConnectionStatusPanel("connection-status-panel");
mouseInfoPanel = new MouseInfoPanel("mouse-info-panel");
missionData = new MissionData();
/* Unit control buttons */
@ -49,6 +63,10 @@ function setup() {
climbButton = new Button("climb-button", ["images/buttons/climb.svg"], () => { getUnitsManager().selectedUnitsChangeAltitude("climb"); });
descendButton = new Button("descend-button", ["images/buttons/descend.svg"], () => { getUnitsManager().selectedUnitsChangeAltitude("descend"); });
/* Unit control sliders */
altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => getUnitsManager().selectedUnitsSetAltitude(value * 0.3048));
airspeedSlider = new Slider("airspeed-slider", 0, 100, "kts", (value: number) => getUnitsManager().selectedUnitsSetSpeed(value / 1.94384));
/* Visibility buttons */
userVisibilityButton = new Button("user-visibility-button", ["images/buttons/user-full.svg", "images/buttons/user-partial.svg", "images/buttons/user-none.svg", "images/buttons/user-hidden.svg"], () => { });
aiVisibilityButton = new Button("ai-visibility-button", ["images/buttons/ai-full.svg", "images/buttons/ai-partial.svg", "images/buttons/ai-none.svg", "images/buttons/ai-hidden.svg"], () => { });
@ -93,6 +111,10 @@ export function getMap() {
return map;
}
export function getMissionData() {
return missionData;
}
export function getSelectionWheel() {
return selectionWheel;
}
@ -109,6 +131,14 @@ export function getUnitInfoPanel() {
return unitInfoPanel;
}
export function getUnitControlPanel() {
return unitControlPanel;
}
export function getMouseInfoPanel() {
return mouseInfoPanel;
}
export function setActiveCoalition(newActiveCoalition: string) {
activeCoalition = newActiveCoalition;
}
@ -173,4 +203,12 @@ export function getVisibilitySettings() {
return visibility;
}
export function getVisibilityButtons() {
return {user: userVisibilityButton, ai: aiVisibilityButton, weapon: weaponVisibilityButton, dead: deadVisibilityButton}
}
export function getUnitControlSliders() {
return {altitude: altitudeSlider, airspeed: airspeedSlider}
}
window.onload = setup;

View File

@ -45,7 +45,7 @@ export var BoxSelect = Handler.extend({
},
_onMouseDown: function (e: any) {
if (((e.which !== 3) && (e.button !== 2))) { return false; }
if (((e.which !== 1) && (e.button !== 0))) { return false; }
// Clear the deferred resetState if it hasn't executed yet, otherwise it
// will interrupt the interaction and orphan a box element in the container.
@ -107,7 +107,7 @@ export var BoxSelect = Handler.extend({
},
_onMouseUp: function (e: any) {
if ((e.which !== 3) && (e.button !== 2)) { return; }
if ((e.which !== 1) && (e.button !== 0)) { return; }
this._finish();

View File

@ -1,6 +1,7 @@
import * as L from "leaflet"
import { getSelectionWheel, getSelectionScroll, getUnitsManager, getActiveCoalition } from "..";
import { getSelectionWheel, getSelectionScroll, getUnitsManager, getActiveCoalition, getMouseInfoPanel } from "..";
import { spawnAircraft, spawnGroundUnit, spawnSmoke } from "../dcs/dcs";
import { bearing, distance, zeroAppend } from "../other/utils";
import { payloadNames } from "../units/payloadNames";
import { unitTypes } from "../units/unitTypes";
import { BoxSelect } from "./boxselect";
@ -21,8 +22,14 @@ export interface SpawnEvent extends ClickEvent{
export class Map extends L.Map {
#state: string;
#layer?: L.TileLayer;
#preventRightClick: boolean = false;
#rightClickTimer: number = 0;
#preventLeftClick: boolean = false;
#leftClickTimer: number = 0;
#measurePoint: L.LatLng | null;
#measureIcon: L.Icon;
#measureMarker: L.Marker;
#measureLine: L.Polyline = new L.Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1, interactive: false });
#measureLineDiv: HTMLElement;
#lastMousePosition: L.Point = new L.Point(0, 0);
constructor(ID: string) {
/* Init the leaflet map */
@ -34,12 +41,25 @@ export class Map extends L.Map {
/* Init the state machine */
this.#state = "IDLE";
this.#measurePoint = null;
this.#measureIcon = new L.Icon({ iconUrl: 'images/pin.png', iconAnchor: [16, 32]});
this.#measureMarker = new L.Marker([0, 0], {icon: this.#measureIcon, interactive: false});
this.#measureLineDiv = document.createElement("div");
this.#measureLineDiv.classList.add("measure-box");
this.#measureLineDiv.style.display = 'none';
document.body.appendChild(this.#measureLineDiv);
/* Register event handles */
this.on("click", (e: any) => this.#onClick(e));
this.on("dblclick", (e: any) => this.#onDoubleClick(e));
this.on("contextmenu", (e: any) => this.#onContextMenu(e));
this.on('selectionend', (e: any) => this.#onSelectionEnd(e));
this.on('mousedown', (e: any) => this.#onMouseDown(e));
this.on('mouseup', (e: any) => this.#onMouseUp(e));
this.on('mousemove', (e: any) => this.#onMouseMove(e));
this.on('zoom', (e: any) => this.#onZoom(e));
}
setLayer(layerName: string) {
@ -124,63 +144,133 @@ export class Map extends L.Map {
}
/* Selection scroll */
showSelectionScroll(e: ClickEvent | SpawnEvent, options: any, callback: CallableFunction, showCoalition: boolean = false) {
showSelectionScroll(e: ClickEvent | SpawnEvent, title: string, options: any, callback: CallableFunction, showCoalition: boolean = false) {
var x = e.x;
var y = e.y;
getSelectionScroll().show(x, y, options, callback, showCoalition);
getSelectionScroll().show(x, y, title, options, callback, showCoalition);
}
hideSelectionScroll() {
getSelectionScroll().hide();
}
getMousePosition() {
return this.#lastMousePosition;
}
getMouseCoordinates() {
return this.containerPointToLatLng(this.#lastMousePosition);
}
/* Event handlers */
#onClick(e: any) {
this.hideSelectionWheel();
this.hideSelectionScroll();
if (this.#state === "IDLE") {
}
else if (this.#state === "MOVE_UNIT") {
if (!e.originalEvent.ctrlKey) {
getUnitsManager().clearDestinations();
if (!this.#preventLeftClick) {
this.hideSelectionWheel();
this.hideSelectionScroll();
if (this.#state === "IDLE") {
if (e.originalEvent.ctrlKey)
if (!this.#measurePoint)
{
this.#measurePoint = e.latlng;
this.#measureMarker.setLatLng(e.latlng);
this.#measureMarker.addTo(this);
}
else
{
this.#measurePoint = null;
if (this.hasLayer(this.#measureMarker))
this.removeLayer(this.#measureMarker);
}
}
getUnitsManager().addDestination(e.latlng)
}
}
#onDoubleClick(e: any) {
var spawnEvent: SpawnEvent = {x: e.originalEvent.x, y: e.originalEvent.y, latlng: e.latlng, airbaseName: null, coalitionID: null};
if (this.#state == "IDLE") {
var options = [
{ "tooltip": "Spawn air unit", "src": "spawnAir.png", "callback": () => this.#aircraftSpawnMenu(spawnEvent) },
{ "tooltip": "Spawn ground unit", "src": "spawnGround.png", "callback": () => this.#groundUnitSpawnMenu(spawnEvent) },
{ "tooltip": "Smoke", "src": "spawnSmoke.png", "callback": () => this.#smokeSpawnMenu(spawnEvent) },
//{ "tooltip": "Explosion", "src": "spawnExplosion.png", "callback": () => this.#explosionSpawnMenu(e) }
]
this.showSelectionScroll(spawnEvent, options, () => {}, true);
}
}
#onContextMenu(e: any) {
this.#rightClickTimer = setTimeout(() => {
if (!this.#preventRightClick) {
else if (this.#state === "MOVE_UNIT") {
this.setState("IDLE");
getUnitsManager().deselectAllUnits();
this.hideSelectionWheel();
this.hideSelectionScroll();
}
this.#preventRightClick = false;
}, 200);
}
}
#onDoubleClick(e: any) {
}
#onContextMenu(e: any) {
this.hideSelectionWheel();
this.hideSelectionScroll();
if (this.#state === "IDLE") {
var spawnEvent: SpawnEvent = {x: e.originalEvent.x, y: e.originalEvent.y, latlng: e.latlng, airbaseName: null, coalitionID: null};
if (this.#state == "IDLE") {
var options = [
{ "tooltip": "Spawn air unit", "src": "spawnAir.png", "callback": () => this.#aircraftSpawnMenu(spawnEvent) },
{ "tooltip": "Spawn ground unit", "src": "spawnGround.png", "callback": () => this.#groundUnitSpawnMenu(spawnEvent) },
{ "tooltip": "Smoke", "src": "spawnSmoke.png", "callback": () => this.#smokeSpawnMenu(spawnEvent) },
//{ "tooltip": "Explosion", "src": "spawnExplosion.png", "callback": () => this.#explosionSpawnMenu(e) }
]
this.showSelectionScroll(spawnEvent, "Action", options, () => {}, false);
}
}
else if (this.#state === "MOVE_UNIT") {
if (!e.originalEvent.ctrlKey) {
getUnitsManager().selectedUnitsClearDestinations();
}
getUnitsManager().selectedUnitsAddDestination(e.latlng)
}
}
#onSelectionEnd(e: any)
{
clearTimeout(this.#rightClickTimer);
this.#preventRightClick = true;
clearTimeout(this.#leftClickTimer);
this.#preventLeftClick = true;
this.#leftClickTimer = setTimeout(() => {
this.#preventLeftClick = false;
}, 200);
getUnitsManager().selectFromBounds(e.selectionBounds);
}
#onMouseDown(e: any)
{
if ((e.originalEvent.which == 1) && (e.originalEvent.button == 0))
{
this.dragging.disable();
}
}
#onMouseUp(e: any)
{
if ((e.originalEvent.which == 1) && (e.originalEvent.button == 0))
{
this.dragging.enable();
}
}
#onMouseMove(e: any)
{
var selectedUnitPosition = null;
var selectedUnits = getUnitsManager().getSelectedUnits();
if (selectedUnits && selectedUnits.length == 1)
{
selectedUnitPosition = new L.LatLng(selectedUnits[0].latitude, selectedUnits[0].longitude);
}
getMouseInfoPanel().update(<L.LatLng>e.latlng, this.#measurePoint, selectedUnitPosition);
this.#lastMousePosition.x = e.originalEvent.x;
this.#lastMousePosition.y = e.originalEvent.y;
if ( this.#measurePoint)
this.#drawMeasureLine();
else
this.#hideMeasureLine();
}
#onZoom(e: any)
{
if (this.#measurePoint)
this.#drawMeasureLine();
else
this.#hideMeasureLine();
}
/* Spawn from air base */
spawnFromAirbase(e: SpawnEvent)
{
@ -198,7 +288,7 @@ export class Map extends L.Map {
{'coalition': true, 'tooltip': 'Radar', 'src': 'spawnRadar.png', 'callback': () => this.#selectGroundUnit(e, "Radar")},
{'coalition': true, 'tooltip': 'Unarmed', 'src': 'spawnUnarmed.png', 'callback': () => this.#selectGroundUnit(e, "Unarmed")}
]
this.showSelectionScroll(e, options, () => {}, true);
this.showSelectionScroll(e, "Spawn ground unit", options, () => {}, true);
}
#smokeSpawnMenu(e: SpawnEvent) {
@ -211,7 +301,7 @@ export class Map extends L.Map {
{'tooltip': 'Green smoke', 'src': 'spawnSmoke.png', 'callback': () => {this.hideSelectionWheel(); this.hideSelectionScroll(); spawnSmoke('green', e.latlng)}, 'tint': 'green'},
{'tooltip': 'Orange smoke', 'src': 'spawnSmoke.png', 'callback': () => {this.hideSelectionWheel(); this.hideSelectionScroll(); spawnSmoke('orange', e.latlng)}, 'tint': 'orange'},
]
this.showSelectionScroll(e, options, () => {}, true);
this.showSelectionScroll(e, "Spawn smoke", options, () => {}, false);
}
#explosionSpawnMenu(e: SpawnEvent) {
@ -228,7 +318,10 @@ export class Map extends L.Map {
{ 'coalition': true, 'tooltip': 'Drone', 'src': 'spawnDrone.png', 'callback': () => this.#selectAircraft(e, "drone") },
{ 'coalition': true, 'tooltip': 'Transport', 'src': 'spawnTransport.png', 'callback': () => this.#selectAircraft(e, "transport") },
]
this.showSelectionScroll(e, options, () => {}, true);
if (e.airbaseName != null)
this.showSelectionScroll(e, "Spawn at " + e.airbaseName, options, () => {}, true);
else
this.showSelectionScroll(e, "Spawn air unit", options, () => {}, true);
}
/* Show unit selection for air units */
@ -240,11 +333,11 @@ export class Map extends L.Map {
options.sort();
else
options = [];
this.showSelectionScroll(e, options, (unitType: string) => {
this.showSelectionScroll(e, "Select aircraft", options, (unitType: string) => {
this.hideSelectionWheel();
this.hideSelectionScroll();
this.#unitSelectPayload(e, unitType);
});
}, true);
}
/* Show weapon selection for air units */
@ -255,11 +348,11 @@ export class Map extends L.Map {
options = payloadNames[unitType]
if (options != undefined && options.length > 0) {
options.sort();
this.showSelectionScroll({x: e.x, y: e.y, latlng: e.latlng}, options, (payloadName: string) => {
this.showSelectionScroll({x: e.x, y: e.y, latlng: e.latlng}, "Select loadout", options, (payloadName: string) => {
this.hideSelectionWheel();
this.hideSelectionScroll();
spawnAircraft(unitType, e.latlng, getActiveCoalition(), payloadName, e.airbaseName);
});
}, true);
}
else {
spawnAircraft(unitType, e.latlng, getActiveCoalition());
@ -273,10 +366,49 @@ export class Map extends L.Map {
this.hideSelectionScroll();
var options = unitTypes.vehicles[group];
options.sort();
this.showSelectionScroll(e, options, (unitType: string) => {
this.showSelectionScroll(e, "Select ground unit", options, (unitType: string) => {
this.hideSelectionWheel();
this.hideSelectionScroll();
spawnGroundUnit(unitType, e.latlng, getActiveCoalition());
});
}, true);
}
#drawMeasureLine()
{
var mouseLatLng = this.containerPointToLatLng(this.#lastMousePosition);
if (this.#measurePoint != null)
{
var points = [this.#measurePoint, mouseLatLng];
this.#measureLine.setLatLngs(points);
var dist = distance(this.#measurePoint.lat, this.#measurePoint.lng, mouseLatLng.lat, mouseLatLng.lng);
var bear = bearing(this.#measurePoint.lat, this.#measurePoint.lng, mouseLatLng.lat, mouseLatLng.lng);
var startXY = this.latLngToContainerPoint(this.#measurePoint);
var dx = (this.#lastMousePosition.x - startXY.x);
var dy = (this.#lastMousePosition.y - startXY.y);
var angle = Math.atan2(dy, dx);
if (angle > Math.PI / 2)
angle = angle - Math.PI;
if (angle < -Math.PI / 2)
angle = angle + Math.PI;
this.#measureLineDiv.innerHTML = `${zeroAppend(Math.floor(bear), 3)}° / ${zeroAppend(Math.floor(dist*0.000539957), 3)} NM`
this.#measureLineDiv.style.left = (this.#lastMousePosition.x + startXY.x) / 2 - this.#measureLineDiv.offsetWidth / 2 + "px";
this.#measureLineDiv.style.top = (this.#lastMousePosition.y + startXY.y) / 2 - this.#measureLineDiv.offsetHeight / 2 + "px";
this.#measureLineDiv.style.rotate = angle + "rad";
this.#measureLineDiv.style.display = "";
}
if (!this.hasLayer(this.#measureLine))
this.#measureLine.addTo(this);
}
#hideMeasureLine()
{
this.#measureLineDiv.style.display = "none";
if (this.hasLayer(this.#measureLine))
this.removeLayer(this.#measureLine)
}
}

View File

@ -1,41 +1,56 @@
import { Marker, LatLng } from "leaflet";
import { getMap } from "..";
import { Marker, LatLng, Icon } from "leaflet";
import { getMap, getUnitsManager } from "..";
import { SpawnEvent } from "../map/map";
import { AirbaseMarker } from "./airbasemarker";
var bullseyeIcons = [
new Icon({ iconUrl: 'images/bullseye0.png', iconAnchor: [30, 30]}),
new Icon({ iconUrl: 'images/bullseye1.png', iconAnchor: [30, 30]}),
new Icon({ iconUrl: 'images/bullseye2.png', iconAnchor: [30, 30]})
]
export class MissionData
{
//#bullseye : any; //TODO declare interface
//#bullseyeMarker : Marker;
#bullseyes : any; //TODO declare interface
#bullseyeMarkers: any;
#airbases : any; //TODO declare interface
#airbasesMarkers: {[name: string]: AirbaseMarker};
constructor()
{
//this.#bullseye = undefined;
//this.#bullseyeMarker = undefined;
this.#bullseyes = undefined;
this.#bullseyeMarkers = [
new Marker([0, 0], {icon: bullseyeIcons[0]}).addTo(getMap()),
new Marker([0, 0], {icon: bullseyeIcons[1]}).addTo(getMap()),
new Marker([0, 0], {icon: bullseyeIcons[2]}).addTo(getMap())
]
this.#airbasesMarkers = {};
}
update(data: any)
{
//this.#bullseye = data.missionData.bullseye;
this.#bullseyes = data.bullseye;
this.#airbases = data.airbases;
//this.#drawBullseye();
this.#drawAirbases();
if (this.#bullseyes != null && this.#airbases != null)
{
this.#drawBullseye();
this.#drawAirbases();
}
}
//#drawBullseye()
//{
// if (this.#bullseyeMarker === undefined)
// {
// this.#bullseyeMarker = new Marker([this.#bullseye.lat, this.#bullseye.lng]).addTo(map.getMap());
// }
// else
// {
// this.#bullseyeMarker.setLatLng(new LatLng(this.#bullseye.lat, this.#bullseye.lng));
// }
//}
getBullseyes()
{
return this.#bullseyes;
}
#drawBullseye()
{
for (let idx in this.#bullseyes)
{
var bullseye = this.#bullseyes[idx];
this.#bullseyeMarkers[idx].setLatLng(new LatLng(bullseye.lat, bullseye.lng));
}
}
#drawAirbases()
{
@ -48,7 +63,7 @@ export class MissionData
position: new LatLng(airbase.lat, airbase.lng),
name: airbase.callsign,
src: "images/airbase.png"}).addTo(getMap());
this.#airbasesMarkers[idx].on('click', (e) => this.#onAirbaseClick(e));
this.#airbasesMarkers[idx].on('contextmenu', (e) => this.#onAirbaseClick(e));
}
else
{
@ -59,7 +74,25 @@ export class MissionData
#onAirbaseClick(e: any)
{
var spawnEvent: SpawnEvent = {x: e.originalEvent.x, y: e.originalEvent.y, latlng: e.latlng, airbaseName: e.sourceTarget.getName(), coalitionID: e.sourceTarget.getCoalitionID()};
getMap().spawnFromAirbase(spawnEvent);
var options = [];
if (getUnitsManager().getSelectedUnits().length > 0)
options = ["Spawn unit", "Land here"];
else
options = ["Spawn unit"];
getMap().showSelectionScroll(e.originalEvent, e.sourceTarget.getName(), options, (option: string) => this.#onAirbaseOptionSelection(e, option), false);
}
#onAirbaseOptionSelection(e: any, option: string) {
if (option === "Spawn unit") {
var spawnEvent: SpawnEvent = {x: e.originalEvent.x, y: e.originalEvent.y, latlng: e.latlng, airbaseName: e.sourceTarget.getName(), coalitionID: e.sourceTarget.getCoalitionID()};
getMap().spawnFromAirbase(spawnEvent);
}
else if (option === "Land here")
{
getMap().hideSelectionWheel();
getMap().hideSelectionScroll();
getUnitsManager().selectedUnitsLandAt(e.latlng);
}
}
}

View File

@ -26,7 +26,7 @@ export function bearing(lat1: number, lon1: number, lat2: number, lon2: number)
return brng;
}
const zeroPad = function (num: number, places: number) {
export const zeroPad = function (num: number, places: number) {
var string = String(num);
while (string.length < places) {
string += "0";
@ -34,6 +34,14 @@ const zeroPad = function (num: number, places: number) {
return string;
}
export const zeroAppend = function (num: number, places: number) {
var string = String(num);
while (string.length < places) {
string = "0" + string;
}
return string;
}
export function ConvertDDToDMS(D: number, lng: boolean) {
var dir = D < 0 ? (lng ? "W" : "S") : lng ? "E" : "N";
var deg = 0 | (D < 0 ? (D = -D) : D);

View File

@ -0,0 +1,73 @@
import { LatLng } from "leaflet";
import { getMissionData } from "..";
import { distance, bearing, zeroPad, zeroAppend } from "../other/utils";
import { Unit } from "../units/unit";
export class MouseInfoPanel {
#element: HTMLElement
#display: string;
constructor(ID: string) {
this.#element = <HTMLElement>document.getElementById(ID);
this.#display = '';
if (this.#element != null) {
this.#display = this.#element.style.display;
var el = <HTMLElement>this.#element.querySelector(`#measure-position`);
this.show();
}
}
show() {
this.#element.style.display = this.#display;
}
hide() {
this.#element.style.display = "none";
}
update(mousePosition: LatLng, measurePosition: LatLng | null, unitPosition: LatLng | null) {
var bullseyes = getMissionData().getBullseyes();
for (let idx in bullseyes)
{
var dist = distance(bullseyes[idx].lat, bullseyes[idx].lng, mousePosition.lat, mousePosition.lng);
var bear = bearing(bullseyes[idx].lat, bullseyes[idx].lng, mousePosition.lat, mousePosition.lng);
var el = <HTMLElement>this.#element.querySelector(`#bullseye-${idx}`);
if (el != null)
el.innerHTML = `${zeroAppend(Math.floor(bear), 3)}° / ${zeroAppend(Math.floor(dist*0.000539957), 3)} NM`
}
if (measurePosition) {
var dist = distance(measurePosition.lat, measurePosition.lng, mousePosition.lat, mousePosition.lng);
var bear = bearing(measurePosition.lat, measurePosition.lng, mousePosition.lat, mousePosition.lng);
var el = <HTMLElement>this.#element.querySelector(`#measure-position`);
if (el != null)
{
el.innerHTML = `${zeroAppend(Math.floor(bear), 3)}° / ${zeroAppend(Math.floor(dist*0.000539957), 3)} NM`
if (el.parentElement != null)
el.parentElement.style.display = 'flex'; //TODO: don't like that its hardcoded
}
}
else {
var el = <HTMLElement>this.#element.querySelector(`#measure-position`);
if (el != null && el.parentElement != null)
el.parentElement.style.display = 'none';
}
if (unitPosition) {
var dist = distance(unitPosition.lat, unitPosition.lng, mousePosition.lat, mousePosition.lng);
var bear = bearing(unitPosition.lat, unitPosition.lng, mousePosition.lat, mousePosition.lng);
var el = <HTMLElement>this.#element.querySelector(`#unit-position`);
if (el != null)
{
el.innerHTML = `${zeroAppend(Math.floor(bear), 3)}° / ${zeroAppend(Math.floor(dist*0.000539957), 3)} NM`
if (el.parentElement != null)
el.parentElement.style.display = 'flex'; //TODO: don't like that its hardcoded
}
}
else {
var el = <HTMLElement>this.#element.querySelector(`#unit-position`);
if (el != null && el.parentElement != null)
el.parentElement.style.display = 'none';
}
}
}

View File

@ -0,0 +1,363 @@
import { imageOverlay } from "leaflet";
import { getUnitControlSliders, getUnitsManager } from "..";
import { ConvertDDToDMS, rad2deg } from "../other/utils";
import { Aircraft, AirUnit, GroundUnit, Helicopter, NavyUnit, Unit } from "../units/unit";
export class UnitControlPanel {
#element: HTMLElement
#display: string;
constructor(ID: string) {
this.#element = <HTMLElement>document.getElementById(ID);
this.#display = '';
if (this.#element != null) {
this.#display = this.#element.style.display;
var formationCreationContainer = <HTMLElement>(this.#element.querySelector("#formation-creation-container"));
if (formationCreationContainer != null)
{
var createButton = <HTMLElement>formationCreationContainer.querySelector("#create-formation");
createButton?.addEventListener("click", () => getUnitsManager().selectedUnitsCreateFormation());
var undoButton = <HTMLElement>formationCreationContainer.querySelector("#undo-formation");
undoButton?.addEventListener("click", () => getUnitsManager().selectedUnitsUndoFormation());
}
var ROEButtonsContainer = <HTMLElement>(this.#element.querySelector("#roe-buttons-container"));
if (ROEButtonsContainer != null)
{
(<HTMLElement>ROEButtonsContainer.querySelector("#free"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetROE("Free"));
(<HTMLElement>ROEButtonsContainer.querySelector("#designated-free"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetROE("Designated free"));
(<HTMLElement>ROEButtonsContainer.querySelector("#designated"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetROE("Designated"));
(<HTMLElement>ROEButtonsContainer.querySelector("#return"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetROE("Return"));
(<HTMLElement>ROEButtonsContainer.querySelector("#hold"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetROE("Hold"));
}
var reactionToThreatButtonsContainer = <HTMLElement>(this.#element.querySelector("#reaction-to-threat-buttons-container"));
if (reactionToThreatButtonsContainer != null)
{
(<HTMLElement>reactionToThreatButtonsContainer.querySelector("#none"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetReactionToThreat("None"));
(<HTMLElement>reactionToThreatButtonsContainer.querySelector("#passive"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetReactionToThreat("Passive"));
(<HTMLElement>reactionToThreatButtonsContainer.querySelector("#evade"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetReactionToThreat("Evade"));
(<HTMLElement>reactionToThreatButtonsContainer.querySelector("#escape"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetReactionToThreat("Escape"));
(<HTMLElement>reactionToThreatButtonsContainer.querySelector("#abort"))?.addEventListener("click", () => getUnitsManager().selectedUnitsSetReactionToThreat("Abort"));
}
this.hide();
}
}
show() {
this.#element.style.display = this.#display;
}
hide() {
this.#element.style.display = "none";
}
update(units: Unit[]) {
if (this.#element != null)
{
var selectedUnitsContainer = <HTMLElement>(this.#element.querySelector("#selected-units-container"));
var formationCreationContainer = <HTMLElement>(this.#element.querySelector("#formation-creation-container"));
if (selectedUnitsContainer != null && formationCreationContainer != null)
{
this.#addUnitsButtons(units, selectedUnitsContainer);
this.#showFlightControlSliders(units);
this.#showFormationButtons(units, formationCreationContainer);
}
var ROEButtonsContainer = <HTMLElement>(this.#element.querySelector("#roe-buttons-container"));
if (ROEButtonsContainer != null)
{
(<HTMLElement>ROEButtonsContainer.querySelector("#free"))?.classList.toggle("white", this.#getROE(units) === "Free");
(<HTMLElement>ROEButtonsContainer.querySelector("#designated-free"))?.classList.toggle("white", this.#getROE(units) === "Designated free");
(<HTMLElement>ROEButtonsContainer.querySelector("#designated"))?.classList.toggle("white", this.#getROE(units) === "Designated");
(<HTMLElement>ROEButtonsContainer.querySelector("#return"))?.classList.toggle("white", this.#getROE(units) === "Return");
(<HTMLElement>ROEButtonsContainer.querySelector("#hold"))?.classList.toggle("white", this.#getROE(units) === "Hold");
}
var reactionToThreatButtonsContainer = <HTMLElement>(this.#element.querySelector("#reaction-to-threat-buttons-container"));
if (reactionToThreatButtonsContainer != null)
{
(<HTMLElement>reactionToThreatButtonsContainer.querySelector("#none"))?.classList.toggle("white", this.#getReactionToThreat(units) === "None");
(<HTMLElement>reactionToThreatButtonsContainer.querySelector("#passive"))?.classList.toggle("white", this.#getReactionToThreat(units) === "Passive");
(<HTMLElement>reactionToThreatButtonsContainer.querySelector("#evade"))?.classList.toggle("white", this.#getReactionToThreat(units) === "Evade");
(<HTMLElement>reactionToThreatButtonsContainer.querySelector("#escape"))?.classList.toggle("white", this.#getReactionToThreat(units) === "Escape");
(<HTMLElement>reactionToThreatButtonsContainer.querySelector("#abort"))?.classList.toggle("white", this.#getReactionToThreat(units) === "Abort");
}
}
}
#showFlightControlSliders(units: Unit[])
{
var sliders = getUnitControlSliders();
sliders.airspeed.show();
sliders.altitude.show();
if (this.#checkAllUnitsAircraft(units))
{
sliders.airspeed.setMinMax(100, 600);
sliders.altitude.setMinMax(0, 50000);
}
else if (this.#checkAllUnitsHelicopter(units))
{
sliders.airspeed.setMinMax(0, 200);
sliders.altitude.setMinMax(0, 10000);
}
else if (this.#checkAllUnitsGroundUnit(units))
{
sliders.airspeed.setMinMax(0, 60);
sliders.altitude.hide();
}
else if (this.#checkAllUnitsNavyUnit(units))
{
sliders.airspeed.setMinMax(0, 60);
sliders.altitude.hide();
}
else {
sliders.airspeed.hide();
sliders.altitude.hide();
}
var targetSpeed = this.#getTargetAirspeed(units);
if (targetSpeed != null)
{
sliders.airspeed.setActive(true);
sliders.airspeed.setValue(targetSpeed * 1.94384);
}
else
{
sliders.airspeed.setActive(false);
}
var targetAltitude = this.#getTargetAltitude(units);
if (targetAltitude != null)
{
sliders.altitude.setActive(true);
sliders.altitude.setValue(targetAltitude / 0.3048);
}
else
{
sliders.altitude.setActive(false);
}
}
#addUnitsButtons(units: Unit[], selectedUnitsContainer: HTMLElement)
{
/* Remove any pre-existing unit button */
var elements = selectedUnitsContainer.getElementsByClassName("js-unit-container");
while (elements.length > 0)
selectedUnitsContainer.removeChild(elements[0])
/* Create all the units buttons */
for (let unit of units)
{
this.#addUnitButton(unit, selectedUnitsContainer);
if (unit.isLeader)
for (let wingman of unit.getWingmen())
this.#addUnitButton(wingman, selectedUnitsContainer);
}
}
#addUnitButton(unit: Unit, container: HTMLElement)
{
var el = document.createElement("div");
/* Unit name (actually type, but DCS calls it name for some reason) */
var nameDiv = document.createElement("div");
nameDiv.classList.add("rounded-container-small");
if (unit.name.length >= 7)
nameDiv.innerHTML = `${unit.name.substring(0, 4)} ...`;
else
nameDiv.innerHTML = `${unit.name}`;
/* Unit icon */
var icon = document.createElement("img");
if (unit.isLeader)
icon.src = "images/icons/formation.png"
else if (unit.isWingman)
{
var wingmen = unit.getLeader()?.getWingmen();
if (wingmen && wingmen.lastIndexOf(unit) == wingmen.length - 1)
icon.src = "images/icons/formation-end.svg"
else
icon.src = "images/icons/formation-middle.svg"
}
else
icon.src = "images/icons/singleton.png"
el.innerHTML = unit.unitName;
el.prepend(nameDiv);
/* Show the icon only for air units */
if ((unit instanceof AirUnit))
el.append(icon);
el.classList.add("rounded-container", "js-unit-container");
if (!unit.getSelected())
el.classList.add("not-selected")
/* Set background color */
if (unit.coalitionID == 1)
{
el.classList.add("red");
icon.classList.add("red");
}
else if (unit.coalitionID == 2)
{
el.classList.add("blue");
icon.classList.add("blue");
}
else
{
el.classList.add("neutral");
icon.classList.add("neutral");
}
el.addEventListener("click", () => getUnitsManager().selectUnit(unit.ID));
container.appendChild(el);
}
#showFormationButtons(units: Unit[], formationCreationContainer: HTMLElement)
{
var createButton = <HTMLElement>formationCreationContainer.querySelector("#create-formation");
var undoButton = <HTMLElement>formationCreationContainer.querySelector("#undo-formation");
if (createButton && undoButton && this.#checkAllUnitsAir(units))
{
if (!this.#checkUnitsAlreadyInFormation(units))
{
createButton.style.display = '';
undoButton.style.display = 'none';
}
else if (this.#checkUnitsAlreadyInFormation(units) && this.#checkAllUnitsSameFormation(units))
{
createButton.style.display = 'none';
undoButton.style.display = '';
}
else
{
createButton.style.display = 'none';
undoButton.style.display = 'none';
}
}
}
#checkAllUnitsAir(units: Unit[])
{
for (let unit of units)
if (!(unit instanceof AirUnit))
return false
return true
}
#checkAllUnitsAircraft(units: Unit[])
{
for (let unit of units)
if (!(unit instanceof Aircraft))
return false
return true
}
#checkAllUnitsHelicopter(units: Unit[])
{
for (let unit of units)
if (!(unit instanceof Helicopter))
return false
return true
}
#checkAllUnitsGroundUnit(units: Unit[])
{
for (let unit of units)
if (!(unit instanceof GroundUnit))
return false
return true
}
#checkAllUnitsNavyUnit(units: Unit[])
{
for (let unit of units)
if (!(unit instanceof NavyUnit))
return false
return true
}
#checkAllUnitsSameFormation(units: Unit[])
{
var leaderFound = false;
for (let unit of units)
{
if (unit.isLeader)
{
if (leaderFound)
return false
else
leaderFound = true;
}
if (!unit.isLeader)
return false
}
return true
}
#checkUnitsAlreadyInFormation(units: Unit[])
{
for (let unit of units)
if (unit.isLeader)
return true
return false
}
#getTargetAirspeed(units: Unit[])
{
var airspeed = null;
for (let unit of units)
{
if (unit.targetSpeed != airspeed && airspeed != null)
return null
else
airspeed = unit.targetSpeed;
}
return airspeed;
}
#getTargetAltitude(units: Unit[])
{
var altitude = null;
for (let unit of units)
{
if (unit.targetAltitude != altitude && altitude != null)
return null
else
altitude = unit.targetAltitude;
}
return altitude;
}
#getROE(units: Unit[])
{
var ROE = null;
for (let unit of units)
{
if (unit.ROE !== ROE && ROE != null)
return null
else
ROE = unit.ROE;
}
return ROE;
}
#getReactionToThreat(units: Unit[])
{
var reactionToThreat = null;
for (let unit of units)
{
if (unit.reactionToThreat !== reactionToThreat && reactionToThreat != null)
return null
else
reactionToThreat = unit.reactionToThreat;
}
return reactionToThreat;
}
}

View File

@ -48,6 +48,15 @@ export class UnitInfoPanel {
this.#element.querySelector("#latitude")!.innerHTML = ConvertDDToDMS(unit.latitude, false);
this.#element.querySelector("#longitude")!.innerHTML = ConvertDDToDMS(unit.longitude, true);
this.#element.querySelector("#task")!.innerHTML = unit.currentTask !== ""? unit.currentTask: "Not controlled";
this.#element.querySelector("#task")!.classList.remove("red", "blue", "neutral");
if (unit.coalitionID == 1)
this.#element.querySelector("#task")!.classList.add("red");
else if (unit.coalitionID == 2)
this.#element.querySelector("#task")!.classList.add("blue");
else
this.#element.querySelector("#task")!.classList.add("neutral");
}
}
}

View File

@ -2,7 +2,7 @@ import { Marker, LatLng, Polyline, Icon } from 'leaflet';
import { ConvertDDToDMS } from '../other/utils';
import { getMap, getUnitsManager, getVisibilitySettings } from '..';
import { UnitMarker, MarkerOptions, AircraftMarker, HelicopterMarker, GroundUnitMarker, NavyUnitMarker, WeaponMarker } from './unitmarker';
import { addDestination, attackUnit, changeAltitude, changeSpeed } from '../dcs/dcs';
import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, landAt, setAltitude, setReactionToThreat, setROE, setSpeed } from '../dcs/dcs';
var pathIcon = new Icon({
iconUrl: 'images/marker-icon.png',
@ -12,9 +12,6 @@ var pathIcon = new Icon({
export class Unit {
ID: number = -1;
leader: boolean = false;
wingman: boolean = false;
wingmen: Unit[] = [];
formation: string = "";
name: string = "";
unitName: string = "";
@ -33,7 +30,17 @@ export class Unit {
activePath: any = null;
ammo: any = null;
targets: any = null;
hasTask: boolean = false;
isLeader: boolean = false;
isWingman: boolean = false;
leaderID: number = 0;
wingmen: Unit[] = [];
wingmenIDs: number[] = [];
targetSpeed: number = 0;
targetAltitude: number = 0;
ROE: string = "";
reactionToThreat: string = "";
#selectable: boolean;
#selected: boolean = false;
#preventClick: boolean = false;
@ -61,42 +68,36 @@ export class Unit {
this.#marker = marker;
this.#marker.on('click', (e) => this.#onClick(e));
this.#marker.on('dblclick', (e) => this.#onDoubleClick(e));
this.#marker.on('contextmenu', (e) => this.#onContextMenu(e));
this.#pathPolyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1 });
this.#pathPolyline.addTo(getMap());
this.#targetsPolylines = [];
}
update(response: JSON) {
update(response: any) {
for (let entry in response) {
// @ts-ignore
// @ts-ignore TODO handle better
this[entry] = response[entry];
}
// TODO handle better
if (response['activePath'] == undefined)
this.activePath = null
/* Dead units can't be selected */
this.setSelected(this.getSelected() && this.alive)
this.#updateMarker();
this.#clearTargets();
if (this.getSelected())
if (this.getSelected() && this.activePath != null)
{
this.#drawPath();
this.#drawTargets();
}
else
this.#clearPath();
/*
this.wingmen = [];
if (response["wingmenIDs"] != null)
{
for (let ID of response["wingmenIDs"])
{
this.wingmen.push(unitsManager.getUnitByID(ID));
}
}
*/
this.#clearPath();
}
setSelected(selected: boolean) {
@ -105,6 +106,7 @@ export class Unit {
this.#selected = selected;
this.#marker.setSelected(selected);
getUnitsManager().onUnitSelection();
}
}
@ -140,6 +142,28 @@ export class Unit {
return false;
}
getLeader() {
return getUnitsManager().getUnitByID(this.leaderID);
}
getFormation() {
return [<Unit>this].concat(this.getWingmen())
}
getWingmen() {
var wingmen: Unit[] = [];
if (this.wingmenIDs != null)
{
for (let ID of this.wingmenIDs)
{
var unit = getUnitsManager().getUnitByID(ID)
if (unit)
wingmen.push(unit);
}
}
return wingmen;
}
#onClick(e: any) {
this.#timer = setTimeout(() => {
if (!this.#preventClick) {
@ -157,22 +181,21 @@ export class Unit {
#onDoubleClick(e: any) {
clearTimeout(this.#timer);
this.#preventClick = true;
}
#onContextMenu(e: any) {
var options = [
'Attack',
'Follow'
]
//if (!this.leader && !this.wingman) {
// options.push({ 'tooltip': 'Create formation', 'src': 'formation.png', 'callback': () => { getMap().hideSelectionWheel(); /*unitsManager.createFormation(this.ID);*/ } });
//}
getMap().showSelectionScroll(e.originalEvent, options, (action: string) => this.#executeAction(action));
getMap().showSelectionScroll(e.originalEvent, "Action: " + this.unitName, options, (action: string) => this.#executeAction(action));
}
#executeAction(action: string) {
getMap().hideSelectionScroll();
if (action === "Attack")
getUnitsManager().attackUnit(this.ID);
getUnitsManager().selectedUnitsAttackUnit(this.ID);
}
#updateMarker() {
@ -199,8 +222,8 @@ export class Unit {
#drawPath() {
if (this.activePath != null) {
var _points = [];
_points.push(new LatLng(this.latitude, this.longitude));
var points = [];
points.push(new LatLng(this.latitude, this.longitude));
/* Add markers if missing */
while (this.#pathMarkers.length < Object.keys(this.activePath).length) {
@ -218,8 +241,8 @@ export class Unit {
for (let WP in this.activePath) {
var destination = this.activePath[WP];
this.#pathMarkers[parseInt(WP) - 1].setLatLng([destination.lat, destination.lng]);
_points.push(new LatLng(destination.lat, destination.lng));
this.#pathPolyline.setLatLngs(_points);
points.push(new LatLng(destination.lat, destination.lng));
this.#pathPolyline.setLatLngs(points);
}
}
}
@ -232,7 +255,6 @@ export class Unit {
this.#pathPolyline.setLatLngs([]);
}
#drawTargets()
{
for (let typeIndex in this.targets)
@ -278,7 +300,6 @@ export class Unit {
}
}
attackUnit(targetID: number) {
/* Call DCS attackUnit function */
if (this.ID != targetID) {
@ -289,7 +310,11 @@ export class Unit {
}
}
landAt(latlng: LatLng)
{
landAt(this.ID, latlng);
}
changeSpeed(speedChange: string)
{
changeSpeed(this.ID, speedChange);
@ -300,6 +325,26 @@ export class Unit {
changeAltitude(this.ID, altitudeChange);
}
setSpeed(speed: number)
{
setSpeed(this.ID, speed);
}
setAltitude(altitude: number)
{
setAltitude(this.ID, altitude);
}
setROE(ROE: string)
{
setROE(this.ID, ROE);
}
setReactionToThreat(reactionToThreat: string)
{
setReactionToThreat(this.ID, reactionToThreat);
}
/*
setformation(formation)
{
@ -318,25 +363,13 @@ export class Unit {
xhr.send(JSON.stringify(data));
}
setLeader(wingmenIDs)
{
// TODO move in dedicated file
var xhr = new XMLHttpRequest();
xhr.open("PUT", RESTaddress);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
console.log(this.unitName + " created formation with: " + wingmenIDs);
}
};
var command = {"ID": this.ID, "wingmenIDs": wingmenIDs}
var data = {"setLeader": command}
xhr.send(JSON.stringify(data));
}
*/
setLeader(isLeader: boolean, wingmenIDs: number[] = [])
{
setLeader(this.ID, isLeader, wingmenIDs);
}
}
export class AirUnit extends Unit {

View File

@ -1,6 +1,7 @@
import { LatLng, LatLngBounds } from "leaflet";
import { getMap, getUnitInfoPanel } from "..";
import { getMap, getUnitControlPanel, getUnitInfoPanel } from "..";
import { Unit, GroundUnit } from "./unit";
import { cloneUnit } from "../dcs/dcs";
export class UnitsManager {
#units: { [ID: number]: Unit };
@ -9,6 +10,24 @@ export class UnitsManager {
constructor() {
this.#units = {};
this.#copiedUnits = [];
document.addEventListener('copy', () => this.copyUnits());
document.addEventListener('paste', () => this.pasteUnits());
}
#updateUnitControlPanel() {
/* Update the unit control panel */
if (this.getSelectedUnits().length > 0) {
getUnitControlPanel().show();
getUnitControlPanel().update(this.getSelectedLeaders().concat(this.getSelectedSingletons()));
}
else {
getUnitControlPanel().hide();
}
}
getUnits() {
return this.#units;
}
addUnit(ID: number, data: any) {
@ -27,7 +46,10 @@ export class UnitsManager {
}
getUnitByID(ID: number) {
return this.#units[ID];
if (ID in this.#units)
return this.#units[ID];
else
return null;
}
removeUnit(ID: number) {
@ -40,6 +62,13 @@ export class UnitsManager {
}
}
selectUnit(ID: number, deselectAllUnits: boolean = true)
{
if (deselectAllUnits)
this.deselectAllUnits();
this.#units[ID]?.setSelected(true);
}
update(data: any) {
for (let ID in data["units"]) {
/* Create the unit if missing from the local array, then update the data. Drawing is handled by leaflet. */
@ -49,6 +78,7 @@ export class UnitsManager {
this.#units[parseInt(ID)].update(data["units"][ID]);
}
/* Update the unit info panel */
if (this.getSelectedUnits().length == 1) {
getUnitInfoPanel().show();
getUnitInfoPanel().update(this.getSelectedUnits()[0]);
@ -67,6 +97,8 @@ export class UnitsManager {
getMap().setState("IDLE");
//unitControlPanel.setEnabled(false);
}
this.#updateUnitControlPanel();
}
selectFromBounds(bounds: LatLngBounds)
@ -92,7 +124,35 @@ export class UnitsManager {
return selectedUnits;
}
addDestination(latlng: L.LatLng) {
getSelectedLeaders() {
var leaders: Unit[] = [];
for (let idx in this.getSelectedUnits())
{
var unit = this.getSelectedUnits()[idx];
if (unit.isLeader)
leaders.push(unit);
else if (unit.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.isLeader && !unit.isWingman)
singletons.push(unit);
}
return singletons;
}
selectedUnitsAddDestination(latlng: L.LatLng) {
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits) {
var commandedUnit = selectedUnits[idx];
@ -104,7 +164,7 @@ export class UnitsManager {
}
}
clearDestinations() {
selectedUnitsClearDestinations() {
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits) {
var commandedUnit = selectedUnits[idx];
@ -116,6 +176,15 @@ export class UnitsManager {
}
}
selectedUnitsLandAt(latlng: LatLng)
{
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
selectedUnits[idx].landAt(latlng);
}
}
selectedUnitsChangeSpeed(speedChange: string)
{
var selectedUnits = this.getSelectedUnits();
@ -123,6 +192,8 @@ export class UnitsManager {
{
selectedUnits[idx].changeSpeed(speedChange);
}
setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail
}
selectedUnitsChangeAltitude(altitudeChange: string)
@ -132,35 +203,66 @@ export class UnitsManager {
{
selectedUnits[idx].changeAltitude(altitudeChange);
}
setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail
}
// handleKeyEvent(e)
// {
// if (e.originalEvent.code === 'KeyC' && e.originalEvent.ctrlKey)
// {
// this.copyUnits();
// }
// else if (e.originalEvent.code === 'KeyV' && e.originalEvent.ctrlKey)
// {
// this.pasteUnits();
// }
// }
selectedUnitsSetSpeed(speed: number)
{
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
selectedUnits[idx].setSpeed(speed);
}
}
// copyUnits()
// {
// this.#copiedUnits = this.getSelectedUnits();
// }
selectedUnitsSetAltitude(altitude: number)
{
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
selectedUnits[idx].setAltitude(altitude);
}
}
// pasteUnits()
// {
// for (let idx in this.#copiedUnits)
// {
// var unit = this.#copiedUnits[idx];
// cloneUnit(unit.ID);
// }
// }
selectedUnitsSetROE(ROE: string)
{
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
selectedUnits[idx].setROE(ROE);
}
attackUnit(ID: number) {
setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail
}
selectedUnitsSetReactionToThreat(reactionToThreat: string)
{
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
selectedUnits[idx].setReactionToThreat(reactionToThreat);
}
setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail
}
copyUnits()
{
this.#copiedUnits = this.getSelectedUnits();
}
pasteUnits()
{
for (let idx in this.#copiedUnits)
{
var unit = this.#copiedUnits[idx];
cloneUnit(unit.ID, getMap().getMouseCoordinates());
}
}
selectedUnitsAttackUnit(ID: number) {
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits) {
/* If a unit is a wingman, send the command to its leader */
@ -173,59 +275,59 @@ export class UnitsManager {
}
}
// createFormation(ID)
// {
// var selectedUnits = this.getSelectedUnits();
// var wingmenIDs = [];
// for (let idx in selectedUnits)
// {
// if (selectedUnits[idx].wingman)
// {
// showMessage(selectedUnits[idx].unitName + " is already in a formation.");
// return;
// }
// else if (selectedUnits[idx].leader)
// {
// showMessage(selectedUnits[idx].unitName + " is already in a formation.");
// return;
// }
// else
// {
// /* TODO
// if (selectedUnits[idx].category !== this.getUnitByID(ID).category)
// {
// showMessage("All units must be of the same category to create a formation.");
// }
// */
// if (selectedUnits[idx].ID != ID)
// {
// wingmenIDs.push(selectedUnits[idx].ID);
// }
// }
// }
// if (wingmenIDs.length > 0)
// {
// this.getUnitByID(ID).setLeader(wingmenIDs);
// }
// else
// {
// showMessage("At least 2 units must be selected to create a formation.");
// }
// }
selectedUnitsCreateFormation(ID: number | null = null)
{
var selectedUnits = this.getSelectedUnits();
if (selectedUnits.length >= 2)
{
if (ID == null)
ID = selectedUnits[0].ID
var wingmenIDs = [];
for (let idx in selectedUnits)
{
if (selectedUnits[idx].isWingman)
{
console.log(selectedUnits[idx].unitName + " is already in a formation.");
return;
}
else if (selectedUnits[idx].isLeader)
{
console.log(selectedUnits[idx].unitName + " is already in a formation.");
return;
}
else
{
/* TODO
if (selectedUnits[idx].category !== this.getUnitByID(ID).category)
{
showMessage("All units must be of the same category to create a formation.");
}
*/
if (selectedUnits[idx].ID != ID)
{
wingmenIDs.push(selectedUnits[idx].ID);
}
}
}
if (wingmenIDs.length > 0)
{
this.getUnitByID(ID)?.setLeader(true, wingmenIDs);
}
else
{
console.log("At least 2 units must be selected to create a formation.");
}
}
setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail
}
// getLeader(ID)
// {
// for (let idx in this.#units)
// {
// var unit = this.#units[idx];
// if (unit.leader)
// {
// if (unit.wingmen.includes(this.getUnitByID(ID)))
// {
// return unit;
// }
// }
// }
// showMessage("Error: no leader found for this unit")
// }
selectedUnitsUndoFormation(ID: number | null = null)
{
for (let leader of this.getSelectedLeaders())
{
leader.setLeader(false);
}
setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail
}
}

View File

@ -30,6 +30,7 @@
<%- include('unitcontrolpanel.ejs') %>
<%- include('visibilitycontrolpanel.ejs') %>
<%- include('connectionstatuspanel.ejs') %>
<%- include('mouseinfopanel.ejs') %>
<script src="javascripts/bundle.js"></script>
</body>

View File

@ -0,0 +1,6 @@
<div class="olympus-panel" id="mouse-info-panel">
<div id="measure-position-container" class="rectangular-container"><img src="images/pin.png"><div id="measure-position">---° / --- NM</div></div>
<div id="unit-position-container" class="rectangular-container"><img src="images/unit.png"><div id="unit-position">---° / --- NM</div></div>
<div class="rectangular-container"><img src="images/BEBlue.png"><div id="bullseye-2">---° / --- NM</div></div>
<div class="rectangular-container"><img src="images/BERed.png"><div id="bullseye-1">---° / --- NM</div></div>
</div>

View File

@ -1,8 +1,11 @@
<div id="selection-scroll" class="olympus-selection-scroll-container">
<label id="coalition-switch-container">
<input type="checkbox" id="coalition-switch"> <span
class="olympus-selection-scroll-slider olympus-selection-scroll-switch"></span>
</label>
<div id="olympus-selection-scroll-top-bar">
<div class="olympus-selection-scroll-title"></div>
<label id="coalition-switch-container">
<input type="checkbox" id="coalition-switch"> <span
class="olympus-selection-scroll-slider olympus-selection-scroll-switch"></span>
</label>
</div>
<div class="olympus-selection-scroll">
</div>
</div>

View File

@ -1,12 +1,65 @@
<div class="olympus-panel" id="unit-control-buttons">
<div class="olympus-button" id="slow-button"></div>
<div class="olympus-button" id="fast-button"></div>
<div class="olympus-button" id="descend-button"></div>
<div class="olympus-button" id="climb-button"></div>
</div>
<div class="olympus-panel" id="unit-control-panel">
<div class="olympus-panel" id="unit-control-buttons">
<div class="olympus-button" id="slow-button"></div>
<div class="olympus-button" id="fast-button"></div>
<div class="olympus-button" id="descend-button"></div>
<div class="olympus-button" id="climb-button"></div>
</div>
<div id="title-label">Selected units</div>
<div class="rounded-container">Olympus-1</div>
<div class="rounded-container">Olympus-2</div>
<div id="selected-units-container">
<!-- This is where all the unit selection buttons will be shown-->
</div>
<div id="formation-creation-container">
<div class="rectangular-button white" id="create-formation"><img src="images\buttons\create.svg">Create formation</div>
<div class="rectangular-button white" id="undo-formation"><img src="images\buttons\erase.svg">Undo formation</div>
</div>
<div class="hl"></div>
<div id="section-label">Controls</div>
<div id="flight-controls-buttons-container">
<div class="slider-container flight-control-slider" id="altitude-slider">
<div class="flight-control-title">Altitude</div>
<div class="flight-control-value" id="value"></div>
<input type="range" min="1" max="100" value="50" class="slider">
</div>
<div class="slider-container flight-control-slider" id="airspeed-slider">
<div class="flight-control-title">Speed</div>
<div class="flight-control-value" id="value"></div>
<input type="range" min="1" max="100" value="50" class="slider">
</div>
</div>
<div id="section-label">Formation</div>
<div id="formation-buttons-container">
<div class="rectangular-button">Echelon</div>
<div class="rectangular-button">Fingertip</div>
<div class="rectangular-button">Trail</div>
<div class="rectangular-button">Line abreast</div>
</div>
<div class="hl"></div>
<div id="section-label">Rules of engagement</div>
<div id="roe-buttons-container">
<div class="rectangular-button" id="free">Free</div>
<div class="rectangular-button" id="designated-free">Designated free</div>
<div class="rectangular-button" id="designated">Designated</div>
<div class="rectangular-button" id="return">Return</div>
<div class="rectangular-button" id="hold">Hold</div>
</div>
<div class="hl"></div>
<div id="section-label">Reaction to threat</div>
<div id="reaction-to-threat-buttons-container">
<div class="rectangular-button" id="none">None</div>
<div class="rectangular-button" id="passive">Passive</div>
<div class="rectangular-button" id="evade">Evade</div>
<div class="rectangular-button" id="escape">Escape</div>
<div class="rectangular-button" id="abort">Abort</div>
</div>
</div>

View File

@ -2,7 +2,7 @@
[Setup]
AppName=DCS Olympus
AppVerName=DCS Olympus Alpha v0.0.2
AppVerName=DCS Olympus Alpha v0.0.5
DefaultDirName={usersavedgames}\DCS.openbeta
DefaultGroupName=DCSOlympus
OutputBaseFilename=DCSOlympus

View File

@ -37,10 +37,10 @@ function Olympus.getCoalitionByCoalitionID(coalitionID)
end
-- Builds a valid task depending on the provided options
function Olympus.buildTask(options)
function Olympus.buildEnrouteTask(options)
local task = nil
-- Engage specific target by ID. Checks if target exists.
if options['id'] == 'EngageUnit' and options['targetID'] ~= nil then
if options['id'] == 'EngageUnit' and options['targetID'] then
local target = Olympus.getUnitByID(options['targetID'])
if target and target:isExist() then
task = {
@ -54,25 +54,56 @@ function Olympus.buildTask(options)
return task
end
-- Move a unit. Since most tasks in DCS are Enroute tasks, this function is the main way to control the unit AI
-- Builds a valid task depending on the provided options
function Olympus.buildTask(options)
local task = nil
-- Engage specific target by ID. Checks if target exists.
if options['id'] == 'FollowUnit' and options['leaderID'] and options['offset'] then
local leader = Olympus.getUnitByID(options['leaderID'])
if leader and leader:isExist() then
task = {
id = 'Follow',
params = {
groupId = leader:getGroup():getID(),
pos = options['offset'],
lastWptIndexFlag = false,
lastWptIndex = 1
}
}
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, speed, category, taskOptions)
Olympus.notify("Olympus.move " .. ID .. " (" .. lat .. ", " .. lng ..") " .. altitude .. "m " .. speed .. "m/s " .. category, 2)
local unit = Olympus.getUnitByID(ID)
if unit then
if category == "Aircraft" then
local startPoint = mist.getLeadPos(unit:getGroup())
local endPoint = coord.LLtoLO(lat, lng, 0)
local endPoint = coord.LLtoLO(lat, lng, 0)
local path = {
[1] = mist.fixedWing.buildWP(startPoint, flyOverPoint, speed, altitude, 'BARO'),
[2] = mist.fixedWing.buildWP(endPoint, turningPoint, speed, altitude, 'BARO')
}
local path = {}
if taskOptions and taskOptions['id'] == 'Land' then
path = {
[1] = mist.fixedWing.buildWP(startPoint, flyOverPoint, speed, altitude, 'BARO'),
[2] = mist.fixedWing.buildWP(endPoint, landing, speed, 0, 'AGL')
}
else
path = {
[1] = mist.fixedWing.buildWP(startPoint, flyOverPoint, speed, altitude, 'BARO'),
[2] = mist.fixedWing.buildWP(endPoint, turningPoint, speed, altitude, 'BARO')
}
end
-- If a task exists assign it to the controller
local task = Olympus.buildTask(taskOptions)
if task then
path[1].task = task
path[2].task = task
if taskOptions then
local task = Olympus.buildEnrouteTask(taskOptions)
if task then
path[1].task = task
path[2].task = task
end
end
-- Assign the mission task to the controller
@ -171,7 +202,7 @@ function Olympus.spawnAircraft(coalition, unitType, lat, lng, spawnOptions)
local spawnLocation = mist.utils.makeVec3GL(coord.LLtoLO(lat, lng, 0))
if payload == nil then
if payloadName and payloadName ~= "" and Olympus.unitPayloads[unitType][payloadName] ~= nil then
if payloadName and payloadName ~= "" and Olympus.unitPayloads[unitType][payloadName] then
payload = Olympus.unitPayloads[unitType][payloadName]
else
payload = {}
@ -250,7 +281,7 @@ function Olympus.spawnAircraft(coalition, unitType, lat, lng, spawnOptions)
task = 'CAP',
}
mist.dynAdd(vars)
local newGroup = mist.dynAdd(vars)
-- Save the payload to be reused in case the unit is cloned. TODO: save by ID not by name (it works but I like consistency)
Olympus.payloadRegistry[vars.name] = payload
@ -259,40 +290,99 @@ function Olympus.spawnAircraft(coalition, unitType, lat, lng, spawnOptions)
end
-- Clones a unit by ID. Will clone the unit with the same original payload as the source unit. TODO: only works on Olympus unit not ME units.
function Olympus.clone(ID)
function Olympus.clone(ID, lat, lng)
Olympus.notify("Olympus.clone " .. ID, 2)
local unit = Olympus.getUnitByID(ID)
if unit then
local coalition = Olympus.getCoalitionByCoalitionID(unit:getCoalition())
local lat, lng, alt = coord.LOtoLL(unit:getPoint())
-- TODO: only works on Aircraft
local spawnOptions = {
payload = Olympus.payloadRegistry[unitName]
payload = Olympus.payloadRegistry[unit:getName()]
}
Olympus.spawnAircraft(coalition, unit:getTypeName(), lat + 0.001, lng + 0.001, spawnOptions)
Olympus.spawnAircraft(coalition, unit:getTypeName(), lat, lng, spawnOptions)
end
Olympus.notify("Olympus.clone completed successfully", 2)
end
function Olympus.follow(leaderID, ID)
Olympus.notify("Olympus.follow " .. ID .. " " .. leaderID, 2)
local leader = Olympus.getUnitByID(leaderID)
function Olympus.delete(ID, lat, lng)
Olympus.notify("Olympus.delete " .. ID, 2)
local unit = Olympus.getUnitByID(ID)
local followTask = {
id = 'Follow',
params = {
groupId = leader:getGroup():getID(),
pos = {x = 0 , y = 0, z = 20} ,
lastWptIndexFlag = false,
lastWptIndex = 1
}
}
Olympus.notify("Olympus.follow group ID" .. unit:getGroup():getID(), 2)
unit:getGroup():getController():pushTask(followTask)
Olympus.notify("Olympus.follow completed successfully", 2)
if unit then
unit:destroy();
Olympus.notify("Olympus.delete completed successfully", 2)
end
end
function Olympus.setTask(ID, taskOptions)
Olympus.notify("Olympus.setTask " .. ID .. " " .. Olympus.serializeTable(taskOptions), 2)
local unit = Olympus.getUnitByID(ID)
if unit then
local task = Olympus.buildTask(taskOptions);
if task then
unit:getGroup():getController():setTask(task)
Olympus.notify("Olympus.setTask completed successfully", 2)
end
end
end
function Olympus.resetTask(ID)
Olympus.notify("Olympus.resetTask " .. ID, 2)
local unit = Olympus.getUnitByID(ID)
if unit then
unit:getGroup():getController():resetTask()
Olympus.notify("Olympus.resetTask completed successfully", 2)
end
end
function Olympus.setCommand(ID, command)
Olympus.notify("Olympus.setCommand " .. ID .. " " .. Olympus.serializeTable(command), 2)
local unit = Olympus.getUnitByID(ID)
if unit then
unit:getGroup():getController():setCommand(command)
Olympus.notify("Olympus.setCommand completed successfully", 2)
end
end
function Olympus.setOption(ID, optionID, optionValue)
Olympus.notify("Olympus.setCommand " .. ID .. " " .. optionID .. " " .. optionValue, 2)
local unit = Olympus.getUnitByID(ID)
if unit then
unit:getGroup():getController():setOption(optionID, optionValue)
Olympus.notify("Olympus.setOption completed successfully", 2)
end
end
function Olympus.serializeTable(val, name, skipnewlines, depth)
skipnewlines = skipnewlines or false
depth = depth or 0
local tmp = string.rep(" ", depth)
if name then
if type(name) == "number" then
tmp = tmp .. "[" .. name .. "]" .. " = "
else
tmp = tmp .. name .. " = "
end
end
if type(val) == "table" then
tmp = tmp .. "{" .. (not skipnewlines and "\n" or "")
for k, v in pairs(val) do
tmp = tmp .. Olympus.serializeTable(v, k, skipnewlines, depth + 1) .. "," .. (not skipnewlines and "\n" or "")
end
tmp = tmp .. string.rep(" ", depth) .. "}"
elseif type(val) == "number" then
tmp = tmp .. tostring(val)
elseif type(val) == "string" then
tmp = tmp .. string.format("%q", val)
elseif type(val) == "boolean" then
tmp = tmp .. (val and "true" or "false")
else
tmp = tmp .. "\"[inserializeable datatype:" .. type(val) .. "]\""
end
return tmp
end
Olympus.notify("OlympusCommand script loaded successfully", 2)

View File

@ -8,11 +8,14 @@ function Olympus.setMissionData(arg, time)
local missionData = {}
-- Bullseye data
local bullseyeVec3 = coalition.getMainRefPoint(0)
local bullseyeLatitude, bullseyeLongitude, bullseyeAltitude = coord.LOtoLL(bullseyeVec3)
local bullseye = {}
bullseye["lat"] = bullseyeLatitude
bullseye["lng"] = bullseyeLongitude
for i = 0, 2 do
local bullseyeVec3 = coalition.getMainRefPoint(i)
local bullseyeLatitude, bullseyeLongitude, bullseyeAltitude = coord.LOtoLL(bullseyeVec3)
bullseye[i] = {}
bullseye[i]["lat"] = bullseyeLatitude
bullseye[i]["lng"] = bullseyeLongitude
end
-- Units tactical data
-- TODO find some way to spread the load of getting this data (split)
@ -29,6 +32,8 @@ function Olympus.setMissionData(arg, time)
table["targets"]["radar"] = controller:getDetectedTargets(4)
table["targets"]["rwr"] = controller:getDetectedTargets(16)
table["targets"]["other"] = controller:getDetectedTargets(2, 8, 32)
table["hasTask"] = controller:hasTask()
table["ammo"] = unit:getAmmo()
table["fuel"] = unit:getFuel()

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

@ -18,23 +18,6 @@
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClInclude Include="include\commands.h" />
<ClInclude Include="include\scriptLoader.h" />
<ClInclude Include="include\server.h" />
<ClInclude Include="include\scheduler.h" />
<ClInclude Include="include\unit.h" />
<ClInclude Include="include\unitsFactory.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\commands.cpp" />
<ClCompile Include="src\scriptLoader.cpp" />
<ClCompile Include="src\core.cpp" />
<ClCompile Include="src\server.cpp" />
<ClCompile Include="src\scheduler.cpp" />
<ClCompile Include="src\unit.cpp" />
<ClCompile Include="src\unitsFactory.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\dcstools\dcstools.vcxproj">
<Project>{2b255368-39a0-431a-a6de-cc739ac70dc1}</Project>
@ -49,6 +32,35 @@
<Project>{b85009ce-4a5c-4a5a-b85d-001b3a2651b2}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClInclude Include="include\aircraft.h" />
<ClInclude Include="include\airunit.h" />
<ClInclude Include="include\commands.h" />
<ClInclude Include="include\groundunit.h" />
<ClInclude Include="include\helicopter.h" />
<ClInclude Include="include\navyunit.h" />
<ClInclude Include="include\scheduler.h" />
<ClInclude Include="include\scriptloader.h" />
<ClInclude Include="include\server.h" />
<ClInclude Include="include\unit.h" />
<ClInclude Include="include\unitsfactory.h" />
<ClInclude Include="include\weapon.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\aircraft.cpp" />
<ClCompile Include="src\airunit.cpp" />
<ClCompile Include="src\commands.cpp" />
<ClCompile Include="src\core.cpp" />
<ClCompile Include="src\groundunit.cpp" />
<ClCompile Include="src\helicopter.cpp" />
<ClCompile Include="src\navyunit.cpp" />
<ClCompile Include="src\scheduler.cpp" />
<ClCompile Include="src\scriptloader.cpp" />
<ClCompile Include="src\server.cpp" />
<ClCompile Include="src\unit.cpp" />
<ClCompile Include="src\unitsfactory.cpp" />
<ClCompile Include="src\weapon.cpp" />
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>

View File

@ -9,45 +9,81 @@
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="include\aircraft.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\airunit.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\commands.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\server.h">
<ClInclude Include="include\groundunit.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\helicopter.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\navyunit.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\scheduler.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\scriptloader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\server.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\unit.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\unitsFactory.h">
<ClInclude Include="include\unitsfactory.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="include\scriptLoader.h">
<ClInclude Include="include\weapon.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\aircraft.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\airunit.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\commands.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\core.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\server.cpp">
<ClCompile Include="src\groundunit.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\helicopter.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\navyunit.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\scheduler.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\scriptloader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\server.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\unit.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\unitsFactory.cpp">
<ClCompile Include="src\unitsfactory.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\scriptLoader.cpp">
<ClCompile Include="src\weapon.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>

View File

@ -8,9 +8,57 @@ namespace CommandPriority {
};
namespace CommandType {
enum CommandTypes { NO_TYPE, MOVE, SMOKE, LASE, EXPLODE, SPAWN_AIR, SPAWN_GROUND, CLONE, LAND, REFUEL, FOLLOW };
enum CommandTypes { NO_TYPE, MOVE, SMOKE, SPAWN_AIR, SPAWN_GROUND, CLONE, FOLLOW, RESET_TASK, SET_OPTION, SET_COMMAND, SET_TASK };
};
namespace SetCommandType {
enum SetCommandTypes {
ROE = 0,
REACTION_ON_THREAT = 1,
RADAR_USING = 3,
FLARE_USING = 4,
Formation = 5,
RTB_ON_BINGO = 6,
SILENCE = 7,
RTB_ON_OUT_OF_AMMO = 10,
ECM_USING = 13,
PROHIBIT_AA = 14,
PROHIBIT_JETT = 15,
PROHIBIT_AB = 16,
PROHIBIT_AG = 17,
MISSILE_ATTACK = 18,
PROHIBIT_WP_PASS_REPORT = 19,
OPTION_RADIO_USAGE_CONTACT = 21,
OPTION_RADIO_USAGE_ENGAGE = 22,
OPTION_RADIO_USAGE_KILL = 23,
JETT_TANKS_IF_EMPTY = 25,
FORCED_ATTACK = 26
};
}
namespace ROE {
enum ROEs {
WEAPON_FREE = 0,
OPEN_FIRE_WEAPON_FREE = 1,
OPEN_FIRE = 2,
RETURN_FIRE = 3,
WEAPON_HOLD = 4,
};
}
namespace ReactionToThreat {
enum ReactionToThreats {
NO_REACTION = 0,
PASSIVE_DEFENCE = 1,
EVADE_FIRE = 2,
BYPASS_AND_ESCAPE = 3,
ALLOW_ABORT_MISSION = 4
};
}
/* Base command class */
class Command
{
@ -25,10 +73,10 @@ protected:
};
/* Simple low priority move command (from user click) */
class MoveCommand : public Command
class Move : public Command
{
public:
MoveCommand(int ID, Coords destination, double speed, double altitude, wstring unitCategory, wstring taskOptions):
Move(int ID, Coords destination, double speed, double altitude, wstring unitCategory, wstring taskOptions):
ID(ID),
destination(destination),
speed(speed),
@ -51,10 +99,10 @@ private:
};
/* Smoke command */
class SmokeCommand : public Command
class Smoke : public Command
{
public:
SmokeCommand(wstring color, Coords location) :
Smoke(wstring color, Coords location) :
color(color),
location(location)
{
@ -69,10 +117,10 @@ private:
};
/* Spawn ground unit command */
class SpawnGroundUnitCommand : public Command
class SpawnGroundUnit : public Command
{
public:
SpawnGroundUnitCommand(wstring coalition, wstring unitType, Coords location) :
SpawnGroundUnit(wstring coalition, wstring unitType, Coords location) :
coalition(coalition),
unitType(unitType),
location(location)
@ -89,10 +137,10 @@ private:
};
/* Spawn air unit command */
class SpawnAircraftCommand : public Command
class SpawnAircraft : public Command
{
public:
SpawnAircraftCommand(wstring coalition, wstring unitType, Coords location, wstring payloadName, wstring airbaseName) :
SpawnAircraft(wstring coalition, wstring unitType, Coords location, wstring payloadName, wstring airbaseName) :
coalition(coalition),
unitType(unitType),
location(location),
@ -114,35 +162,107 @@ private:
};
/* Clone unit command */
class CloneCommand : public Command
class Clone : public Command
{
public:
CloneCommand(int ID) :
ID(ID)
Clone(int ID, Coords location) :
ID(ID),
location(location)
{
priority = CommandPriority::LOW;
type = CommandType::CLONE;
};
virtual wstring getString(lua_State* L);
private:
const int ID;
const Coords location;
};
/* Delete unit command */
class Delete : public Command
{
public:
Delete(int ID) :
ID(ID)
{
priority = CommandPriority::HIGH;
type = CommandType::CLONE;
};
virtual wstring getString(lua_State* L);
private:
const int ID;
};
/* Follow command */
class FollowCommand : public Command
class SetTask : public Command
{
public:
FollowCommand(int leaderID, int ID) :
leaderID(leaderID),
ID(ID)
SetTask(int ID, wstring task) :
ID(ID),
task(task)
{
priority = CommandPriority::LOW;
priority = CommandPriority::MEDIUM;
type = CommandType::FOLLOW;
};
virtual wstring getString(lua_State* L);
private:
const int leaderID;
const int ID;
const wstring task;
};
/* Reset task command */
class ResetTask : public Command
{
public:
ResetTask(int ID) :
ID(ID)
{
priority = CommandPriority::HIGH;
type = CommandType::RESET_TASK;
};
virtual wstring getString(lua_State* L);
private:
const int ID;
};
/* Set command */
class SetCommand : public Command
{
public:
SetCommand(int ID, wstring command) :
ID(ID),
command(command)
{
priority = CommandPriority::HIGH;
type = CommandType::RESET_TASK;
};
virtual wstring getString(lua_State* L);
private:
const int ID;
const wstring command;
};
/* Set option command */
class SetOption : public Command
{
public:
SetOption(int ID, int optionID, int optionValue) :
ID(ID),
optionID(optionID),
optionValue(optionValue)
{
priority = CommandPriority::HIGH;
type = CommandType::RESET_TASK;
};
virtual wstring getString(lua_State* L);
private:
const int ID;
const int optionID;
const int optionValue;
};

View File

@ -4,8 +4,9 @@
#include "dcstools.h"
#include "luatools.h"
#define GROUND_DEST_DIST_THR 100
#define AIR_DEST_DIST_THR 2000
namespace State {
enum States { IDLE, REACH_DESTINATION, ATTACK, WINGMAN, FOLLOW, LAND, REFUEL, AWACS, EWR, TANKER, RUN_AWAY };
};
class Unit
{
@ -17,19 +18,22 @@ public:
void updateMissionData(json::value json);
json::value json();
virtual void setState(int newState) { state = newState; };
void resetTask();
void setPath(list<Coords> path);
void setActiveDestination(Coords newActiveDestination) { activeDestination = newActiveDestination; }
void setAlive(bool newAlive) { alive = newAlive; }
void setTarget(int targetID);
void setLeader(bool newLeader) { leader = newLeader; }
void setWingman(bool newWingman) { wingman = newWingman; }
void setIsLeader(bool newIsLeader);
void setIsWingman(bool newIsWingman);
void setLeader(Unit* newLeader) { leader = newLeader; }
void setWingmen(vector<Unit*> newWingmen) { wingmen = newWingmen; }
void setFormation(wstring newFormation) { formation = newFormation; }
virtual void changeSpeed(wstring change) {};
virtual void changeAltitude(wstring change) {};
void resetActiveDestination();
void setFormationOffset(Offset formationOffset);
void setROE(wstring newROE);
void setReactionToThreat(wstring newReactionToThreat);
void landAt(Coords loc);
int getID() { return ID; }
wstring getName() { return name; }
@ -46,38 +50,53 @@ public:
Coords getActiveDestination() { return activeDestination; }
virtual wstring getCategory() { return L"No category"; };
wstring getTarget();
bool isTargetAlive();
wstring getCurrentTask() { return currentTask; }
bool getAlive() { return alive; }
bool getIsLeader() { return isLeader; }
bool getIsWingman() { return isWingman; }
wstring getFormation() { return formation; }
virtual double getTargetSpeed() { return targetSpeed; };
virtual double getTargetAltitude() { return targetAltitude; };
virtual void setTargetSpeed(double newSpeed) { targetSpeed = newSpeed; }
virtual void setTargetAltitude(double newAltitude) { targetAltitude = newAltitude; }
virtual void changeSpeed(wstring change) {};
virtual void changeAltitude(wstring change) {};
void resetActiveDestination();
protected:
int ID;
bool AI = false;
bool alive = true;
wstring name = L"undefined";
wstring unitName = L"undefined";
wstring groupName = L"undefined";
json::value type = json::value::null();
int country = NULL;
int coalitionID = NULL;
double latitude = NULL;
double longitude = NULL;
double altitude = NULL;
double heading = NULL;
double speed = NULL;
json::value flags = json::value::null();
int targetID = NULL;
bool holding = false;
bool looping = false;
wstring taskOptions = L"{}";
wstring currentTask = L"";
bool leader = false;
bool wingman = false;
wstring formation = L"";
int state = State::IDLE;
bool hasTask = false;
bool AI = false;
bool alive = true;
wstring name = L"undefined";
wstring unitName = L"undefined";
wstring groupName = L"undefined";
json::value type = json::value::null();
int country = NULL;
int coalitionID = NULL;
double latitude = NULL;
double longitude = NULL;
double altitude = NULL;
double heading = NULL;
double speed = NULL;
json::value flags = json::value::null();
int targetID = NULL;
wstring currentTask = L"";
bool isLeader = false;
bool isWingman = false;
Offset formationOffset = Offset(NULL);
wstring formation = L"";
Unit* leader = nullptr;
wstring ROE = L"";
wstring reactionToThreat = L"";
vector<Unit*> wingmen;
double targetSpeed = 0;
double targetAltitude = 0;
double fuel = 0;
double targetSpeed = 0;
double targetAltitude = 0;
double fuel = 0;
json::value ammo;
json::value targets;
@ -85,112 +104,16 @@ protected:
Coords activeDestination = Coords(0);
Coords oldPosition = Coords(0); // Used to approximate speed
virtual void AIloop();
virtual void AIloop() = 0;
private:
mutex mutexLock;
};
class AirUnit : public Unit
{
public:
AirUnit(json::value json, int ID);
virtual wstring getCategory() = 0;
protected:
virtual void AIloop();
};
class Aircraft : public AirUnit
{
public:
Aircraft(json::value json, int ID);
virtual wstring getCategory() { return L"Aircraft"; };
virtual void changeSpeed(wstring change);
virtual void changeAltitude(wstring change);
virtual double getTargetSpeed() { return targetSpeed; };
virtual double getTargetAltitude() { return targetAltitude; };
protected:
double targetSpeed = 150;
double targetAltitude = 5000;
};
class Helicopter : public AirUnit
{
public:
Helicopter(json::value json, int ID);
virtual wstring getCategory() { return L"Helicopter"; };
virtual void changeSpeed(wstring change);
virtual void changeAltitude(wstring change);
virtual double getTargetSpeed() { return targetSpeed; };
virtual double getTargetAltitude() { return targetAltitude; };
protected:
double targetSpeed = 50;
double targetAltitude = 1000;
};
class GroundUnit : public Unit
{
public:
GroundUnit(json::value json, int ID);
virtual void AIloop();
virtual wstring getCategory() { return L"GroundUnit"; };
virtual void changeSpeed(wstring change);
virtual void changeAltitude(wstring change) {};
virtual double getTargetSpeed() { return targetSpeed; };
protected:
double targetSpeed = 10;
};
class NavyUnit : public Unit
{
public:
NavyUnit(json::value json, int ID);
virtual void AIloop();
virtual wstring getCategory() { return L"NavyUnit"; };
virtual void changeSpeed(wstring change);
virtual void changeAltitude(wstring change) {};
virtual double getTargetSpeed() { return targetSpeed; };
protected:
double targetSpeed = 10;
};
class Weapon : public Unit
{
public:
Weapon(json::value json, int ID);
virtual wstring getCategory() = 0;
protected:
/* Weapons are not controllable and have no AIloop */
virtual void AIloop() {};
};
class Missile : public Weapon
{
public:
Missile(json::value json, int ID);
virtual wstring getCategory() { return L"Missile"; };
};
class Bomb : public Weapon
{
public:
Bomb(json::value json, int ID);
virtual wstring getCategory() { return L"Bomb"; };
};

View File

@ -0,0 +1,21 @@
#pragma once
#include "airunit.h"
class Aircraft : public AirUnit
{
public:
Aircraft(json::value json, int ID);
virtual wstring getCategory() { return L"Aircraft"; };
virtual void changeSpeed(wstring change);
virtual void changeAltitude(wstring change);
virtual double getTargetSpeed() { return targetSpeed; };
virtual double getTargetAltitude() { return targetAltitude; };
virtual void setTargetSpeed(double newTargetSpeed);
virtual void setTargetAltitude(double newTargetAltitude);
protected:
double targetSpeed = 300 / 1.94384;
double targetAltitude = 20000 * 0.3048;
};

View File

@ -0,0 +1,30 @@
#pragma once
#include "framework.h"
#include "utils.h"
#include "dcstools.h"
#include "luatools.h"
#include "Unit.h"
#define AIR_DEST_DIST_THR 2000
class AirUnit : public Unit
{
public:
AirUnit(json::value json, int ID);
virtual wstring getCategory() = 0;
virtual void changeSpeed(wstring change) {};
virtual void changeAltitude(wstring change) {};
virtual void setTargetSpeed(double newTargetSpeed) {};
virtual void setTargetAltitude(double newTargetAltitude) {};
protected:
virtual void AIloop();
virtual void setState(int newState);
bool isDestinationReached();
bool setActiveDestination();
void createHoldingPattern();
bool updateActivePath(bool looping);
void goToDestination(wstring enrouteTask = L"nil");
void taskWingmen();
};

View File

@ -0,0 +1,19 @@
#pragma once
#include "unit.h"
#define GROUND_DEST_DIST_THR 100
class GroundUnit : public Unit
{
public:
GroundUnit(json::value json, int ID);
virtual void AIloop();
virtual wstring getCategory() { return L"GroundUnit"; };
virtual void changeSpeed(wstring change);
virtual void changeAltitude(wstring change) {};
virtual double getTargetSpeed() { return targetSpeed; };
protected:
double targetSpeed = 10;
};

View File

@ -0,0 +1,21 @@
#pragma once
#include "airunit.h"
class Helicopter : public AirUnit
{
public:
Helicopter(json::value json, int ID);
virtual wstring getCategory() { return L"Helicopter"; };
virtual void changeSpeed(wstring change);
virtual void changeAltitude(wstring change);
virtual double getTargetSpeed() { return targetSpeed; };
virtual double getTargetAltitude() { return targetAltitude; };
virtual void setTargetSpeed(double newTargetSpeed);
virtual void setTargetAltitude(double newTargetAltitude);
protected:
double targetSpeed = 100 / 1.94384;
double targetAltitude = 5000 * 0.3048;
};

View File

@ -0,0 +1,17 @@
#pragma once
#include "unit.h"
class NavyUnit : public Unit
{
public:
NavyUnit(json::value json, int ID);
virtual void AIloop();
virtual wstring getCategory() { return L"NavyUnit"; };
virtual void changeSpeed(wstring change);
virtual void changeAltitude(wstring change) {};
virtual double getTargetSpeed() { return targetSpeed; };
protected:
double targetSpeed = 10;
};

View File

@ -14,6 +14,7 @@ public:
void updateExportData(lua_State* L);
void updateMissionData(json::value missionData);
void updateAnswer(json::value& answer);
void deleteUnit(int ID);
private:
map<int, Unit*> units;

30
src/core/include/weapon.h Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include "unit.h"
class Weapon : public Unit
{
public:
Weapon(json::value json, int ID);
virtual wstring getCategory() = 0;
protected:
/* Weapons are not controllable and have no AIloop */
virtual void AIloop() {};
};
class Missile : public Weapon
{
public:
Missile(json::value json, int ID);
virtual wstring getCategory() { return L"Missile"; };
};
class Bomb : public Weapon
{
public:
Bomb(json::value json, int ID);
virtual wstring getCategory() { return L"Bomb"; };
};

View File

@ -3,7 +3,7 @@
#include "dcstools.h"
/* Move command */
wstring MoveCommand::getString(lua_State* L)
wstring Move::getString(lua_State* L)
{
std::wostringstream commandSS;
commandSS.precision(10);
@ -19,7 +19,7 @@ wstring MoveCommand::getString(lua_State* L)
}
/* Smoke command */
wstring SmokeCommand::getString(lua_State* L)
wstring Smoke::getString(lua_State* L)
{
std::wostringstream commandSS;
commandSS.precision(10);
@ -31,7 +31,7 @@ wstring SmokeCommand::getString(lua_State* L)
}
/* Spawn ground command */
wstring SpawnGroundUnitCommand::getString(lua_State* L)
wstring SpawnGroundUnit::getString(lua_State* L)
{
std::wostringstream commandSS;
commandSS.precision(10);
@ -44,7 +44,7 @@ wstring SpawnGroundUnitCommand::getString(lua_State* L)
}
/* Spawn air command */
wstring SpawnAircraftCommand::getString(lua_State* L)
wstring SpawnAircraft::getString(lua_State* L)
{
std::wostringstream optionsSS;
optionsSS.precision(10);
@ -65,23 +65,71 @@ wstring SpawnAircraftCommand::getString(lua_State* L)
}
/* Clone unit command */
wstring CloneCommand::getString(lua_State* L)
wstring Clone::getString(lua_State* L)
{
std::wostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.clone, "
commandSS << "Olympus.clone, "
<< ID << ", "
<< location.lat << ", "
<< location.lng;
return commandSS.str();
}
/* Delete unit command */
wstring Delete::getString(lua_State* L)
{
std::wostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.delete, "
<< ID;
return commandSS.str();
}
/* Follow unit command */
wstring FollowCommand::getString(lua_State* L)
/* Set task command */
wstring SetTask::getString(lua_State* L)
{
std::wostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.follow, "
<< leaderID << ","
commandSS << "Olympus.setTask, "
<< ID << ","
<< task;
return commandSS.str();
}
/* Reset task command */
wstring ResetTask::getString(lua_State* L)
{
std::wostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.resetTask, "
<< ID;
return commandSS.str();
}
/* Set command command */
wstring SetCommand::getString(lua_State* L)
{
std::wostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.setCommand, "
<< ID << ","
<< command;
return commandSS.str();
}
/* Set option command */
wstring SetOption::getString(lua_State* L)
{
std::wostringstream commandSS;
commandSS.precision(10);
commandSS << "Olympus.setOption, "
<< ID << ","
<< optionID << ","
<< optionValue;
return commandSS.str();
}

View File

@ -73,17 +73,17 @@ void Scheduler::handleRequest(wstring key, json::value value)
log(unitName + L" set path destination " + WP + L" (" + to_wstring(lat) + L", " + to_wstring(lng) + L")");
Coords dest; dest.lat = lat; dest.lng = lng;
newPath.push_back(dest);
Unit* unit = unitsFactory->getUnit(ID);
if (unit != nullptr)
{
unit->setPath(newPath);
log(unitName + L" new path set successfully");
}
else
{
log(unitName + L" not found, request will be discarded");
}
}
Unit* unit = unitsFactory->getUnit(ID);
if (unit != nullptr)
{
unit->setPath(newPath);
unit->setState(State::REACH_DESTINATION);
log(unitName + L" new path set successfully");
}
else
log(unitName + L" not found, request will be discarded");
}
}
else if (key.compare(L"smoke") == 0)
@ -93,7 +93,7 @@ void Scheduler::handleRequest(wstring key, json::value value)
double lng = value[L"location"][L"lng"].as_double();
log(L"Adding " + color + L" smoke at (" + to_wstring(lat) + L", " + to_wstring(lng) + L")");
Coords loc; loc.lat = lat; loc.lng = lng;
command = dynamic_cast<Command*>(new SmokeCommand(color, loc));
command = dynamic_cast<Command*>(new Smoke(color, loc));
}
else if (key.compare(L"spawnGround") == 0)
{
@ -103,7 +103,7 @@ void Scheduler::handleRequest(wstring key, json::value value)
double lng = value[L"location"][L"lng"].as_double();
log(L"Spawning " + coalition + L" ground unit of type " + type + L" at (" + to_wstring(lat) + L", " + to_wstring(lng) + L")");
Coords loc; loc.lat = lat; loc.lng = lng;
command = dynamic_cast<Command*>(new SpawnGroundUnitCommand(coalition, type, loc));
command = dynamic_cast<Command*>(new SpawnGroundUnit(coalition, type, loc));
}
else if (key.compare(L"spawnAir") == 0)
{
@ -115,7 +115,7 @@ void Scheduler::handleRequest(wstring key, json::value value)
wstring payloadName = value[L"payloadName"].as_string();
wstring airbaseName = value[L"airbaseName"].as_string();
log(L"Spawning " + coalition + L" air unit of type " + type + L" with payload " + payloadName + L" at (" + to_wstring(lat) + L", " + to_wstring(lng) + L" " + airbaseName + L")");
command = dynamic_cast<Command*>(new SpawnAircraftCommand(coalition, type, loc, payloadName, airbaseName));
command = dynamic_cast<Command*>(new SpawnAircraft(coalition, type, loc, payloadName, airbaseName));
}
else if (key.compare(L"attackUnit") == 0)
{
@ -129,72 +129,90 @@ void Scheduler::handleRequest(wstring key, json::value value)
wstring targetName;
if (unit != nullptr)
{
unitName = unit->getUnitName();
}
else
{
return;
}
if (target != nullptr)
{
targetName = target->getUnitName();
}
else
{
return;
}
log(L"Unit " + unitName + L" attacking unit " + targetName);
unit->setTarget(targetID);
unit->setState(State::ATTACK);
}
else if (key.compare(L"stopAttack") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsFactory->getUnit(ID);
if (unit != nullptr)
unit->setState(State::REACH_DESTINATION);
else
return;
}
else if (key.compare(L"changeSpeed") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsFactory->getUnit(ID);
if (unit != nullptr)
{
unit->changeSpeed(value[L"change"].as_string());
}
}
else if (key.compare(L"changeAltitude") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsFactory->getUnit(ID);
if (unit != nullptr)
{
unit->changeAltitude(value[L"change"].as_string());
}
}
else if (key.compare(L"setSpeed") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsFactory->getUnit(ID);
if (unit != nullptr)
unit->setTargetSpeed(value[L"speed"].as_double());
}
else if (key.compare(L"setAltitude") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsFactory->getUnit(ID);
if (unit != nullptr)
unit->setTargetAltitude(value[L"altitude"].as_double());
}
else if (key.compare(L"cloneUnit") == 0)
{
int ID = value[L"ID"].as_integer();
command = dynamic_cast<Command*>(new CloneCommand(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;
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 = unitsFactory->getUnit(ID);
json::value wingmenIDs = value[L"wingmenIDs"];
vector<Unit*> wingmen;
if (unit != nullptr)
bool isLeader = value[L"isLeader"].as_bool();
if (isLeader)
{
for (auto itr = wingmenIDs.as_array().begin(); itr != wingmenIDs.as_array().end(); itr++)
json::value wingmenIDs = value[L"wingmenIDs"];
vector<Unit*> wingmen;
if (unit != nullptr)
{
Unit* wingman = unitsFactory->getUnit(itr->as_integer());
if (wingman != nullptr)
for (auto itr = wingmenIDs.as_array().begin(); itr != wingmenIDs.as_array().end(); itr++)
{
wingman->setWingman(true);
wingmen.push_back(wingman);
log(L"Setting " + wingman->getName() + L" as wingman leader");
Unit* wingman = unitsFactory->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");
}
unit->setWingmen(wingmen);
unit->setLeader(true);
unit->resetActiveDestination();
log(L"Setting " + unit->getName() + L" as formation leader");
}
else {
unit->setIsLeader(false);
}
}
else if (key.compare(L"setFormation") == 0)
@ -204,6 +222,34 @@ void Scheduler::handleRequest(wstring key, json::value value)
wstring formation = value[L"formation"].as_string();
unit->setFormation(formation);
}
else if (key.compare(L"setROE") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsFactory->getUnit(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 = unitsFactory->getUnit(ID);
wstring reactionToThreat = value[L"reactionToThreat"].as_string();
unit->setReactionToThreat(reactionToThreat);
}
else if (key.compare(L"landAt") == 0)
{
int ID = value[L"ID"].as_integer();
Unit* unit = unitsFactory->getUnit(ID);
double lat = value[L"location"][L"lat"].as_double();
double lng = value[L"location"][L"lng"].as_double();
Coords loc; loc.lat = lat; loc.lng = lng;
unit->landAt(loc);
}
else if (key.compare(L"deleteUnit") == 0)
{
int ID = value[L"ID"].as_integer();
unitsFactory->deleteUnit(ID);
}
else
{
log(L"Unknown command: " + key);

View File

@ -12,8 +12,6 @@ using namespace GeographicLib;
extern Scheduler* scheduler;
extern UnitsFactory* unitsFactory;
const Geodesic& geod = Geodesic::WGS84();
Unit::Unit(json::value json, int ID) :
ID(ID)
{
@ -34,7 +32,7 @@ void Unit::updateExportData(json::value json)
if (oldPosition != NULL)
{
double dist = 0;
geod.Inverse(latitude, longitude, oldPosition.lat, oldPosition.lng, dist);
Geodesic::WGS84().Inverse(latitude, longitude, oldPosition.lat, oldPosition.lng, dist);
speed = speed * 0.95 + (dist / UPDATE_TIME_INTERVAL) * 0.05;
}
oldPosition = Coords(latitude, longitude, altitude);
@ -79,97 +77,17 @@ void Unit::updateExportData(json::value json)
void Unit::updateMissionData(json::value json)
{
/* Lock for thread safety */
lock_guard<mutex> guard(mutexLock);
if (json.has_number_field(L"fuel"))
fuel = json[L"fuel"].as_number().to_int32();
if (json.has_object_field(L"ammo"))
ammo = json[L"ammo"];
if (json.has_object_field(L"targets"))
targets = json[L"targets"];
}
void Unit::setPath(list<Coords> path)
{
activePath = path;
holding = false;
}
void Unit::setTarget(int newTargetID)
{
targetID = newTargetID;
resetActiveDestination();
}
wstring Unit::getTarget()
{
if (targetID == NULL)
{
return L"";
}
Unit* target = unitsFactory->getUnit(targetID);
if (target != nullptr)
{
if (target->alive)
{
return target->getUnitName();
}
else
{
targetID = NULL;
return L"";
}
}
else
{
targetID = NULL;
return L"";
}
}
void Unit::AIloop()
{
// For wingman units, the leader decides the active destination
if (!wingman)
{
/* Set the active destination to be always equal to the first point of the active path. This is in common with all AI units */
if (activePath.size() > 0)
{
if (activeDestination != activePath.front())
{
activeDestination = activePath.front();
Command* command = dynamic_cast<Command*>(new MoveCommand(ID, activeDestination, getTargetSpeed(), getTargetAltitude(), getCategory(), taskOptions));
scheduler->appendCommand(command);
if (leader)
{
for (auto itr = wingmen.begin(); itr != wingmen.end(); itr++)
{
// Manually set the path and the active destination of the wingmen
(*itr)->setPath(activePath);
(*itr)->setActiveDestination(activeDestination);
Command* command = dynamic_cast<Command*>(new FollowCommand(ID, (*itr)->getID()));
scheduler->appendCommand(command);
}
}
}
}
else
{
if (activeDestination != NULL)
{
log(unitName + L" no more points in active path");
activeDestination = Coords(0); // Set the active path to NULL
currentTask = L"Idle";
}
}
}
}
/* This function reset the activation so that the AI lopp will call again the MoveCommand. This is useful to change speed and altitude, for example */
void Unit::resetActiveDestination()
{
log(unitName + L" resetting active destination");
activeDestination = Coords(0);
if (json.has_boolean_field(L"hasTask"))
hasTask = json[L"hasTask"].as_bool();
}
json::value Unit::json()
@ -194,18 +112,24 @@ json::value Unit::json()
json[L"flags"] = flags;
json[L"category"] = json::value::string(getCategory());
json[L"currentTask"] = json::value::string(getCurrentTask());
json[L"leader"] = leader;
json[L"wingman"] = wingman;
json[L"isLeader"] = isLeader;
json[L"isWingman"] = isWingman;
json[L"formation"] = json::value::string(formation);
json[L"fuel"] = fuel;
json[L"ammo"] = ammo;
json[L"targets"] = targets;
json[L"targetSpeed"] = getTargetSpeed();
json[L"targetAltitude"] = getTargetAltitude();
json[L"hasTask"] = hasTask;
json[L"ROE"] = json::value::string(ROE);
json[L"reactionToThreat"] = json::value::string(reactionToThreat);
int i = 0;
for (auto itr = wingmen.begin(); itr != wingmen.end(); itr++)
{
json[L"wingmenIDs"][i++] = (*itr)->getID();
}
if (leader != nullptr)
json[L"leaderID"] = leader->getID();
/* Send the active path as a json object */
if (activePath.size() > 0) {
@ -225,239 +149,122 @@ json::value Unit::json()
return json;
}
/* Air unit */
AirUnit::AirUnit(json::value json, int ID) : Unit(json, ID)
void Unit::setPath(list<Coords> path)
{
};
void AirUnit::AIloop()
{
if (targetID != 0)
if (state != State::WINGMAN && state != State::FOLLOW)
{
std::wostringstream taskOptionsSS;
taskOptionsSS << "{"
<< "id = 'EngageUnit'" << ","
<< "targetID = " << targetID << ","
<< "}";
taskOptions = taskOptionsSS.str();
currentTask = L"Attacking " + getTarget();
activePath = path;
resetActiveDestination();
}
}
void Unit::setTarget(int newTargetID)
{
targetID = newTargetID;
}
wstring Unit::getTarget()
{
if (isTargetAlive())
{
Unit* target = unitsFactory->getUnit(targetID);
if (target != nullptr)
return target->getUnitName();
}
return L"";
}
bool Unit::isTargetAlive()
{
if (targetID == NULL)
return false;
Unit* target = unitsFactory->getUnit(targetID);
if (target != nullptr)
return target->alive;
else
{
currentTask = L"Reaching destination";
}
return false;
}
/* Call the common AI loop */
Unit::AIloop();
/* This function reset the activation so that the AI lopp will call again the MoveCommand. This is useful to change speed and altitude, for example */
void Unit::resetActiveDestination()
{
activeDestination = Coords(NULL);
}
/* Air unit AI Loop */
if (activeDestination != NULL)
{
double newDist = 0;
geod.Inverse(latitude, longitude, activeDestination.lat, activeDestination.lng, newDist);
if (newDist < AIR_DEST_DIST_THR)
void Unit::resetTask()
{
Command* command = dynamic_cast<Command*>(new ResetTask(ID));
scheduler->appendCommand(command);
}
void Unit::setIsLeader(bool newIsLeader) {
isLeader = newIsLeader;
if (!isLeader) {
for (auto wingman : wingmen)
{
/* Destination reached */
if (holding || looping)
{
activePath.push_back(activePath.front());
}
activePath.pop_front();
log(name + L" destination reached");
wingman->setFormation(L"");
wingman->setIsWingman(false);
wingman->setLeader(nullptr);
}
}
}
void Unit::setIsWingman(bool newIsWingman)
{
isWingman = newIsWingman;
if (isWingman)
setState(State::WINGMAN);
else
{
/* Air units must ALWAYS have a destination or they will RTB and may become uncontrollable */
Coords point1;
Coords point2;
Coords point3;
geod.Direct(latitude, longitude, 45, 10000, point1.lat, point1.lng);
geod.Direct(point1.lat, point1.lng, 135, 10000, point2.lat, point2.lng);
geod.Direct(point2.lat, point2.lng, 225, 10000, point3.lat, point3.lng);
activePath.push_back(point1);
activePath.push_back(point2);
activePath.push_back(point3);
activePath.push_back(Coords(latitude, longitude));
holding = true;
currentTask = L"Holding";
}
setState(State::IDLE);
}
/* Aircraft */
Aircraft::Aircraft(json::value json, int ID) : AirUnit(json, ID)
void Unit::setFormationOffset(Offset newFormationOffset)
{
log("New Aircraft created with ID: " + to_string(ID));
};
void Aircraft::changeSpeed(wstring change)
{
if (change.compare(L"stop") == 0)
{
/* Air units can't hold a position, so we can only set them to hold. At the moment, this will erase any other command. TODO: helicopters should be able to hover in place */
activePath.clear();
}
else if (change.compare(L"slow") == 0)
{
targetSpeed *= 0.9;
resetActiveDestination();
}
else if (change.compare(L"fast") == 0)
{
targetSpeed *= 1.1;
resetActiveDestination();
}
formationOffset = newFormationOffset;
resetTask();
}
void Aircraft::changeAltitude(wstring change)
{
if (change.compare(L"descend") == 0)
{
targetAltitude *= 0.9;
}
else if (change.compare(L"climb") == 0)
{
targetAltitude *= 1.1;
}
resetActiveDestination();
void Unit::setROE(wstring newROE) {
ROE = newROE;
int ROEEnum;
if (newROE.compare(L"Free") == 0)
ROEEnum = ROE::WEAPON_FREE;
else if (newROE.compare(L"Designated free") == 0)
ROEEnum = ROE::OPEN_FIRE_WEAPON_FREE;
else if (newROE.compare(L"Designated") == 0)
ROEEnum = ROE::OPEN_FIRE;
else if (newROE.compare(L"Return") == 0)
ROEEnum = ROE::RETURN_FIRE;
else if (newROE.compare(L"Hold") == 0)
ROEEnum = ROE::WEAPON_HOLD;
else
return;
Command* command = dynamic_cast<Command*>(new SetOption(ID, SetCommandType::ROE, ROEEnum));
scheduler->appendCommand(command);
}
/* Helicopter */
Helicopter::Helicopter(json::value json, int ID) : AirUnit(json, ID)
{
log("New Helicopter created with ID: " + to_string(ID));
};
void Helicopter::changeSpeed(wstring change)
{
if (change.compare(L"stop") == 0)
{
/* Air units can't hold a position, so we can only set them to hold. At the moment, this will erase any other command. TODO: helicopters should be able to hover in place */
activePath.clear();
}
else if (change.compare(L"slow") == 0)
{
targetSpeed *= 0.9;
resetActiveDestination();
}
else if (change.compare(L"fast") == 0)
{
targetSpeed *= 1.1;
resetActiveDestination();
}
void Unit::setReactionToThreat(wstring newReactionToThreat) {
reactionToThreat = newReactionToThreat;
int reactionToThreatEnum;
if (newReactionToThreat.compare(L"None") == 0)
reactionToThreatEnum = ReactionToThreat::NO_REACTION;
else if (newReactionToThreat.compare(L"Passive") == 0)
reactionToThreatEnum = ReactionToThreat::PASSIVE_DEFENCE;
else if (newReactionToThreat.compare(L"Evade") == 0)
reactionToThreatEnum = ReactionToThreat::EVADE_FIRE;
else if (newReactionToThreat.compare(L"Escape") == 0)
reactionToThreatEnum = ReactionToThreat::BYPASS_AND_ESCAPE;
else if (newReactionToThreat.compare(L"Abort") == 0)
reactionToThreatEnum = ReactionToThreat::ALLOW_ABORT_MISSION;
else
return;
Command* command = dynamic_cast<Command*>(new SetOption(ID, SetCommandType::REACTION_ON_THREAT, reactionToThreatEnum));
scheduler->appendCommand(command);
}
void Helicopter::changeAltitude(wstring change)
{
if (change.compare(L"descend") == 0)
{
targetAltitude *= 0.9;
}
else if (change.compare(L"climb") == 0)
{
targetAltitude *= 1.1;
}
resetActiveDestination();
}
/* Ground unit */
GroundUnit::GroundUnit(json::value json, int ID) : Unit(json, ID)
{
log("New Ground Unit created with ID: " + to_string(ID));
};
void GroundUnit::AIloop()
{
/* Call the common AI loop */
Unit::AIloop();
/* Ground unit AI Loop */
if (activeDestination != NULL)
{
double newDist = 0;
geod.Inverse(latitude, longitude, activeDestination.lat, activeDestination.lng, newDist);
if (newDist < GROUND_DEST_DIST_THR)
{
/* Destination reached */
activePath.pop_front();
log(unitName + L" destination reached");
}
}
}
void GroundUnit::changeSpeed(wstring change)
{
if (change.compare(L"stop") == 0)
{
}
else if (change.compare(L"slow") == 0)
{
}
else if (change.compare(L"fast") == 0)
{
}
}
/* Navy Unit */
NavyUnit::NavyUnit(json::value json, int ID) : Unit(json, ID)
{
log("New Navy Unit created with ID: " + to_string(ID));
};
void NavyUnit::AIloop()
{
/* Call the common AI loop */
Unit::AIloop();
/* Navy unit AI Loop */
if (activeDestination != NULL)
{
double newDist = 0;
geod.Inverse(latitude, longitude, activeDestination.lat, activeDestination.lng, newDist);
if (newDist < GROUND_DEST_DIST_THR)
{
/* Destination reached */
activePath.pop_front();
log(unitName + L" destination reached");
}
}
}
void NavyUnit::changeSpeed(wstring change)
{
if (change.compare(L"stop") == 0)
{
}
else if (change.compare(L"slow") == 0)
{
}
else if (change.compare(L"fast") == 0)
{
}
}
/* Weapon */
Weapon::Weapon(json::value json, int ID) : Unit(json, ID)
{
};
/* Missile */
Missile::Missile(json::value json, int ID) : Weapon(json, ID)
{
log("New Missile created with ID: " + to_string(ID));
};
/* Bomb */
Bomb::Bomb(json::value json, int ID) : Weapon(json, ID)
{
log("New Bomb created with ID: " + to_string(ID));
};
void Unit::landAt(Coords loc) {
activePath.clear();
activePath.push_back(loc);
setState(State::LAND);
}

68
src/core/src/aircraft.cpp Normal file
View File

@ -0,0 +1,68 @@
#include "aircraft.h"
#include "utils.h"
#include "logger.h"
#include "commands.h"
#include "scheduler.h"
#include "defines.h"
#include "unitsFactory.h"
#include <GeographicLib/Geodesic.hpp>
using namespace GeographicLib;
extern Scheduler* scheduler;
extern UnitsFactory* unitsFactory;
/* Aircraft */
Aircraft::Aircraft(json::value json, int ID) : AirUnit(json, ID)
{
log("New Aircraft created with ID: " + to_string(ID));
};
void Aircraft::changeSpeed(wstring change)
{
if (change.compare(L"stop") == 0)
{
setState(State::IDLE);
}
else if (change.compare(L"slow") == 0)
targetSpeed -= 25 / 1.94384;
else if (change.compare(L"fast") == 0)
targetSpeed += 25 / 1.94384;
if (targetSpeed < 50 / 1.94384)
targetSpeed = 50 / 1.94384;
goToDestination(); /* Send the command to reach the destination */
}
void Aircraft::changeAltitude(wstring change)
{
if (change.compare(L"descend") == 0)
{
if (targetAltitude > 5000)
targetAltitude -= 2500 / 3.28084;
else if (targetAltitude > 0)
targetAltitude -= 500 / 3.28084;
}
else if (change.compare(L"climb") == 0)
{
if (targetAltitude > 5000)
targetAltitude += 2500 / 3.28084;
else if (targetAltitude >= 0)
targetAltitude += 500 / 3.28084;
}
if (targetAltitude < 0)
targetAltitude = 0;
goToDestination(); /* Send the command to reach the destination */
}
void Aircraft::setTargetSpeed(double newTargetSpeed) {
targetSpeed = newTargetSpeed;
goToDestination();
}
void Aircraft::setTargetAltitude(double newTargetAltitude) {
targetAltitude = newTargetAltitude;
goToDestination();
}

344
src/core/src/airunit.cpp Normal file
View File

@ -0,0 +1,344 @@
#include "airunit.h"
#include "utils.h"
#include "logger.h"
#include "commands.h"
#include "scheduler.h"
#include "defines.h"
#include "unitsFactory.h"
#include <GeographicLib/Geodesic.hpp>
using namespace GeographicLib;
extern Scheduler* scheduler;
extern UnitsFactory* unitsFactory;
/* Air unit */
AirUnit::AirUnit(json::value json, int ID) : Unit(json, ID)
{
};
void AirUnit::setState(int newState)
{
if (state != newState)
{
switch (state) {
case State::IDLE: {
break;
}
case State::REACH_DESTINATION: {
break;
}
case State::ATTACK: {
setTarget(NULL);
break;
}
case State::FOLLOW: {
break;
}
case State::WINGMAN: {
if (isWingman)
return;
break;
}
case State::LAND: {
break;
}
default:
break;
}
switch (newState) {
case State::IDLE: {
resetActiveDestination();
break;
}
case State::REACH_DESTINATION: {
resetActiveDestination();
break;
}
case State::ATTACK: {
if (isTargetAlive()) {
Unit* target = unitsFactory->getUnit(targetID);
Coords targetPosition = Coords(target->getLatitude(), target->getLongitude(), 0);
activePath.clear();
activePath.push_front(targetPosition);
resetActiveDestination();
}
break;
}
case State::FOLLOW: {
resetActiveDestination();
break;
}
case State::WINGMAN: {
resetActiveDestination();
break;
}
case State::LAND: {
resetActiveDestination();
break;
}
default:
break;
}
resetTask();
log(unitName + L" setting state from " + to_wstring(state) + L" to " + to_wstring(newState));
state = newState;
}
}
bool AirUnit::isDestinationReached()
{
if (activeDestination != NULL)
{
double dist = 0;
Geodesic::WGS84().Inverse(latitude, longitude, activeDestination.lat, activeDestination.lng, dist);
if (dist < AIR_DEST_DIST_THR)
{
log(unitName + L" destination reached");
return true;
}
else {
return false;
}
}
else
return true;
}
bool AirUnit::setActiveDestination()
{
if (activePath.size() > 0)
{
activeDestination = activePath.front();
log(unitName + L" active destination set to queue front");
return true;
}
else
{
activeDestination = Coords(0);
log(unitName + L" active destination set to NULL");
return false;
}
}
void AirUnit::createHoldingPattern()
{
/* Air units must ALWAYS have a destination or they will RTB and become uncontrollable */
activePath.clear();
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);
activePath.push_back(point1);
activePath.push_back(point2);
activePath.push_back(point3);
activePath.push_back(Coords(latitude, longitude));
log(unitName + L" holding pattern created");
}
bool AirUnit::updateActivePath(bool looping)
{
if (activePath.size() > 0)
{
/* Push the next destination in the queue to the front */
if (looping)
activePath.push_back(activePath.front());
activePath.pop_front();
log(unitName + L" active path front popped");
return true;
}
else {
return false;
}
}
void AirUnit::goToDestination(wstring enrouteTask)
{
if (activeDestination != NULL)
{
Command* command = dynamic_cast<Command*>(new Move(ID, activeDestination, getTargetSpeed(), getTargetAltitude(), getCategory(), enrouteTask));
scheduler->appendCommand(command);
hasTask = true;
}
else
log(unitName + L" error, no active destination!");
}
void AirUnit::taskWingmen()
{
switch (state) {
case State::IDLE:
case State::REACH_DESTINATION:
case State::ATTACK:{
int idx = 1;
for (auto const& wingman : wingmen)
{
if (!wingman->getIsWingman())
{
wingman->setIsWingman(true);
wingman->setLeader(this);
}
if (wingman->getFormation().compare(formation) != 0)
{
wingman->resetTask();
wingman->setFormation(formation);
if (formation.compare(L"Line abreast") == 0)
wingman->setFormationOffset(Offset(0 * idx, 0 * idx, 1852 * idx));
idx++;
}
}
break;
}
default:
break;
}
}
void AirUnit::AIloop()
{
/* State machine */
switch (state) {
case State::IDLE: {
wstring enrouteTask = L"nil";
currentTask = L"Idle";
if (activeDestination == NULL || !hasTask)
{
createHoldingPattern();
setActiveDestination();
goToDestination(enrouteTask);
}
else {
if (isDestinationReached() && updateActivePath(true) && setActiveDestination())
goToDestination(enrouteTask);
}
if (isLeader)
taskWingmen();
break;
}
case State::REACH_DESTINATION: {
wstring enrouteTask = L"nil";
currentTask = L"Reaching destination";
if (activeDestination == NULL || !hasTask)
{
setActiveDestination();
goToDestination(enrouteTask);
}
else {
if (isDestinationReached()) {
if (updateActivePath(false) && setActiveDestination())
goToDestination(enrouteTask);
else {
setState(State::IDLE);
break;
}
}
}
if (isLeader)
taskWingmen();
break;
}
case State::LAND: {
wstring enrouteTask = L"{" "id = 'land' }";
currentTask = L"Landing";
if (activeDestination == NULL)
{
setActiveDestination();
goToDestination(enrouteTask);
}
if (isLeader)
taskWingmen();
break;
}
case State::ATTACK: {
/* If the target is not alive (either not set or was succesfully destroyed) go back to REACH_DESTINATION */
if (!isTargetAlive()) {
setState(State::REACH_DESTINATION);
break;
}
/* Attack state is an "enroute" task, meaning the unit will keep trying to attack even if a new destination is set. This is useful to
manoeuvre the unit so that it can detect and engage the target. */
std::wostringstream enrouteTaskSS;
enrouteTaskSS << "{"
<< "id = 'EngageUnit'" << ","
<< "targetID = " << targetID << ","
<< "}";
wstring enrouteTask = enrouteTaskSS.str();
currentTask = L"Attacking " + getTarget();
if (activeDestination == NULL || !hasTask)
{
setActiveDestination();
goToDestination(enrouteTask);
}
else {
if (isDestinationReached()) {
if (updateActivePath(false) && setActiveDestination())
goToDestination(enrouteTask);
else {
setState(State::IDLE);
break;
}
}
}
if (isLeader)
taskWingmen();
break;
}
case State::FOLLOW: {
/* TODO */
setState(State::IDLE);
break;
}
case State::WINGMAN: {
/* In the WINGMAN state, the unit relinquishes control to the leader */
activePath.clear();
activeDestination = Coords(NULL);
if (leader == nullptr || !leader->getAlive())
{
this->setFormation(L"");
this->setIsWingman(false);
break;
}
if (!hasTask) {
if (leader != nullptr && leader->getAlive() && formationOffset != NULL)
{
std::wostringstream taskSS;
taskSS << "{"
<< "id = 'FollowUnit'" << ", "
<< "leaderID = " << leader->getID() << ","
<< "offset = {"
<< "x = " << formationOffset.x << ","
<< "y = " << formationOffset.y << ","
<< "z = " << formationOffset.z
<< "},"
<< "}";
Command* command = dynamic_cast<Command*>(new SetTask(ID, taskSS.str()));
scheduler->appendCommand(command);
hasTask = true;
}
}
break;
}
default:
break;
}
}

View File

@ -12,6 +12,8 @@ UnitsFactory* unitsFactory = nullptr;
Server* server = nullptr;
Scheduler* scheduler = nullptr;
json::value airbasesData;
json::value bullseyeData;
/* Called when DCS simulation stops. All singleton instances are deleted. */
extern "C" DllExport int coreDeinit(lua_State* L)
@ -71,40 +73,8 @@ extern "C" DllExport int coreMissionData(lua_State * L)
unitsFactory->updateMissionData(missionData[L"unitsData"]);
if (missionData.has_object_field(L"airbases"))
airbasesData = missionData[L"airbases"];
if (missionData.has_object_field(L"bullseye"))
bullseyeData = missionData[L"bullseye"];
return(0);
}
BOOL WINAPI DllMain(
HINSTANCE hinstDLL, // handle to DLL module
DWORD fdwReason, // reason for calling function
LPVOID lpvReserved) // reserved
{
// Perform actions based on the reason for calling.
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
// Initialize once for each new process.
// Return FALSE to fail DLL load.
break;
case DLL_THREAD_ATTACH:
// Do thread-specific initialization.
break;
case DLL_THREAD_DETACH:
// Do thread-specific cleanup.
break;
case DLL_PROCESS_DETACH:
if (lpvReserved != nullptr)
{
break; // do not do cleanup if process termination scenario
}
// Perform any necessary cleanup.
break;
}
return TRUE; // Successful DLL_PROCESS_ATTACH.
}

View File

@ -0,0 +1,71 @@
#include "groundunit.h"
#include "utils.h"
#include "logger.h"
#include "commands.h"
#include "scheduler.h"
#include "defines.h"
#include "unitsFactory.h"
#include <GeographicLib/Geodesic.hpp>
using namespace GeographicLib;
extern Scheduler* scheduler;
extern UnitsFactory* unitsFactory;
/* Ground unit */
GroundUnit::GroundUnit(json::value json, int ID) : Unit(json, ID)
{
log("New Ground Unit created with ID: " + to_string(ID));
};
void GroundUnit::AIloop()
{
/* Set the active destination to be always equal to the first point of the active path. This is in common with all AI units */
if (activePath.size() > 0)
{
if (activeDestination != activePath.front())
{
activeDestination = activePath.front();
Command* command = dynamic_cast<Command*>(new Move(ID, activeDestination, getTargetSpeed(), getTargetAltitude(), getCategory(), L"nil"));
scheduler->appendCommand(command);
}
}
else
{
if (activeDestination != NULL)
{
log(unitName + L" no more points in active path");
activeDestination = Coords(0); // Set the active path to NULL
currentTask = L"Idle";
}
}
/* Ground unit AI Loop */
if (activeDestination != NULL)
{
double newDist = 0;
Geodesic::WGS84().Inverse(latitude, longitude, activeDestination.lat, activeDestination.lng, newDist);
if (newDist < GROUND_DEST_DIST_THR)
{
/* Destination reached */
activePath.pop_front();
log(unitName + L" destination reached");
}
}
}
void GroundUnit::changeSpeed(wstring change)
{
if (change.compare(L"stop") == 0)
{
}
else if (change.compare(L"slow") == 0)
{
}
else if (change.compare(L"fast") == 0)
{
}
}

View File

@ -0,0 +1,69 @@
#include "helicopter.h"
#include "utils.h"
#include "logger.h"
#include "commands.h"
#include "scheduler.h"
#include "defines.h"
#include "unitsFactory.h"
#include <GeographicLib/Geodesic.hpp>
using namespace GeographicLib;
extern Scheduler* scheduler;
extern UnitsFactory* unitsFactory;
/* Helicopter */
Helicopter::Helicopter(json::value json, int ID) : AirUnit(json, ID)
{
log("New Helicopter created with ID: " + to_string(ID));
};
void Helicopter::changeSpeed(wstring change)
{
if (change.compare(L"stop") == 0)
{
/* Air units can't hold a position, so we can only set them to hold. At the moment, this will erase any other command. TODO: helicopters should be able to hover in place */
activePath.clear();
}
else if (change.compare(L"slow") == 0)
targetSpeed -= 10 / 1.94384;
else if (change.compare(L"fast") == 0)
targetSpeed += 10 / 1.94384;
if (targetSpeed < 0)
targetSpeed = 0;
goToDestination(); /* Send the command to reach the destination */
}
void Helicopter::changeAltitude(wstring change)
{
if (change.compare(L"descend") == 0)
{
if (targetAltitude > 100)
targetAltitude -= 100 / 3.28084;
else if (targetAltitude > 0)
targetAltitude -= 10 / 3.28084;
}
else if (change.compare(L"climb") == 0)
{
if (targetAltitude > 100)
targetAltitude += 100 / 3.28084;
else if (targetAltitude >= 0)
targetAltitude += 10 / 3.28084;
}
if (targetAltitude < 0)
targetAltitude = 0;
goToDestination(); /* Send the command to reach the destination */
}
void Helicopter::setTargetSpeed(double newTargetSpeed) {
targetSpeed = newTargetSpeed;
goToDestination();
}
void Helicopter::setTargetAltitude(double newTargetAltitude) {
targetAltitude = newTargetAltitude;
goToDestination();
}

40
src/core/src/navyunit.cpp Normal file
View File

@ -0,0 +1,40 @@
#include "navyunit.h"
#include "utils.h"
#include "logger.h"
#include "commands.h"
#include "scheduler.h"
#include "defines.h"
#include "unitsFactory.h"
#include <GeographicLib/Geodesic.hpp>
using namespace GeographicLib;
extern Scheduler* scheduler;
extern UnitsFactory* unitsFactory;
/* Navy Unit */
NavyUnit::NavyUnit(json::value json, int ID) : Unit(json, ID)
{
log("New Navy Unit created with ID: " + to_string(ID));
};
void NavyUnit::AIloop()
{
/* TODO */
}
void NavyUnit::changeSpeed(wstring change)
{
if (change.compare(L"stop") == 0)
{
}
else if (change.compare(L"slow") == 0)
{
}
else if (change.compare(L"fast") == 0)
{
}
}

View File

@ -10,6 +10,7 @@
extern UnitsFactory* unitsFactory;
extern Scheduler* scheduler;
extern json::value airbasesData;
extern json::value bullseyeData;
void handle_eptr(std::exception_ptr eptr)
{
@ -59,6 +60,7 @@ void Server::handle_get(http_request request)
try {
unitsFactory->updateAnswer(answer);
answer[L"airbases"] = airbasesData;
answer[L"bullseye"] = bullseyeData;
response.set_body(answer);
}
catch (...) {

View File

@ -2,9 +2,15 @@
#include "unitsFactory.h"
#include "logger.h"
#include "unit.h"
#include "utils.h"
#include "aircraft.h"
#include "helicopter.h"
#include "groundunit.h"
#include "navyunit.h"
#include "weapon.h"
#include "commands.h"
#include "scheduler.h"
extern Scheduler* scheduler;
UnitsFactory::UnitsFactory(lua_State* L)
{
@ -114,3 +120,12 @@ void UnitsFactory::updateAnswer(json::value& answer)
answer[L"units"] = unitsJson;
}
void UnitsFactory::deleteUnit(int ID)
{
if (getUnit(ID) != nullptr)
{
Command* command = dynamic_cast<Command*>(new Delete(ID));
scheduler->appendCommand(command);
}
}

31
src/core/src/weapon.cpp Normal file
View File

@ -0,0 +1,31 @@
#include "weapon.h"
#include "utils.h"
#include "logger.h"
#include "commands.h"
#include "scheduler.h"
#include "defines.h"
#include "unitsFactory.h"
#include <GeographicLib/Geodesic.hpp>
using namespace GeographicLib;
extern Scheduler* scheduler;
extern UnitsFactory* unitsFactory;
/* Weapon */
Weapon::Weapon(json::value json, int ID) : Unit(json, ID)
{
};
/* Missile */
Missile::Missile(json::value json, int ID) : Weapon(json, ID)
{
log("New Missile created with ID: " + to_string(ID));
};
/* Bomb */
Bomb::Bomb(json::value json, int ID) : Weapon(json, ID)
{
log("New Bomb created with ID: " + to_string(ID));
};

View File

@ -7,6 +7,12 @@ struct Coords {
double alt = 0;
};
struct Offset {
double x = 0;
double y = 0;
double z = 0;
};
// Get current date/time, format is YYYY-MM-DD.HH:mm:ss
const DllExport std::string CurrentDateTime();
std::wstring DllExport to_wstring(const std::string& str);
@ -16,3 +22,8 @@ bool DllExport operator== (const Coords& a, const Coords& b);
bool DllExport operator!= (const Coords& a, const Coords& b);
bool DllExport operator== (const Coords& a, const int& b);
bool DllExport operator!= (const Coords& a, const int& b);
bool DllExport operator== (const Offset& a, const Offset& b);
bool DllExport operator!= (const Offset& a, const Offset& b);
bool DllExport operator== (const Offset& a, const int& b);
bool DllExport operator!= (const Offset& a, const int& b);

View File

@ -42,3 +42,8 @@ bool operator== (const Coords& a, const Coords& b) { return a.lat == b.lat && a.
bool operator!= (const Coords& a, const Coords& b) { return !(a == b); }
bool operator== (const Coords& a, const int& b) { return a.lat == b && a.lng == b && a.alt == b; }
bool operator!= (const Coords& a, const int& b) { return !(a == b); }
bool operator== (const Offset& a, const Offset& b) { return a.x == b.x && a.y == b.y && a.z == b.z; }
bool operator!= (const Offset& a, const Offset& b) { return !(a == b); }
bool operator== (const Offset& a, const int& b) { return a.x == b && a.y == b && a.z == b; }
bool operator!= (const Offset& a, const int& b) { return !(a == b); }