53 Commits

Author SHA1 Message Date
Pax1601
bd894704b1 v0.4.4 2023-09-10 17:26:33 +02:00
Pax1601
7af2162b50 Merge pull request #379 from Pax1601/368-spawn-points-can-integer-overflow-on-the-server-side
Switched from unsigned int to int for spawn points
2023-09-09 19:15:34 +02:00
Pax1601
c735d108d4 Merge pull request #378 from Pax1601/refactor
Refactor
2023-09-09 19:08:31 +02:00
Pax1601
6a2ffc936e More bugfixing, set CAS and ASL to be default modes 2023-09-09 19:07:09 +02:00
Pax1601
74d5480587 Minor refactor and bug fixing 2023-09-08 22:41:37 +02:00
Pax1601
89c39c7038 Code documentation and refactoring 2023-09-08 17:40:53 +02:00
Pax1601
61f955cfeb Added copy ability in RTS mode 2023-09-08 16:25:12 +02:00
Pax1601
744adee94c Merge branch 'main' into 345-allow-for-copy-and-paste-in-rts-mode 2023-09-07 16:56:55 +02:00
Pax1601
267c0b037d Merge branch 'main' into 345-allow-for-copy-and-paste-in-rts-mode 2023-09-07 13:03:50 +02:00
Pax1601
860414b7b2 Merge pull request #377 from Pax1601/370-write-better-method-of-determining-if-a-command-has-been-completed
370 write better method of determining if a command has been completed
2023-09-07 13:03:05 +02:00
Pax1601
09bf361d44 Added ability to delete original cloned units
Mostly useful to group units together
2023-09-07 13:02:14 +02:00
Pax1601
e2f80c5788 Fixed table deepcopy 2023-09-06 20:19:47 +02:00
Pax1601
a96a6eb57d Added command hash control 2023-09-05 17:25:32 +02:00
Pax1601
71a141e0b8 Merge pull request #373 from Pax1601/372-pan-up-doesnt-work-via-keyboard
Pan up now pans
2023-09-05 09:42:55 +02:00
PeekabooSteam
333169d18c Pan up now pans 2023-09-04 22:57:32 +01:00
Pax1601
cbb878cf96 Fixed errors in lua clone units 2023-09-04 21:44:22 +02:00
Pax1601
d684f91a5a Merge branch '370-write-better-method-of-determining-if-a-command-has-been-completed' into 345-allow-for-copy-and-paste-in-rts-mode 2023-09-04 19:02:10 +02:00
Pax1601
f2de4cd34c Completed implementation 2023-09-04 19:01:50 +02:00
Pax1601
3d076a605b Merge branch '370-write-better-method-of-determining-if-a-command-has-been-completed' into 345-allow-for-copy-and-paste-in-rts-mode 2023-09-04 17:31:58 +02:00
Pax1601
3607f88e18 Converted clone function to pure lua 2023-09-04 17:27:09 +02:00
Pax1601
a0d2bb11ed Switched from unsigned int to int for spawn points 2023-09-04 16:06:41 +02:00
Pax1601
ede245a37d Implemented executed commands provider 2023-09-04 12:41:54 +02:00
Pax1601
aca1e112d2 Merge pull request #371 from Pax1601/369-coalition-colour-incorrectly-set-on-spawn-menu-in-rts-mode
Minor bugfix
2023-09-04 11:00:46 +02:00
Pax1601
bf5d6dac18 Minor bugfix 2023-09-04 11:00:28 +02:00
Pax1601
182ce4da42 Hotfix 2023-09-03 17:55:58 +02:00
Pax1601
41e2e6fa59 Updated documentation 2023-09-03 16:45:48 +02:00
Pax1601
fe0e964a5a Merge pull request #367 from Pax1601/343-add-ability-to-select-unit-nation-and-livery
v0.4.4
2023-09-03 16:34:34 +02:00
Pax1601
bee35f9ee9 v0.4.4 2023-09-03 16:34:11 +02:00
Pax1601
e66a1bb5b4 Merge pull request #366 from Pax1601/343-add-ability-to-select-unit-nation-and-livery
343 add ability to select unit nation and livery
2023-09-03 16:32:06 +02:00
Pax1601
8490997604 Merge branch 'main' into 343-add-ability-to-select-unit-nation-and-livery 2023-09-03 15:18:35 +02:00
Pax1601
6898d1df6d Completed unit nation selection and new spawn menu 2023-09-03 15:15:11 +02:00
Pax1601
a338e5fa26 Added docs folder 2023-09-01 16:44:47 +02:00
Pax1601
695adc8acb More work on units spawn menu and started to add documentation 2023-09-01 16:13:15 +02:00
Pax1601
fab7d26191 More work on scripted spawn menu 2023-08-31 17:25:47 +02:00
Pax1601
3959139dd8 Started conversion to scripted unit spawn menu 2023-08-30 20:01:25 +02:00
Pax1601
1298669f1c Merge pull request #365 from Pax1601/358-right-click-long-press-for-unit-action-context-menu
358 right click long press for unit action context menu
2023-08-30 09:13:13 +02:00
Pax1601
4fe40f5ff7 Merge branch 'main' into 358-right-click-long-press-for-unit-action-context-menu 2023-08-30 09:13:06 +02:00
Pax1601
a10c113c42 Right click long press menu completed 2023-08-30 09:10:42 +02:00
Pax1601
d5f4b5c711 More work on country selection 2023-08-29 19:17:36 +02:00
Pax1601
93707af56b Merge pull request #363 from Pax1601/350-in-rts-mode-helicopters-are-shown-with-a-distinctive-shape-that-allows-to-identify-them
Non visually detected units are now drawn with generic symbol
2023-08-29 14:59:42 +02:00
Pax1601
0ae694c1a8 Non visually detected units are now drawn with generic symbol 2023-08-29 14:59:23 +02:00
Pax1601
0b53fb19b7 Merge pull request #362 from Pax1601/356-add-marker-for-deployed-smoke
Create colored smoke markers
2023-08-29 10:02:50 +02:00
Pax1601
cc386e86b9 Create colored smoke markers 2023-08-29 10:02:18 +02:00
Pax1601
a922d7de31 Merge pull request #361 from Pax1601/349-add-ability-to-hide-units-names
349 add ability to hide units names
2023-08-29 09:39:27 +02:00
Pax1601
8e7d64f0e4 Remove shortcut and added option in dropdown 2023-08-29 09:39:07 +02:00
Pax1601
0bc406538b Merge pull request #360 from Pax1601/351-double-click-on-a-unit-should-select-all-units-of-that-time-on-the-screen
351 double click on a unit should select all units of that time on the screen
2023-08-28 17:14:23 +02:00
Pax1601
9547559e00 Refactoring and merge 2023-08-28 17:14:03 +02:00
Pax1601
70783dc828 Merge branch 'main' into 351-double-click-on-a-unit-should-select-all-units-of-that-time-on-the-screen 2023-08-28 17:05:34 +02:00
Pax1601
85bdd791b8 Implemented long press right click menu 2023-08-28 15:48:46 +02:00
Pax1601
9d7e61556d Added unit country selection 2023-08-27 15:59:50 +02:00
Pax1601
eb80c39b98 Loadouts changes and spawn menu update 2023-08-24 16:16:59 +02:00
PeekabooSteam
07b69fe96f Added dbl-click select but it's buggy AF 2023-08-12 19:31:08 +01:00
PeekabooSteam
e0a3fd1795 Corrected some variable names. 2023-08-12 16:46:37 +01:00
323 changed files with 133888 additions and 12707 deletions

View File

@@ -15,7 +15,7 @@ const DEMO_UNIT_DATA = {
radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 },
generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false },
ammo: [{ quantity: 2, name: "A cool missile\0Ciao", guidance: 0, category: 0, missileCategory: 0 } ],
contacts: [{ID: 2, detectionMethod: 1}, {ID: 3, detectionMethod: 4}],
contacts: [{ID: 2, detectionMethod: 1}, {ID: 3, detectionMethod: 4}, {ID: 5, detectionMethod: 4}],
activePath: [{lat: 38, lng: -115, alt: 0}, {lat: 38, lng: -114, alt: 0}]
},
["2"]:{ category: "Aircraft", alive: true, human: false, controlled: false, coalition: 1, country: 0, name: "FA-18C_hornet", unitName: "Cool guy 1-2", groupName: "Cool group 2", state: 1, task: "Being cool",
@@ -112,6 +112,8 @@ class DemoDataGenerator {
app.get('/demo/bullseyes', (req, res) => this.bullseyes(req, res));
app.get('/demo/airbases', (req, res) => this.airbases(req, res));
app.get('/demo/mission', (req, res) => this.mission(req, res));
app.get('/demo/commands', (req, res) => this.command(req, res));
app.put('/demo', (req, res) => this.put(req, res));
app.use('/demo', basicAuth({
users: {
@@ -417,21 +419,24 @@ class DemoDataGenerator {
ret.mission.coalitions = {
red: [
'Russia',
'China'
'RUSSIA',
'CHINA'
],
blue: [
'United States',
'Great Britain'
'UK',
'USA'
],
neutral: [
'ITALY'
]
}
ret.mission.commandModeOptions = {
restrictSpawns: false,
restrictSpawns: true,
restrictToCoalition: true,
setupTime: 0,
spawnPoints: {
red: 1000,
red: 400,
blue: 400
},
eras: ["WW2", "Early Cold War", "Late Cold War", "Modern"]
@@ -454,7 +459,17 @@ class DemoDataGenerator {
}
res.send(JSON.stringify(ret));
}
command(req, res) {
var ret = {commandExecuted: Math.random() > 0.5};
res.send(JSON.stringify(ret));
}
put(req, res) {
var ret = {commandHash: Math.random().toString(36).slice(2, 19)}
res.send(JSON.stringify(ret));
}
}
module.exports = DemoDataGenerator;

341
client/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "DCSOlympus",
"version": "v0.4.3-alpha",
"version": "v0.4.4-alpha",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "DCSOlympus",
"version": "v0.4.3-alpha",
"version": "v0.4.4-alpha",
"dependencies": {
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
@@ -40,6 +40,8 @@
"nodemon": "^2.0.20",
"sortablejs": "^1.15.0",
"tsify": "^5.0.4",
"typedoc": "^0.24.8",
"typedoc-umlclass": "^0.7.1",
"typescript": "^4.9.4",
"watchify": "^4.0.0"
}
@@ -3614,6 +3616,12 @@
"node": ">=8"
}
},
"node_modules/ansi-sequence-parser": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz",
"integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==",
"dev": true
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -3983,6 +3991,15 @@
"node": ">=8"
}
},
"node_modules/binary-split": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/binary-split/-/binary-split-1.0.5.tgz",
"integrity": "sha512-AQ5fcBrUU5hoIafkEvNKqxT+2xbqlSqAXef6IdCQr5wpHu9E7NGM6rTAlYJYbtxvAvjfx8nJkBy6rNlbPPI+Pw==",
"dev": true,
"dependencies": {
"through2": "^2.0.3"
}
},
"node_modules/bn.js": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
@@ -4726,6 +4743,15 @@
"url": "https://opencollective.com/date-fns"
}
},
"node_modules/dbly-linked-list": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/dbly-linked-list/-/dbly-linked-list-0.3.4.tgz",
"integrity": "sha512-327vOlwspi9i1T3Kc9yZhRUR8qDdgMQ4HmXsFDDCQ/HTc3sNe7gnF5b0UrsnaOJ0rvmG7yBZpK0NoOux9rKYKw==",
"dev": true,
"dependencies": {
"lodash.isequal": "^4.5.0"
}
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -5883,6 +5909,12 @@
"node": ">=6"
}
},
"node_modules/jsonc-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
"integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==",
"dev": true
},
"node_modules/jsonparse": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
@@ -5962,6 +5994,12 @@
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"dev": true
},
"node_modules/lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"dev": true
},
"node_modules/lodash.memoize": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz",
@@ -5989,11 +6027,29 @@
"yallist": "^3.0.2"
}
},
"node_modules/lunr": {
"version": "2.3.9",
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
"dev": true
},
"node_modules/map-stream": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz",
"integrity": "sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ=="
},
"node_modules/marked": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
"integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==",
"dev": true,
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@@ -6477,6 +6533,23 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/plantuml-encoder": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/plantuml-encoder/-/plantuml-encoder-1.4.0.tgz",
"integrity": "sha512-sxMwpDw/ySY1WB2CE3+IdMuEcWibJ72DDOsXLkSmEaSzwEUaYBT6DWgOfBiHGCux4q433X6+OEFWjlVqp7gL6g==",
"dev": true
},
"node_modules/plantuml-pipe": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/plantuml-pipe/-/plantuml-pipe-1.5.0.tgz",
"integrity": "sha512-a4brAspbSlQYDdzLtDulB7ZQOGI5JC3Kyk7cTo8kDrM2gOQcarXkP/fN0mR+gD3srS9BMI06rOQKkvhrFpqYzg==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"binary-split": "^1.0.5",
"split2": "^4.2.0"
}
},
"node_modules/point-in-polygon": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz",
@@ -6507,6 +6580,15 @@
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"dev": true
},
"node_modules/progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"dev": true,
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -6578,6 +6660,15 @@
"node": ">=0.4.x"
}
},
"node_modules/queue-fifo": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/queue-fifo/-/queue-fifo-0.2.6.tgz",
"integrity": "sha512-rwlnZHAaTmWEGKC7ziasK8u4QnZW/uN6kSiG+tHNf/1GA+R32FArZi18s3SYUpKcA0Y6jJoUDn5GT3Anoc2mWw==",
"dev": true,
"dependencies": {
"dbly-linked-list": "0.3.4"
}
},
"node_modules/quickselect": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/quickselect/-/quickselect-1.1.1.tgz",
@@ -6921,6 +7012,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/shiki": {
"version": "0.14.4",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.4.tgz",
"integrity": "sha512-IXCRip2IQzKwxArNNq1S+On4KPML3Yyn8Zzs/xRgcgOWIr8ntIK3IKzjFPfjy/7kt9ZMjc+FItfqHRBg8b6tNQ==",
"dev": true,
"dependencies": {
"ansi-sequence-parser": "^1.1.0",
"jsonc-parser": "^3.2.0",
"vscode-oniguruma": "^1.7.0",
"vscode-textmate": "^8.0.0"
}
},
"node_modules/simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
@@ -7006,6 +7109,15 @@
"node": "*"
}
},
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"dev": true,
"engines": {
"node": ">= 10.x"
}
},
"node_modules/statuses": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
@@ -7392,6 +7504,66 @@
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
"dev": true
},
"node_modules/typedoc": {
"version": "0.24.8",
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.8.tgz",
"integrity": "sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w==",
"dev": true,
"dependencies": {
"lunr": "^2.3.9",
"marked": "^4.3.0",
"minimatch": "^9.0.0",
"shiki": "^0.14.1"
},
"bin": {
"typedoc": "bin/typedoc"
},
"engines": {
"node": ">= 14.14"
},
"peerDependencies": {
"typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x"
}
},
"node_modules/typedoc-umlclass": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/typedoc-umlclass/-/typedoc-umlclass-0.7.1.tgz",
"integrity": "sha512-nHEPjbda1oIZ5lKNMainzi93UB1FyyMNoFWjNlipjK/Adx/RtepJdaGdIrZ8EgtuWGi7pW+xP8jaRmb40vj/9w==",
"dev": true,
"dependencies": {
"plantuml-encoder": "^1.4.0",
"plantuml-pipe": "^1.5.0",
"progress": "^2.0.3",
"queue-fifo": "^0.2.6"
},
"peerDependencies": {
"typedoc": "0.23.x || 0.24.x"
}
},
"node_modules/typedoc/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/typedoc/node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/typescript": {
"version": "4.9.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
@@ -7571,6 +7743,18 @@
"integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
"dev": true
},
"node_modules/vscode-oniguruma": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz",
"integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==",
"dev": true
},
"node_modules/vscode-textmate": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz",
"integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==",
"dev": true
},
"node_modules/watchify": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/watchify/-/watchify-4.0.0.tgz",
@@ -10423,6 +10607,12 @@
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true
},
"ansi-sequence-parser": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz",
"integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==",
"dev": true
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -10731,6 +10921,15 @@
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true
},
"binary-split": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/binary-split/-/binary-split-1.0.5.tgz",
"integrity": "sha512-AQ5fcBrUU5hoIafkEvNKqxT+2xbqlSqAXef6IdCQr5wpHu9E7NGM6rTAlYJYbtxvAvjfx8nJkBy6rNlbPPI+Pw==",
"dev": true,
"requires": {
"through2": "^2.0.3"
}
},
"bn.js": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
@@ -11344,6 +11543,15 @@
"integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==",
"dev": true
},
"dbly-linked-list": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/dbly-linked-list/-/dbly-linked-list-0.3.4.tgz",
"integrity": "sha512-327vOlwspi9i1T3Kc9yZhRUR8qDdgMQ4HmXsFDDCQ/HTc3sNe7gnF5b0UrsnaOJ0rvmG7yBZpK0NoOux9rKYKw==",
"dev": true,
"requires": {
"lodash.isequal": "^4.5.0"
}
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -12260,6 +12468,12 @@
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true
},
"jsonc-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
"integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==",
"dev": true
},
"jsonparse": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
@@ -12328,6 +12542,12 @@
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"dev": true
},
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"dev": true
},
"lodash.memoize": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz",
@@ -12352,11 +12572,23 @@
"yallist": "^3.0.2"
}
},
"lunr": {
"version": "2.3.9",
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
"dev": true
},
"map-stream": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz",
"integrity": "sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ=="
},
"marked": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
"integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==",
"dev": true
},
"md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@@ -12741,6 +12973,22 @@
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true
},
"plantuml-encoder": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/plantuml-encoder/-/plantuml-encoder-1.4.0.tgz",
"integrity": "sha512-sxMwpDw/ySY1WB2CE3+IdMuEcWibJ72DDOsXLkSmEaSzwEUaYBT6DWgOfBiHGCux4q433X6+OEFWjlVqp7gL6g==",
"dev": true
},
"plantuml-pipe": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/plantuml-pipe/-/plantuml-pipe-1.5.0.tgz",
"integrity": "sha512-a4brAspbSlQYDdzLtDulB7ZQOGI5JC3Kyk7cTo8kDrM2gOQcarXkP/fN0mR+gD3srS9BMI06rOQKkvhrFpqYzg==",
"dev": true,
"requires": {
"binary-split": "^1.0.5",
"split2": "^4.2.0"
}
},
"point-in-polygon": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz",
@@ -12768,6 +13016,12 @@
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"dev": true
},
"progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"dev": true
},
"proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -12828,6 +13082,15 @@
"integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==",
"dev": true
},
"queue-fifo": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/queue-fifo/-/queue-fifo-0.2.6.tgz",
"integrity": "sha512-rwlnZHAaTmWEGKC7ziasK8u4QnZW/uN6kSiG+tHNf/1GA+R32FArZi18s3SYUpKcA0Y6jJoUDn5GT3Anoc2mWw==",
"dev": true,
"requires": {
"dbly-linked-list": "0.3.4"
}
},
"quickselect": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/quickselect/-/quickselect-1.1.1.tgz",
@@ -13124,6 +13387,18 @@
"integrity": "sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw==",
"dev": true
},
"shiki": {
"version": "0.14.4",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.4.tgz",
"integrity": "sha512-IXCRip2IQzKwxArNNq1S+On4KPML3Yyn8Zzs/xRgcgOWIr8ntIK3IKzjFPfjy/7kt9ZMjc+FItfqHRBg8b6tNQ==",
"dev": true,
"requires": {
"ansi-sequence-parser": "^1.1.0",
"jsonc-parser": "^3.2.0",
"vscode-oniguruma": "^1.7.0",
"vscode-textmate": "^8.0.0"
}
},
"simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
@@ -13185,6 +13460,12 @@
"through": "2"
}
},
"split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"dev": true
},
"statuses": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
@@ -13500,6 +13781,50 @@
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
"dev": true
},
"typedoc": {
"version": "0.24.8",
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.24.8.tgz",
"integrity": "sha512-ahJ6Cpcvxwaxfu4KtjA8qZNqS43wYt6JL27wYiIgl1vd38WW/KWX11YuAeZhuz9v+ttrutSsgK+XO1CjL1kA3w==",
"dev": true,
"requires": {
"lunr": "^2.3.9",
"marked": "^4.3.0",
"minimatch": "^9.0.0",
"shiki": "^0.14.1"
},
"dependencies": {
"brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0"
}
},
"minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
"dev": true,
"requires": {
"brace-expansion": "^2.0.1"
}
}
}
},
"typedoc-umlclass": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/typedoc-umlclass/-/typedoc-umlclass-0.7.1.tgz",
"integrity": "sha512-nHEPjbda1oIZ5lKNMainzi93UB1FyyMNoFWjNlipjK/Adx/RtepJdaGdIrZ8EgtuWGi7pW+xP8jaRmb40vj/9w==",
"dev": true,
"requires": {
"plantuml-encoder": "^1.4.0",
"plantuml-pipe": "^1.5.0",
"progress": "^2.0.3",
"queue-fifo": "^0.2.6"
}
},
"typescript": {
"version": "4.9.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
@@ -13627,6 +13952,18 @@
"integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
"dev": true
},
"vscode-oniguruma": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz",
"integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==",
"dev": true
},
"vscode-textmate": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz",
"integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==",
"dev": true
},
"watchify": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/watchify/-/watchify-4.0.0.tgz",

View File

@@ -2,21 +2,22 @@
"name": "DCSOlympus",
"node-main": "./bin/www",
"main": "http://localhost:3000",
"version": "v0.4.3-alpha",
"version": "v0.4.4-alpha",
"private": true,
"scripts": {
"copy": "copy.bat",
"start": "npm run copy & concurrently --kill-others \"npm run watch\" \"nodemon ./bin/www\"",
"watch": "watchify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]"
"watch": "watchify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]",
"document": "typedoc --out ../docs src/index.ts src/contextmenus/*.ts src/controls/*.ts src/map/*.ts src/mission/*.ts src/other/*.ts src/panels/*.ts src/popups/*.ts src/server/*.ts src/unit/*.ts src/weapon/*.ts"
},
"dependencies": {
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"ejs": "^3.1.8",
"express": "~4.16.1",
"express-basic-auth": "^1.2.1",
"morgan": "~1.9.1",
"save": "^2.9.0",
"express-basic-auth": "^1.2.1"
"save": "^2.9.0"
},
"devDependencies": {
"@babel/preset-env": "^7.21.4",
@@ -42,6 +43,8 @@
"nodemon": "^2.0.20",
"sortablejs": "^1.15.0",
"tsify": "^5.0.4",
"typedoc": "^0.24.8",
"typedoc-umlclass": "^0.7.1",
"typescript": "^4.9.4",
"watchify": "^4.0.0"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="640"
height="480"
id="svg10"
sodipodi:docname="blue.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
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"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
id="namedview10"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="0.88166667"
inkscape:cx="497.3535"
inkscape:cy="301.13421"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg10"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata16">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs14" />
<path
fill="#bc0000"
fill-opacity="1"
d="M 0,0 H 640 V 480 H 0 Z"
id="path2"
style="fill:#0000cd;fill-opacity:1;stroke-width:0.999996" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,84 +1,556 @@
{
"RED": "AGGRESSORS",
"INS": "INSURGENTS",
"DZ" : "ALGERIA",
"AR" : "ARGENTINA",
"AU" : "AUSTRALIA",
"AT" : "AUSTRIA",
"BH" : "BAHRAIN",
"BY" : "BELARUS",
"BE" : "BELGIUM",
"BO" : "BOLIVIA",
"BR" : "BRAZIL",
"BG" : "BULGARIA",
"CA" : "CANADA",
"CL" : "CHILE",
"CN" : "CHINA",
"HR" : "CROATIA",
"CU" : "CUBA",
"CY" : "CYPRUS",
"CZ" : "CHEZH_REPUBLIC",
"DK" : "DENMARK",
"EG" : "EGYPT",
"ET" : "ETHIOPIA",
"FI" : "FINLAND",
"FR" : "FRANCE",
"GE" : "GEORGIA",
"DE" : "GERMANY",
"GH" : "GHANA",
"GI" : "Gibraltar",
"GR" : "GREECE",
"HN" : "HONDURAS",
"HU" : "HUNGARY",
"IS" : "Iceland",
"IN" : "INDIA",
"ID" : "INDONESIA",
"IR" : "IRAN",
"IQ" : "IRAQ",
"IE" : "Ireland",
"IM" : "Isle Of Man",
"IL" : "ISRAEL",
"IT" : "ITALY",
"JP" : "JAPAN",
"JO" : "JORDAN",
"KZ" : "KAZAKHSTAN",
"KR" : "SOUTH_KOREA",
"KW" : "KUWAIT",
"LB" : "LEBANON",
"LY" : "LIBYIA",
"MY" : "MALAYSIA",
"MX" : "MEXICO",
"MA" : "MOROCCO",
"NL" : "THE_NETHERLANDS",
"NG" : "NIGERIA",
"NO" : "NORWAY",
"OM" : "OMAN",
"PK" : "PAKISTAN",
"PE" : "PERU",
"PH" : "PHILIPPINES",
"PL" : "POLAND",
"PT" : "PORTUGAL",
"QA" : "QATAR",
"RO" : "ROMANIA",
"RU" : "RUSSIA",
"SA" : "SAUDI ARABIA",
"RS" : "SERBIA",
"SK" : "SLOVAKIA",
"SI" : "SLOVENIA",
"ZA" : "SOUTH AFRICA",
"ES" : "SPAIN",
"SD" : "SUDAN",
"SE" : "SWEDEN",
"CH" : "SWITZERLAND",
"SY" : "SYRIA",
"TH" : "THAILAND",
"TN" : "TUNISIA",
"TR" : "TURKEY",
"UA" : "UKRAINE",
"AE" : "UNITED ARAB EMIRATES",
"GB" : "UK",
"US" : "USA",
"VE" : "VENEZUELA",
"VN" : "VIETNAM",
"YE" : "YEMEN"
"AGGRESSORS": {
"flagCode": "RED",
"liveryCodes": [
"RSO"
]
},
"INSURGENTS": {
"flagCode": "UNK",
"liveryCodes": [
"INS"
]
},
"ALGERIA": {
"flagCode": "DZ",
"liveryCodes": [
"DZA"
]
},
"ARGENTINA": {
"flagCode": "AR",
"liveryCodes": [
"ARG"
]
},
"AUSTRALIA": {
"flagCode": "AU",
"liveryCodes": [
"AUS",
"AUSAF"
]
},
"AUSTRIA": {
"flagCode": "AT",
"liveryCodes": [
"AUT"
]
},
"BAHRAIN": {
"flagCode": "BH",
"liveryCodes": [
"BHR"
]
},
"BELARUS": {
"flagCode": "BY",
"liveryCodes": [
"BLR"
]
},
"BELGIUM": {
"flagCode": "BE",
"liveryCodes": [
"BEL"
]
},
"BOLIVIA": {
"flagCode": "BO",
"liveryCodes": [
"BOL"
]
},
"BRAZIL": {
"flagCode": "BR",
"liveryCodes": [
"BRA"
]
},
"BULGARIA": {
"flagCode": "BG",
"liveryCodes": [
"BGR"
]
},
"CANADA": {
"flagCode": "CA",
"liveryCodes": [
"CAN"
]
},
"CHILE": {
"flagCode": "CL",
"liveryCodes": [
"CHL"
]
},
"CHINA": {
"flagCode": "CN",
"liveryCodes": [
"CHN"
]
},
"CROATIA": {
"flagCode": "HR",
"liveryCodes": [
"HRV"
]
},
"CUBA": {
"flagCode": "CU",
"liveryCodes": [
"CUB"
]
},
"CYPRUS": {
"flagCode": "CY",
"liveryCodes": [
"CYP"
]
},
"CHEZH_REPUBLIC": {
"displayName": "Czech Republic",
"flagCode": "CZ",
"liveryCodes": [
"CZE"
]
},
"DENMARK": {
"flagCode": "DK",
"liveryCodes": [
"DEN"
]
},
"EGYPT": {
"flagCode": "EG",
"liveryCodes": [
"EGY",
"EGP"
]
},
"ETHIOPIA": {
"flagCode": "ET",
"liveryCodes": [
"ETH"
]
},
"FINLAND": {
"flagCode": "FI",
"liveryCodes": [
"FIN"
]
},
"FRANCE": {
"flagCode": "FR",
"liveryCodes": [
"FRA"
]
},
"GEORGIA": {
"flagCode": "GE",
"liveryCodes": [
"GRG"
]
},
"GERMANY": {
"flagCode": "DE",
"liveryCodes": [
"GER"
]
},
"GHANA": {
"flagCode": "GH",
"liveryCodes": [
"GHA"
]
},
"GREECE": {
"flagCode": "GR",
"liveryCodes": [
"GRC"
]
},
"HONDURAS": {
"flagCode": "HN",
"liveryCodes": [
"HND"
]
},
"HUNGARY": {
"flagCode": "HU",
"liveryCodes": [
"HUN"
]
},
"INDIA": {
"flagCode": "IN",
"liveryCodes": [
"IND"
]
},
"INDONESIA": {
"flagCode": "ID",
"liveryCodes": [
"IDN"
]
},
"IRAN": {
"flagCode": "IR",
"liveryCodes": [
"IRN"
]
},
"IRAQ": {
"flagCode": "IQ",
"liveryCodes": [
"IRQ"
]
},
"ISRAEL": {
"flagCode": "IL",
"liveryCodes": [
"ISR"
]
},
"ITALY": {
"flagCode": "IT",
"liveryCodes": [
"ITA"
]
},
"JAPAN": {
"flagCode": "JP",
"liveryCodes": [
"JPN"
]
},
"JORDAN": {
"flagCode": "JO",
"liveryCodes": [
"JOR"
]
},
"KAZAKHSTAN": {
"flagCode": "KZ",
"liveryCodes": [
"KAZ"
]
},
"SOUTH_KOREA": {
"displayName": "South Korea",
"flagCode": "KR",
"liveryCodes": [
"KOR"
]
},
"KUWAIT": {
"flagCode": "KW",
"liveryCodes": [
"KWT"
]
},
"LEBANON": {
"flagCode": "LB",
"liveryCodes": [
"LBN"
]
},
"MALAYSIA": {
"flagCode": "MY",
"liveryCodes": [
"MYS"
]
},
"MEXICO": {
"flagCode": "MX",
"liveryCodes": [
"MEX"
]
},
"MOROCCO": {
"flagCode": "MA",
"liveryCodes": [
"MAR"
]
},
"THE_NETHERLANDS": {
"displayName": "The Netherlands",
"flagCode": "NL",
"liveryCodes": [
"NETH"
]
},
"NIGERIA": {
"flagCode": "NG",
"liveryCodes": [
"NGA"
]
},
"NORWAY": {
"flagCode": "NO",
"liveryCodes": [
"NOR"
]
},
"OMAN": {
"flagCode": "OM",
"liveryCodes": [
"OMN"
]
},
"PAKISTAN": {
"flagCode": "PK",
"liveryCodes": [
"PAK"
]
},
"PERU": {
"flagCode": "PE",
"liveryCodes": [
"PER"
]
},
"PHILIPPINES": {
"flagCode": "PH",
"liveryCodes": [
"PHL"
]
},
"POLAND": {
"flagCode": "PL",
"liveryCodes": [
"POL"
]
},
"PORTUGAL": {
"flagCode": "PT",
"liveryCodes": [
"PRT"
]
},
"QATAR": {
"flagCode": "QA",
"liveryCodes": [
"QAT"
]
},
"ROMANIA": {
"flagCode": "RO",
"liveryCodes": [
"ROU"
]
},
"RUSSIA": {
"flagCode": "RU",
"liveryCodes": [
"RUS"
]
},
"SAUDI_ARABIA": {
"displayName": "Saudi Arabia",
"flagCode": "SA",
"liveryCodes": [
"SAU"
]
},
"SERBIA": {
"flagCode": "RS",
"liveryCodes": [
"SRB"
]
},
"SLOVAKIA": {
"flagCode": "SK",
"liveryCodes": [
"SVK"
]
},
"SLOVENIA": {
"flagCode": "SI",
"liveryCodes": [
"SVN"
]
},
"SOUTH_AFRICA": {
"displayName": "South Africa",
"flagCode": "ZA",
"liveryCodes": []
},
"SPAIN": {
"flagCode": "ES",
"liveryCodes": [
"SPN",
"SPA"
]
},
"SUDAN": {
"flagCode": "SD",
"liveryCodes": [
"SDN",
"SUN"
]
},
"SWEDEN": {
"flagCode": "SE",
"liveryCodes": [
"SWE"
]
},
"SWITZERLAND": {
"flagCode": "CH",
"liveryCodes": [
"SUI"
]
},
"SYRIA": {
"flagCode": "SY",
"liveryCodes": [
"SYR"
]
},
"THAILAND": {
"flagCode": "TH",
"liveryCodes": [
"THA"
]
},
"TUNISIA": {
"flagCode": "TN",
"liveryCodes": [
"TUN"
]
},
"TURKEY": {
"flagCode": "TR",
"liveryCodes": [
"TUR"
]
},
"UKRAINE": {
"flagCode": "UA",
"liveryCodes": [
"UKR"
]
},
"UNITED_ARAB_EMIRATES": {
"displayName": "United Arab Emirates",
"flagCode": "AE",
"liveryCodes": [
"ARE"
]
},
"UK": {
"displayName": "United Kingdom",
"flagCode": "GB",
"liveryCodes": [
"UK"
]
},
"USA": {
"displayName": "United States of America",
"flagCode": "US",
"liveryCodes": [
"USA",
"USAF"
]
},
"VENEZUELA": {
"flagCode": "VE",
"liveryCodes": [
"VEN"
]
},
"VIETNAM": {
"flagCode": "VN",
"liveryCodes": [
"VNM"
]
},
"YEMEN": {
"flagCode": "YE",
"liveryCodes": [
"YEM"
]
},
"CJTF_BLUE": {
"displayName": "Combined Joint Task Force Blue",
"flagCode": "BLUE",
"liveryCodes": [
"BLUE"
]
},
"SOUTH_OSETIA": {
"displayName": "South Ossetia",
"flagCode": "UNK",
"liveryCodes": []
},
"NORTH_KOREA": {
"displayName": "Democratic People's Republic of Korea",
"flagCode": "KP",
"liveryCodes": [
"PRK"
]
},
"CJTF_RED": {
"displayName": "Combined Joint Task Force Red",
"flagCode": "RED",
"liveryCodes": [
"RED"
]
},
"ABKHAZIA": {
"flagCode": "UNK",
"liveryCodes": [
"ABH"
]
},
"ITALIAN_SOCIAL_REPUBLIC": {
"displayName": "Italian Social Republic",
"flagCode": "SOCIAL",
"liveryCodes": [
"RSI"
]
},
"USSR": {
"displayName": "USSR",
"flagCode": "USSR",
"liveryCodes": []
},
"ECUADOR": {
"flagCode": "EC",
"liveryCodes": [
"ECU"
]
},
"LIBYA": {
"flagCode": "LY",
"liveryCodes": [
"LBY",
"LIB"
]
},
"UN_PEACEKEEPERS": {
"displayName": "United Nations",
"flagCode": "UNK",
"liveryCodes": [
"UN"
]
},
"GDR": {
"flagCode": "UNK",
"liveryCodes": [
"GDR"
]
},
"YUGOSLAVIA": {
"flagCode": "YUG",
"liveryCodes": [
"YUG"
]
},
"THIRDREICH": {
"displayName": "Third Reich",
"flagCode": "THIRD",
"liveryCodes": []
}
}

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="640"
height="480"
id="svg10"
sodipodi:docname="red.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
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"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
id="namedview10"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="0.88166667"
inkscape:cx="497.3535"
inkscape:cy="301.13421"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg10"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata16">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs14" />
<path
fill="#bc0000"
fill-opacity="1"
d="M 0,0 H 640 V 480 H 0 Z"
id="path2"
style="fill:#cc0000;fill-opacity:1;stroke-width:0.999996" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.0"
width="640"
height="480"
viewBox="0 0 640 480"
id="svg8"
sodipodi:docname="third.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs12" />
<sodipodi:namedview
id="namedview10"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
width="640px"
inkscape:zoom="1.058"
inkscape:cx="284.97164"
inkscape:cy="274.57467"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg8" />
<rect
width="640"
height="480"
fill="#dd0000"
id="rect2"
x="0"
y="0"
style="stroke-width:0.999997" />
<circle
cx="320"
cy="240"
r="180"
fill="#ffffff"
id="circle4"
style="stroke-width:1" />
<path
d="M 472.73507,256.97056 387.88225,172.11774 252.11775,307.88225 167.26494,223.02943 M 336.97057,87.264932 252.11775,172.11774 387.88225,307.88225 303.02944,392.73506"
fill="none"
stroke="#000000"
stroke-width="48"
id="path6" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="640"
height="480"
id="svg10"
sodipodi:docname="unk.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
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"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
id="namedview10"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="0.88166667"
inkscape:cx="497.3535"
inkscape:cy="300"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg10"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata16">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs14" />
<path
fill="#bc0000"
fill-opacity="1"
d="M 0,0 H 640 V 480 H 0 Z"
id="path2"
style="fill:#ffffff;fill-opacity:1;stroke-width:0.999996" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:549.6px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1"
x="179.23065"
y="424.19662"
id="text2551"><tspan
sodipodi:role="line"
id="tspan2549"
x="179.23065"
y="424.19662"
style="stroke-width:1">?</tspan></text>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
width="640"
height="480"
id="svg10"
sodipodi:docname="ussr.svg"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
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"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
id="namedview10"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="0.88166667"
inkscape:cx="497.3535"
inkscape:cy="300"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg10" />
<metadata
id="metadata16">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs14" />
<path
fill="#bc0000"
fill-opacity="1"
d="M 0,0 H 639.99998 V 480 H 0 Z"
id="path2"
style="fill:#cc0000;fill-opacity:1;stroke-width:0.999997" />
<path
id="path11728"
d="m 160.0004,30 -6.73546,20.729509 H 131.4688 L 149.10222,63.540898 142.36675,84.2704 160.0004,71.458772 177.63406,84.2704 170.89859,63.540898 188.532,50.729509 h -21.79613 z m 0,10.79999 4.31062,13.266778 h 13.94975 l -11.2856,8.199597 4.31061,13.266777 -11.28538,-8.199363 -11.28538,8.199363 4.31062,-13.266777 -11.28561,-8.199597 h 13.94975 z"
style="fill:#ffd700;fill-opacity:1;stroke:none;stroke-width:0.15px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<g
style="fill:#ffd700;fill-opacity:1;stroke-width:1.25"
id="g2900"
transform="matrix(0.79145503,0,0,0.78939049,3.0638126,3.0127518)">
<path
id="rect4165-6"
d="m 137.43744,171.69421 18.86296,18.9937 17.78834,-17.66589 c 27.05847,29.021 55.43807,56.99501 82.28704,86.12782 4.03444,4.06233 10.59815,4.085 14.66056,0.0506 4.06232,-4.03445 4.08499,-10.59815 0.0506,-14.66056 -28.81871,-27.1901 -57.72545,-54.60143 -86.55328,-81.89095 l 23.96499,-23.80003 -33.34026,-4.61605 z"
style="fill:#ffd700;fill-opacity:1;stroke:none;stroke-width:0.611489;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
id="path4179-3"
d="m 198.2887,110.1955 c 15.51743,8.7394 27.29872,21.28122 34.2484,34.3924 7.04394,13.28902 10.13959,27.16218 10.20325,38.25433 0.13054,22.74374 -18.43771,41.18184 -41.18183,41.18184 -12.13597,0 -23.04607,-5.24868 -30.58302,-13.60085 l -4.16863,3.51033 c -0.70999,-0.27231 -1.46387,-0.41221 -2.22429,-0.41276 -1.82948,1.9e-4 -3.56621,0.80531 -4.74859,2.20136 -2.97368,0.38896 -5.46251,2.44529 -6.40534,5.29224 -3.13486,6.28843 -8.63524,11.21997 -15.29104,13.4776 -0.0637,0.0216 -0.11992,0.05 -0.1758,0.0783 -3.07749,1.12758 -6.16259,3.1643 -8.78919,5.80245 -5.19155,5.23656 -7.72858,11.93658 -6.30024,16.63822 -0.14098,0.40857 -0.21361,0.83759 -0.21498,1.26979 1.5e-4,2.17082 1.75991,3.93058 3.93073,3.93073 0.54341,-0.002 1.08053,-0.11639 1.57745,-0.33632 4.69369,1.05881 11.06885,-1.54582 16.05444,-6.55917 2.82624,-2.85072 4.94356,-6.22349 5.98303,-9.53062 2.31696,-6.62278 7.29699,-12.01856 13.62281,-15.05312 0.15105,-0.0725 0.27303,-0.14714 0.38218,-0.22358 2.12082,-1.01408 3.67251,-2.92895 4.225,-5.2139 9.70222,11.44481 24.25255,18.75299 40.51876,19.13577 29.83352,0.70205 52.13299,-21.25802 53.16414,-52.83642 0.51894,-15.89259 -5.62993,-36.3847 -19.6412,-53.19089 -10.70835,-12.84441 -26.40987,-23.50795 -44.18699,-28.20777 z"
style="fill:#ffd700;fill-opacity:1;stroke:none;stroke-width:0.625044;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 640 480"
version="1.1"
id="svg8"
sodipodi:docname="yug.svg"
width="640"
height="480"
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs12" />
<sodipodi:namedview
id="namedview10"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="0.91840278"
inkscape:cx="253.1569"
inkscape:cy="286.91115"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg8" />
<path
fill="#dd0000"
d="M 0,320 H 640 V 480 H 0"
id="path2"
style="stroke-width:0.999996" />
<path
fill="#ffffff"
d="M 0,160 H 640 V 320 H 0"
id="path4"
style="stroke-width:0.999996" />
<path
fill="#003893"
d="M 0,0 H 640 V 160 H 0"
id="path6"
style="stroke-width:0.999996" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,194 +0,0 @@
/**************************************/
.olympus-dialog {
align-self: center;
background:white;
border-radius: 10px;
display: flex;
flex-direction: column;
justify-self: center;
padding:10px;
position:absolute;
width:fit-content;
z-index: 9999;
}
.olympus-dialog-close {
cursor:pointer;
position:absolute;
right:10px;
top:5px;
}
.olympus-dialog-header {
font-weight:bold;
}
/**************************************/
/***** AIC *****/
.aic-panel {
z-index: 9999;
}
#aic-control-panel {
bottom:30px;
position: absolute;
left:30px;
}
#aic-control-panel .olympus-button img {
max-width: 32px;
}
#aic-toolbox, #aic-callsign-panel {
align-items: flex-start;
align-self: center;
flex-direction: column;
row-gap: 10px;
display:none;
position:absolute;
}
.aic-panel {
background:#eaeaea;
border-bottom-right-radius: 10px;
border-top-right-radius: 10px;
justify-self: left;
padding:5px 10px;
}
.aic-enabled #aic-toolbox, .aic-enabled #aic-callsign-panel {
display:flex;
}
.aic-enabled #aic-callsign-panel {
align-self: auto;
top: 100px;
}
.aic-panel h2 {
font-size:90%;
margin:0;
padding:0;
text-align: center;
}
#aic-callsign-display {
text-align: center;
}
#aic-formation-list {
display:flex;
flex-direction: column;
justify-content: center;
}
#aic-formation-list > div {
align-items: center;
cursor: pointer;
display:flex;
flex-direction: column;
justify-content: center;
margin-top:10px;
position:relative;
}
#aic-formation-list .aic-formation-image img {
border: 1px solid #ccc;
border-radius: 10px;
max-width: 50px;
}
#aic-formation-list .aic-formation-name {
font-size:90%;
}
#aic-formation-list .aic-formation-descriptor {
background:white;
border-radius: 10px;
left:100px;
padding:5px;
position:absolute;
width: max-content;
}
#aic-teleprompt {
background-color: white;
border:2px solid black;
border-radius: 10px;
bottom: 50px;
color: black;
display: none;
justify-content: center;
justify-self: center;
padding: 10px;
position: absolute;
width: fit-content;
z-index: 9999;
}
.aic-enabled #aic-teleprompt {
display:flex;
}
#aic-descriptor {
display:flex;
flex-direction: row;
}
#aic-descriptor .aic-descriptor-section {
display:flex;
flex-direction: column;
margin:0 10px;
}
#aic-descriptor .aic-descriptor-section-label {
background-color:#eaeaea;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
padding:.25em;
text-align: center;
}
#aic-descriptor .aic-descriptor-phrase {
border-bottom: 1px solid #ccc;
display:flex;
flex-direction: row;
margin-bottom:5px;
padding-bottom:2px;
}
#aic-descriptor .aic-descriptor-phrase:last-of-type {
margin-bottom: 0;
}
#aic-descriptor .aic-descriptor-components .aic-descriptor-component {
margin:0 5px;
text-align: center;
}
#aic-descriptor .aic-descriptor-component-label {
display:none;
}
#aic-descriptor .aic-descriptor-component-value:after {
content:",";
margin-right:5px;
}
#aic-descriptor .aic-descriptor-component:last-of-type .aic-descriptor-component-value:after {
content:"; ";
}
#aic-descriptor .aic-descriptor-section:last-of-type .aic-descriptor-component:last-of-type .aic-descriptor-component-value:after {
content:".";
}
/**************************************/

View File

@@ -1,205 +0,0 @@
.ol-strip-board .ol-dialog-header {
align-items: center;
display:flex;
justify-content: space-between;
}
.ol-strip-board-strips {
display:flex;
flex-direction: column;
row-gap: 4px;
}
.ol-strip-board-strip {
align-items: center;
border-radius: var( --border-radius-sm );
column-gap: 4px;
display:flex;
flex-flow: row nowrap;
row-gap:4px;
}
.ol-strip-board-strip[data-flight-status="checkedin"] {
background-color: #ffffff2A;
}
.ol-strip-board-strip[data-flight-status="readytotaxi"] {
background-color: #ffff0063;
}
.ol-strip-board-strip[data-flight-status="clearedtotaxi"] {
background-color: #00ff0030;
}
.ol-strip-board-strip[data-flight-status="halted"] {
background-color: #FF000040;
}
.ol-strip-board-strip[data-flight-status="terminated"] {
background-color: black;
}
.ol-strip-board-headers {
column-gap: 4px;
display:flex;
flex-flow:row nowrap;
text-align: center;
}
.ol-strip-board-headers > *, .ol-strip-board-strip > [data-point] {
padding: 4px;
text-overflow: ellipsis;
white-space: nowrap;
width:80px;
}
.ol-strip-board-strip input[type="text"] {
appearance: none;
background-color: transparent;
border:1px solid #ffffff30;
border-radius: var( --border-radius-sm );
color:white;
font-size:12px;
font-weight:normal;
outline:none;
padding: 4px 0;
text-align: center;
width:100%;
}
.ol-strip-board-strip[data-time-warning="level-1"] [data-point="timeToGo"] {
border:1px solid #cc0000;
}
.ol-strip-board-headers :nth-child(1) {
width:12px;
}
.ol-strip-board-headers :nth-child(2),
.ol-strip-board-strip :nth-child(2),
[data-board-type="ground"] .ol-strip-board-headers :nth-child(3),
[data-board-type="ground"] .ol-strip-board-strip :nth-child(3) {
width:130px;
}
[data-board-type="ground"] .ol-strip-board-strip :nth-child(5) {
text-align: center;
}
.ol-strip-board-headers :last-child,
.ol-strip-board-strip :last-child {
width:20px;
}
[data-board-type="tower"] .ol-strip-board-strip > * {
text-align: center;
}
[data-board-type="tower"] .ol-strip-board-strip a {
color:white;
}
[data-board-type="tower"] .ol-strip-board-strip > :nth-child(2) {
text-align: left;
}
[data-board-type="tower"] .ol-strip-board-strip :nth-child(3) input,
[data-board-type="tower"] .ol-strip-board-strip :nth-child(5) input {
width:30px;
}
[data-board-type="tower"] .ol-strip-board-strip :nth-child(3) {
font-size:10px;
}
[data-altitude-assigned] [data-point="assignedAltitude"] input,
[data-speed-assigned] [data-point="assignedSpeed"] input {
background-color:#ffffffbb;
color: black;
font-weight: var( --font-weight-bolder );
}
[data-warning-altitude] [data-point="altitude"],
[data-warning-speed] [data-point="speed"] {
background:#cc0000;
border-radius: var( --border-radius-sm );
}
.ol-strip-board-strip > [data-point="name"] {
text-overflow: ellipsis;
overflow:hidden;
}
.ol-strip-board-strip .ol-select-value {
opacity: .85;
}
.ol-strip-board-add-flight {
display:flex;
flex-flow: row nowrap;
position:relative;
}
.ol-strip-board-add-flight > * {
border:none;
outline: none;
padding:4px 8px;
}
.add-flight-by-click img {
filter:invert();
height: 12px;
}
.ol-strip-board-add-flight input {
border-radius: var( --border-radius-sm );
}
.ol-strip-board-add-flight .ol-auto-suggest {
background:white;
border-radius: var(--border-radius-sm );
color:black;
display:none;
flex-direction: column;
left:0;
margin:0;
position:absolute;
translate:0 -100%;
top:0;
}
.ol-strip-board-add-flight .ol-auto-suggest[data-has-suggestions] {
display:flex;
row-gap: 4px;
}
.ol-strip-board-add-flight .ol-auto-suggest[data-has-suggestions] a {
cursor: pointer;
}
[data-board-type="ground"] {
bottom:20px;
}
[data-board-type="tower"] {
right:10px;
top:10px;
}
[data-board-type="tower"] .ol-auto-suggest {
top:30px;
translate:0;
}

View File

@@ -1,27 +0,0 @@
#unit-list {
display:flex;
flex-direction: column;
font-size:13px;
height: 250px;
width:fit-content;
}
#unit-list > div {
display:flex;
flex-direction: row;
flex-wrap: nowrap;
}
#unit-list > div > div {
text-overflow: ellipsis;
white-space: nowrap;
width:100px;
}
#unit-list > div:first-of-type {
text-align: center;
}
#unit-list > div > div:nth-of-type( 4 ) {
text-align: center;
}

View File

@@ -11,7 +11,7 @@
left: 10px;
position: absolute;
top: 10px;
z-index: 9999;
z-index: 99999;
column-gap: 10px;
}

File diff suppressed because it is too large Load Diff

View File

@@ -30,12 +30,47 @@
flex-direction: column;
justify-content: space-between;
row-gap: 5px;
padding: 20px;
}
#aircraft-spawn-menu,
#helicopter-spawn-menu,
#groundunit-spawn-menu,
#navyunit-spawn-menu {
#map-contextmenu>div:nth-child(n+4)>div {
width: 100%;
}
.contextmenu-advanced-options {
align-items: center;
display: flex;
flex-direction: column;
justify-content: space-between;
row-gap: 5px;
width: 100%;
}
.contextmenu-advanced-options-toggle {
display: flex;
align-content: center;
text-align: left;
width: 100%;
margin: 5px;
column-gap: 5px;
cursor: pointer;
}
.contextmenu-advanced-options-toggle:after {
content: url(/resources/theme/images/icons/chevron-down.svg);
margin: auto;
}
.contextmenu-advanced-options-toggle div:first-child {
width: fit-content;
white-space: nowrap;
}
.contextmenu-advanced-options>* {
width: 100%;
}
.unit-spawn-menu {
height: fit-content;
}
@@ -58,11 +93,30 @@
width: 50px;
}
#aircraft-spawn-menu .ol-select.is-open .ol-select-options,
#helicopter-spawn-menu .ol-select.is-open .ol-select-options {
.unit-spawn-menu .ol-select.is-open .ol-select-options {
max-height: 300px;
}
.unit-loadout-list {
min-width: 0;
}
.unit-loadout-list div {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
background-color: var(--background-steel);
padding: 2px 5px 2px 5px;
}
.unit-loadout-list div:hover {
overflow: visible;
white-space: nowrap;
background-color: var(--background-steel);
width: fit-content;
border-radius: var(--border-radius-sm);
}
.deploy-unit-button {
margin-top: 5px;
text-align: center;
@@ -82,7 +136,7 @@
margin: 0px 5px;
}
.upper-bar button:nth-child(2) {
.upper-bar button:first-of-type {
margin-left: auto;
}
@@ -137,31 +191,44 @@
content: "Create neutral unit";
}
#aircraft-loadout-preview,
#helicopter-loadout-preview {
.unit-label-count-container {
display: flex;
flex-direction: row;
align-items: center;
column-gap: 5px;
}
.unit-label-count-container>div:nth-child(1) {
width: 100%;
min-width: 0;
}
.unit-label-count-container>div:nth-child(2) {
font-size: large;
}
.unit-loadout-preview {
align-content: space-between;
align-items: center;
column-gap: 20px;
column-gap: 10px;
display: flex;
flex-direction: row;
width: 100%;
}
#aircaft-loadout-list,
#helicopter-loadout-list {
.unit-loadout-list {
align-content: center;
display: flex;
flex-direction: column;
height: 100%;
}
#aircraft-unit-image,
#helicopter-unit-image {
.unit-image {
filter: invert(100%);
height: 100px;
width: 25%;
margin-bottom: 10px;
margin-top: 10px;
width: 100px;
aspect-ratio: 1/1;
}
#smoke-spawn-menu {
@@ -264,9 +331,20 @@
width: 16px;
}
.ol-select>.ol-select-options>div button.country-dropdown-element {
display: flex;
flex-direction: row;
align-content: center;
column-gap: 10px;
width: 100%;
}
.country-dropdown-element img {
height: 20px;
aspect-ratio: initial;
}
/* Buttons */
#center-map::before {
content: url("/resources/theme/images/icons/arrows-to-eye-solid.svg");
}
@@ -486,4 +564,36 @@
#airbase-runways>.runway>.heading:last-of-type {
flex-direction: row-reverse;
}
/* Airbase spawn menu */
#airbase-spawn-contextmenu {
display: flex;
flex-direction: column;
height: fit-content;
position: absolute;
row-gap: 5px;
width: 300px;
z-index: 9999;
}
#airbase-spawn-contextmenu>div:nth-child(2) {
align-items: center;
display: flex;
flex-direction: row;
justify-content: space-evenly;
padding-right: 0px;
}
#airbase-spawn-contextmenu >div:nth-child(n+3) {
align-items: center;
display: flex;
flex-direction: column;
justify-content: space-between;
row-gap: 5px;
padding: 20px;
}
#airbase-spawn-contextmenu>div:nth-child(n+3)>div {
width: 100%;
}

View File

@@ -136,19 +136,19 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
}
#altitude-type-switch[data-value="true"]>.ol-switch-fill::before {
content: "AGL";
}
#altitude-type-switch[data-value="false"]>.ol-switch-fill::before {
content: "ASL";
}
#altitude-type-switch[data-value="false"]>.ol-switch-fill::before {
content: "AGL";
}
#speed-type-switch[data-value="true"]>.ol-switch-fill::before {
content: "GS";
content: "CAS";
}
#speed-type-switch[data-value="false"]>.ol-switch-fill::before {
content: "CAS";
content: "GS";
}
#unit-control-panel .ol-slider-value {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="52"
height="52"
viewBox="0 0 13.758333 13.758333"
version="1.1"
id="svg5"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
sodipodi:docname="smoke.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
width="52mm"
units="px"
inkscape:zoom="1.4823794"
inkscape:cx="-107.59729"
inkscape:cy="-51.268927"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1" />
<defs
id="defs2">
<linearGradient
id="linearGradient4717"
inkscape:swatch="solid">
<stop
style="stop-color:#0cffff;stop-opacity:1;"
offset="0"
id="stop4715" />
</linearGradient>
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<circle
style="stroke-width:13.3879;stroke-linecap:round;stroke:none;stroke-opacity:1;fill-opacity:1"
id="path901"
cx="6.3892984"
cy="12.526179"
r="0.40000001" />
<circle
style="stroke-width:21.3082;stroke-linecap:round;stroke:none;stroke-opacity:1;fill-opacity:1"
id="path901-1"
cx="6.1684337"
cy="11.768929"
r="0.63664067" />
<circle
style="stroke-width:28.7005;stroke-linecap:round;stroke:none;stroke-opacity:1;fill-opacity:1"
id="path901-5"
cx="7.067668"
cy="11.674273"
r="0.85750532" />
<circle
style="stroke-width:28.1725;stroke-linecap:round;stroke:none;stroke-opacity:1;fill-opacity:1"
id="path901-4"
cx="6.8468037"
cy="10.711934"
r="0.84172928" />
<circle
style="stroke-width:51.9334;stroke-linecap:round;stroke:none;stroke-opacity:1;fill-opacity:1"
id="path901-6"
cx="8.7714815"
cy="8.3928556"
r="1.5516512" />
<circle
style="stroke-width:41.373;stroke-linecap:round;stroke:none;stroke-opacity:1;fill-opacity:1"
id="path901-2"
cx="7.9037986"
cy="10.159773"
r="1.2361304" />
<circle
style="stroke-width:53.5174;stroke-linecap:round;stroke:none;stroke-opacity:1;fill-opacity:1"
id="path901-3"
cx="7.320085"
cy="7.6040535"
r="1.5989795" />
<circle
style="fill-opacity:1;stroke:none;stroke-width:53.5174;stroke-linecap:round;stroke-opacity:1"
id="path901-3-9"
cx="7.05018"
cy="9.5266676"
r="1.5989795" />
<circle
style="stroke-width:60.3817;stroke-linecap:round;stroke:none;stroke-opacity:1;fill-opacity:1"
id="path901-20"
cx="10.033565"
cy="6.5155067"
r="1.804068" />
<circle
style="stroke-width:60.9097;stroke-linecap:round;stroke:none;stroke-opacity:1;fill-opacity:1"
id="path901-15"
cx="8.0300064"
cy="4.9221258"
r="1.819844" />
<circle
style="stroke-width:56.6856;stroke-linecap:round;stroke:none;stroke-opacity:1;fill-opacity:1"
id="path901-0"
cx="11.800482"
cy="2.3821828"
r="1.6936357" />
<circle
style="stroke-width:56.1575;stroke-linecap:round;stroke:none;stroke-opacity:1;fill-opacity:1"
id="path901-0-9"
cx="11.3272"
cy="4.7801414"
r="1.6778597" />
<circle
style="stroke-width:68.302;stroke-linecap:round;stroke:none;stroke-opacity:1;fill-opacity:1"
id="path901-0-4"
cx="9.8600283"
cy="2.6345997"
r="2.0407088" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

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

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -19,7 +19,7 @@ interface CustomEventMap {
"mapStateChanged": CustomEvent<string>,
"mapContextMenu": CustomEvent<>,
"mapVisibilityOptionsChanged": CustomEvent<>,
"commandModeOptionsChanged": CustomEvent<>,
"commandModeOptionsChanged": CustomEvent<>,
"contactsUpdated": CustomEvent<Unit>,
}
@@ -31,6 +31,11 @@ declare global {
}
}
export interface ConfigParameters {
port: number;
address: string;
}
export interface ContextMenuOption {
tooltip: string;
src: string;

View File

@@ -44,4 +44,9 @@ interface LogData {
logs: {[key: string]: string},
sessionHash: string;
time: number;
}
interface ServerRequestOptions {
time?: number;
commandHash?: string;
}

View File

@@ -51,4 +51,46 @@ interface Offset {
x: number,
y: number,
z: number
}
interface UnitData {
category: string,
ID: number;
alive: boolean;
human: boolean;
controlled: boolean;
coalition: string;
country: number;
name: string;
unitName: string;
groupName: string;
state: string;
task: string;
hasTask: boolean;
position: LatLng;
speed: number;
heading: number;
isTanker: boolean;
isAWACS: boolean;
onOff: boolean;
followRoads: boolean;
fuel: number;
desiredSpeed: number;
desiredSpeedType: string;
desiredAltitude: number;
desiredAltitudeType: string;
leaderID: number;
formationOffset: Offset;
targetID: number;
targetPosition: LatLng;
ROE: string;
reactionToThreat: string;
emissionsCountermeasures: string;
TACAN: TACAN;
radio: Radio;
generalSettings: GeneralSettings;
ammo: Ammo[];
contacts: Contact[];
activePath: LatLng[];
isLeader: boolean;
}

View File

@@ -1,3 +1,6 @@
import { LatLng } from "leaflet";
import { Airbase } from "../mission/airbase";
interface LoadoutItemBlueprint {
name: string;
quantity: number;
@@ -22,6 +25,19 @@ interface UnitBlueprint {
range?: string;
loadouts?: LoadoutBlueprint[];
filename?: string;
liveryID?: string;
liveries?: {[key: string]: {name: string, countries: string[]}};
cost?: number;
}
interface UnitSpawnOptions {
roleType: string;
name: string;
latlng: LatLng;
coalition: string;
count: number;
country: string;
loadout: LoadoutBlueprint | undefined;
airbase: Airbase | undefined;
liveryID: string | undefined;
altitude: number | undefined;
}

View File

@@ -1,172 +0,0 @@
import { ToggleableFeature } from "../features/toggleablefeature";
import { AICFormation_Azimuth } from "./aicformation/azimuth";
import { AICFormation_Range } from "./aicformation/range";
import { AICFormation_Single } from "./aicformation/single";
import { AICFormationDescriptorSection } from "./aicformationdescriptorsection";
export class AIC extends ToggleableFeature {
#formations = [
new AICFormation_Single(),
new AICFormation_Range(),
new AICFormation_Azimuth()
];
constructor() {
super( false );
this.onStatusUpdate();
// This feels kind of dirty
let $aicFormationList = document.getElementById( "aic-formation-list" );
if ( $aicFormationList ) {
this.getFormations().forEach( formation => {
// Image
let $imageDiv = document.createElement( "div" );
$imageDiv.classList.add( "aic-formation-image" );
let $img = document.createElement( "img" );
$img.src = "images/formations/" + formation.icon;
$imageDiv.appendChild( $img );
// Name
let $nameDiv = document.createElement( "div" );
$nameDiv.classList.add( "aic-formation-name" );
$nameDiv.innerText = formation.label;
// Wrapper
let $wrapperDiv = document.createElement( "div" );
$wrapperDiv.dataset.formationName = formation.name;
$wrapperDiv.appendChild( $imageDiv )
$wrapperDiv.appendChild( $nameDiv );
$wrapperDiv.addEventListener( "click", ( ev ) => {
const controlTypeInput = document.querySelector( "input[type='radio'][name='control-type']:checked" );
let controlTypeValue:any = ( controlTypeInput instanceof HTMLInputElement && [ "broadcast", "tactical" ].indexOf( controlTypeInput.value ) > -1 ) ? controlTypeInput.value : "broadcast";
// TODO: make this not an "any"
const output:any = formation.getDescriptor({
"aicCallsign" : "Magic",
"bullseyeName" : "Bullseye",
"control" : controlTypeValue,
"numGroups" : formation.numGroups
});
this.updateTeleprompt( output );
});
// Add to DOM
$aicFormationList?.appendChild( $wrapperDiv );
});
}
}
getFormations() {
return this.#formations;
}
onStatusUpdate() {
// Update the DOM
document.body.classList.toggle( "aic-enabled", this.getStatus() );
}
toggleHelp() {
document.getElementById( "aic-help" )?.classList.toggle( "hide" );
}
//*
updateTeleprompt<T extends AICFormationDescriptorSection>( descriptor:T[] ) {
let $teleprompt = document.getElementById( "aic-teleprompt" );
if ( $teleprompt instanceof HTMLElement ) {
// Clean slate
while ( $teleprompt.childNodes.length > 0 ) {
$teleprompt.childNodes[0].remove();
}
function newDiv() {
return document.createElement( "div" );
}
// Wrapper
let $descriptor = newDiv();
$descriptor.id = "aic-descriptor";
for ( const section of descriptor ) {
if ( section.omitSection ) {
continue;
}
let $section = newDiv();
$section.classList.add( "aic-descriptor-section" );
let $sectionLabel = newDiv();
$sectionLabel.classList.add( "aic-descriptor-section-label" );
$sectionLabel.innerText = section.label;
$section.appendChild( $sectionLabel );
for ( const phrase of section.getPhrases() ) {
let $phrase = newDiv();
$phrase.classList.add( "aic-descriptor-phrase" );
for ( const component of phrase.getComponents() ) {
let $component = newDiv();
$component.classList.add( "aic-descriptor-component" );
let $componentLabel = newDiv();
$componentLabel.classList.add( "aic-descriptor-component-label" );
$componentLabel.innerText = component.label;
let $componentValue = newDiv();
$componentValue.classList.add( "aic-descriptor-component-value" );
$componentValue.innerText = component.value;
$component.appendChild( $componentLabel );
$component.appendChild( $componentValue );
$phrase.appendChild( $component );
}
$section.appendChild( $phrase );
}
$descriptor.appendChild( $section );
}
$teleprompt.appendChild( $descriptor );
}
}
//*/
}

View File

@@ -1,54 +0,0 @@
import { AICFormationContextDataInterface, AICFormationDescriptor } from "./aicformationdescriptor";
import { AICFormationDescriptorPhrase } from "./aicformationdescriptorphrase";
import { AICFormationDescriptorSection } from "./aicformationdescriptorsection";
export interface AICFormationInterface {
"icon" : string,
"label" : string,
"name" : string,
"numGroups" : number,
"summary" : string,
"unitBreakdown" : string[]
}
export abstract class AICFormation {
"icon" = "";
"label" = "";
"name" = "";
"numGroups" = 1;
"summary" = "";
"unitBreakdown":string[] = []
constructor() {
this.unitBreakdown = [];
}
addToDescriptorPhrase( section: AICFormationDescriptorSection, phrase: AICFormationDescriptorPhrase, contextData: AICFormationContextDataInterface ) {
return phrase;
}
getDescriptor( contextData: AICFormationContextDataInterface ) {
return new AICFormationDescriptor().generate( this, contextData );
}
hasUnitBreakdown() {
return this.unitBreakdown.length > 0;
}
showFormationNameInDescriptor() {
return true;
}
}

View File

@@ -1,38 +0,0 @@
import { AICFormation, AICFormationInterface } from "../aicformation";
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
export class AICFormation_Azimuth extends AICFormation implements AICFormationInterface {
"icon" = "azimuth.png";
"label" = "Azimuth";
"name" = "azimuth";
"numGroups" = 2;
"summary" = "Two contacts, side-by-side in a line perpedicular to the perspective.";
"unitBreakdown" = [ "<compass> group", "<compass> group" ];
constructor() {
super();
}
addToDescriptorPhrase( section: AICFormationDescriptorSection, phrase: AICFormationDescriptorPhrase, contextData: AICFormationContextDataInterface ) {
switch ( section.name ) {
case "formation":
phrase.addComponent( new AICFormationDescriptorComponent( "<distance>" ) );
phrase.addComponent( new AICFormationDescriptorComponent( "track <compass>" ) );
}
return phrase;
}
}

View File

@@ -1,38 +0,0 @@
import { AICFormation, AICFormationInterface } from "../aicformation";
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
export class AICFormation_Range extends AICFormation implements AICFormationInterface {
"icon" = "range.png";
"label" = "Range";
"name" = "range";
"numGroups" = 2;
"summary" = "Two contacts, one behind the other";
"unitBreakdown" = [ "Lead group", "Trail group" ];
constructor() {
super();
}
addToDescriptorPhrase( section: AICFormationDescriptorSection, phrase: AICFormationDescriptorPhrase, contextData: AICFormationContextDataInterface ) {
switch ( section.name ) {
case "formation":
phrase.addComponent( new AICFormationDescriptorComponent( "<distance>" ) );
phrase.addComponent( new AICFormationDescriptorComponent( "track <compass>" ) );
}
return phrase;
}
}

View File

@@ -1,24 +0,0 @@
import { AICFormation, AICFormationInterface } from "../aicformation";
import { AICFormationContextDataInterface, AICFormationDescriptor } from "../aicformationdescriptor";
export class AICFormation_Single extends AICFormation implements AICFormationInterface {
"icon" = "single.png";
"label" = "Single";
"name" = "single";
"numGroups" = 1;
"summary" = "One contact on its own";
"unitBreakdown": string[] = [];
constructor() {
super();
}
showFormationNameInDescriptor() {
return false;
}
}

View File

@@ -1,55 +0,0 @@
import { AICFormation } from "./aicformation";
import { AICFormationDescriptorSection } from "./aicformationdescriptorsection";
import { AICFormationDescriptorSection_Formation } from "./aicformationdescriptorsection/formation";
import { AICFormationDescriptorSection_Unit } from "./aicformationdescriptorsection/unit";
import { AICFormationDescriptorSection_NumGroups } from "./aicformationdescriptorsection/numgroups";
import { AICFormationDescriptorSection_Who } from "./aicformationdescriptorsection/who";
export interface AICFormationContextDataInterface {
"aicCallsign" : string,
"bullseyeName" : string,
"control" : "broadcast" | "tactical",
"numGroups" : number
}
export class AICFormationDescriptor {
#sections:AICFormationDescriptorSection[] = [
new AICFormationDescriptorSection_Who(),
new AICFormationDescriptorSection_NumGroups(),
new AICFormationDescriptorSection_Formation(),
new AICFormationDescriptorSection_Unit()
]
constructor() {
}
addSection( section:AICFormationDescriptorSection ) {
this.#sections.push( section );
}
getSections() {
return this.#sections;
}
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
let output:object[] = [];
for ( const section of this.#sections ) {
output.push(
section.generate( formation, contextData )
);
}
return output;
}
}

View File

@@ -1,18 +0,0 @@
interface ComponentInterface {
"label" : string;
"value" : string;
}
export class AICFormationDescriptorComponent implements ComponentInterface {
label = "(not set)";
value = "(not set)";
constructor( value:any, label?:string ) {
this.label = label || "(not set)";
this.value = value;
}
}

View File

@@ -1,9 +0,0 @@
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
export abstract class AICFormactionDescriptorComponent_Distance extends AICFormationDescriptorComponent {
constructor( value:string, label?:string ) {
super( value, label );
}
}

View File

@@ -1,9 +0,0 @@
import { AICFormactionDescriptorComponent_Distance } from "../distance";
export class AICFormationDescriptorComponent_Distance_Range extends AICFormactionDescriptorComponent_Distance {
constructor( value:string, label?:string ) {
super( value, label );
}
}

View File

@@ -1,40 +0,0 @@
import { AICFormation } from "./aicformation";
import { AICFormationContextDataInterface } from "./aicformationdescriptor";
import { AICFormationDescriptorComponent } from "./aicformationdescriptorcomponent";
export interface AICFormationDescriptorPhraseInterface {
"generate" : CallableFunction,
"label" : string,
"name" : string
}
export class AICFormationDescriptorPhrase {
#components : AICFormationDescriptorComponent[] = [];
label = "";
name = "";
constructor() {
}
addComponent( component:AICFormationDescriptorComponent ) {
this.#components.push( component );
return this;
}
getComponents() {
return this.#components;
}
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
return this;
}
}

View File

@@ -1,40 +0,0 @@
import { AICFormation } from "./aicformation";
import { AICFormationContextDataInterface } from "./aicformationdescriptor";
import { AICFormationDescriptorPhrase } from "./aicformationdescriptorphrase";
export interface AICFormationDescriptorSectionInterface {
"generate" : CallableFunction,
"label" : string,
"name" : string,
"omitSection" : boolean
}
export abstract class AICFormationDescriptorSection {
#phrases : AICFormationDescriptorPhrase[] = [];
label = "";
name = "";
omitSection = false;
constructor() {
}
addPhrase( phrase:AICFormationDescriptorPhrase ) {
this.#phrases.push( phrase );
}
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
return this;
}
getPhrases() {
return this.#phrases;
}
}

View File

@@ -1,39 +0,0 @@
import { AICFormation } from "../aicformation";
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
export class AICFormationDescriptorSection_Formation extends AICFormationDescriptorSection {
label = "Formation";
name = "formation";
constructor() {
super();
}
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
if ( !formation.showFormationNameInDescriptor() ) {
this.omitSection = true;
return this;
}
let phrase = new AICFormationDescriptorPhrase();
phrase.addComponent( new AICFormationDescriptorComponent( formation.label, "Formation" ) );
phrase = formation.addToDescriptorPhrase( this, phrase, contextData );
this.addPhrase( phrase );
return this;
}
}

View File

@@ -1,35 +0,0 @@
import { AICFormation } from "../aicformation";
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
export class AICFormationDescriptorSection_NumGroups extends AICFormationDescriptorSection {
label = "Groups";
name = "numgroups";
constructor() {
super();
}
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
let value = "Single group";
if ( contextData.numGroups > 1 ) {
value = contextData.numGroups + " groups";
}
let phrase = new AICFormationDescriptorPhrase();
phrase.addComponent( new AICFormationDescriptorComponent( value, "Number of groups" ) );
this.addPhrase( phrase );
return this;
}
}

View File

@@ -1,83 +0,0 @@
import { AICFormation } from "../aicformation";
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
interface addUnitInformationInterface {
omitTrack?: boolean
}
export class AICFormationDescriptorSection_Unit extends AICFormationDescriptorSection {
label = "Unit";
name = "unit";
constructor() {
super();
}
addUnitInformation( formation:AICFormation, contextData: AICFormationContextDataInterface, phrase: AICFormationDescriptorPhrase, options?:addUnitInformationInterface ) {
options = options || {};
const originPoint = ( contextData.control === "broadcast" ) ? contextData.bullseyeName : "BRAA";
phrase.addComponent( new AICFormationDescriptorComponent( originPoint, "Bearing origin point" ) );
phrase.addComponent( new AICFormationDescriptorComponent( "<bearing>", "Bearing" ) );
phrase.addComponent( new AICFormationDescriptorComponent( "<range>", "Range" ) );
phrase.addComponent( new AICFormationDescriptorComponent( "<altitude>", "Altitude" ) );
if ( contextData.control === "broadcast" ) {
if ( !options.hasOwnProperty( "omitTrack" ) || options.omitTrack !== true ) {
phrase.addComponent( new AICFormationDescriptorComponent( "track <compass>", "Tracking" ) );
}
} else {
phrase.addComponent( new AICFormationDescriptorComponent( "[hot|flanking [left|right]|beam <compass>|cold]", "Azimuth" ) );
}
return phrase;
}
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
if ( formation.hasUnitBreakdown() ) {
for ( const [ i, unitRef ] of formation.unitBreakdown.entries() ) {
let phrase = new AICFormationDescriptorPhrase();
phrase.addComponent( new AICFormationDescriptorComponent( unitRef, "Unit reference" ) );
if ( i === 0 ) {
this.addUnitInformation( formation, contextData, phrase, { "omitTrack": true } );
} else {
phrase.addComponent( new AICFormationDescriptorComponent( "<altitude>" ) );
}
phrase.addComponent( new AICFormationDescriptorComponent( "hostile" ) );
this.addPhrase( phrase );
}
} else {
this.addPhrase(
this.addUnitInformation( formation, contextData, new AICFormationDescriptorPhrase() )
);
}
return this;
}
}

View File

@@ -1,35 +0,0 @@
import { AICFormation } from "../aicformation";
import { AICFormationContextDataInterface } from "../aicformationdescriptor";
import { AICFormationDescriptorSection } from "../aicformationdescriptorsection";
import { AICFormationDescriptorComponent } from "../aicformationdescriptorcomponent";
import { AICFormationDescriptorPhrase } from "../aicformationdescriptorphrase";
export class AICFormationDescriptorSection_Who extends AICFormationDescriptorSection {
label = "Who";
name = "who";
constructor() {
super();
}
generate( formation:AICFormation, contextData: AICFormationContextDataInterface ) {
let phrase = new AICFormationDescriptorPhrase();
if ( contextData.control === "tactical" ) {
phrase.addComponent( new AICFormationDescriptorComponent( "<their callsign>", "Their callsign" ) );
}
phrase.addComponent( new AICFormationDescriptorComponent( contextData.aicCallsign, "Your callsign" ) );
this.addPhrase( phrase );
return this;
}
}

View File

@@ -1,188 +0,0 @@
import { getMissionHandler } from "..";
import { convertDateAndTimeToDate } from "../other/utils";
import { getConnected } from "../server/server";
import { ATCBoard } from "./atcboard";
import { ATCBoardGround } from "./board/ground";
import { ATCBoardTower } from "./board/tower";
export interface FlightInterface {
assignedSpeed: any;
assignedAltitude : any;
id : string;
boardId : string;
name : string;
order : number;
status : "unknown";
takeoffTime : number;
unitId : number;
}
class ATCDataHandler {
#atc:ATC;
#flights:{[key:string]: FlightInterface} = {};
#updateInterval:number|undefined = undefined;
#updateIntervalDelay:number = 2500; // Wait between unit update requests
constructor( atc:ATC ) {
this.#atc = atc;
}
getFlights( boardId:string ) {
return Object.values( this.#flights ).reduce( ( acc:{[key:string]: FlightInterface}, flight ) => {
if ( flight.boardId === boardId ) {
acc[ flight.id ] = flight;
}
return acc;
}, {} );
}
startUpdates() {
this.#updateInterval = window.setInterval( () => {
if ( !getConnected() ) {
return;
}
const aBoardIsVisible = this.#atc.getBoards().some( board => board.boardIsVisible() );
if ( aBoardIsVisible ) {
fetch( '/api/atc/flight', {
method: 'GET',
headers: {
'Accept': '*/*',
'Content-Type': 'application/json'
}
})
.then( response => response.json() )
.then( data => {
this.setFlights( data );
});
}
}, this.#updateIntervalDelay );
}
setFlights( flights:{[key:string]: any} ) {
this.#flights = flights;
}
stopUpdates() {
clearInterval( this.#updateInterval );
}
}
export class ATC {
#boards:ATCBoard[] = [];
#dataHandler:ATCDataHandler;
#initDate:Date = new Date();
constructor() {
this.#dataHandler = new ATCDataHandler( this );
this.lookForBoards();
}
addBoard<T extends ATCBoard>( board:T ) {
board.startUpdates();
this.#boards.push( board );
}
getBoards() {
return this.#boards;
}
getDataHandler() {
return this.#dataHandler;
}
getMissionElapsedSeconds() : number {
return new Date().getTime() - this.#initDate.getTime();
}
getMissionStartDateTime() : Date {
return new Date( 1990, 3, 1, 18, 0, 0 );
}
getMissionDate() : Date {
return convertDateAndTimeToDate(getMissionHandler().getDateAndTime());
}
lookForBoards() {
document.querySelectorAll( ".ol-strip-board" ).forEach( board => {
if ( board instanceof HTMLElement ) {
switch ( board.dataset.boardType ) {
case "ground":
this.addBoard( new ATCBoardGround( this, board ) );
return;
case "tower":
this.addBoard( new ATCBoardTower( this, board ) );
return;
default:
console.warn( "Unknown board type for ATC board, got: " + board.dataset.boardType );
}
}
});
}
startUpdates() {
this.#dataHandler.startUpdates();
}
stopUpdates() {
this.#dataHandler.stopUpdates();
}
}

View File

@@ -1,527 +0,0 @@
import { Dropdown } from "../controls/dropdown";
import { zeroAppend } from "../other/utils";
import { ATC } from "./atc";
import { Unit } from "../unit/unit";
import { getMissionHandler, getUnitsManager } from "..";
import Sortable from "sortablejs";
import { FlightInterface } from "./atc";
import { getConnected } from "../server/server";
export interface StripBoardStripInterface {
"id": string,
"element": HTMLElement,
"dropdowns": {[key:string]: Dropdown},
"isDeleted"?: boolean,
"unitId": number
}
export abstract class ATCBoard {
#atc:ATC;
#boardId:string = "";
#templates: {[key:string]: string} = {};
// Elements
#boardElement:HTMLElement;
#clockElement:HTMLElement;
#stripBoardElement:HTMLElement;
// Content
#isAddFlightByClickEnabled:boolean = false;
#strips:{[key:string]: StripBoardStripInterface} = {};
#unitIdsBeingMonitored:number[] = [];
// Update timing
#updateInterval:number|undefined = undefined;
#updateIntervalDelay:number = 1000;
constructor( atc:ATC, boardElement:HTMLElement, options?:{[key:string]: any} ) {
options = options || {};
this.#atc = atc;
this.#boardElement = boardElement;
this.#stripBoardElement = <HTMLElement>this.getBoardElement().querySelector( ".ol-strip-board-strips" );
this.#clockElement = <HTMLElement>this.getBoardElement().querySelector( ".ol-strip-board-clock" );
new MutationObserver( () => {
if ( this.boardIsVisible() ) {
this.startUpdates();
} else {
this.stopUpdates();
}
}).observe( this.getBoardElement(), {
"attributes": true,
"childList": false,
"subtree": false
});
new Sortable( this.getStripBoardElement(), {
"handle": ".handle",
"onUpdate": ev => {
const order = [].slice.call( this.getStripBoardElement().children ).map( ( strip:HTMLElement ) => {
return strip.dataset.flightId
});
fetch( '/api/atc/flight/order', {
method: 'POST',
headers: {
'Accept': '*/*',
'Content-Type': 'application/json'
},
"body": JSON.stringify({
"boardId" : this.getBoardId(),
"order" : order
})
});
}
});
window.setInterval( () => {
if ( !getConnected() ) {
return;
}
this.updateClock();
}, 1000 );
if ( this.#boardElement.classList.contains( "ol-draggable" ) ) {
let options:any = {};
let handle = this.#boardElement.querySelector( ".handle" );
if ( handle instanceof HTMLElement ) {
options.handle = handle;
}
}
this.#setupAddFlight();
// this.#_setupDemoData();
}
addFlight( unit:Unit ) {
const baseData = unit.getData();
const unitCanBeAdded = () => {
if ( unit.getCategory() !== "Aircraft" ) {
return false;
}
if ( baseData.controlled === true ) {
// return false;
}
if ( this.#unitIdsBeingMonitored.includes( unit.ID ) ) {
return false;
}
return true;
}
if ( !unitCanBeAdded() ) {
return;
}
this.#unitIdsBeingMonitored.push( unit.ID );
return fetch( '/api/atc/flight/', {
method: 'POST',
headers: {
'Accept': '*/*',
'Content-Type': 'application/json'
},
"body": JSON.stringify({
"boardId" : this.getBoardId(),
"name" : baseData.unitName,
"unitId" : unit.ID
})
});
}
addStrip( strip:StripBoardStripInterface ) {
this.#strips[ strip.id ] = strip;
strip.element.querySelectorAll( "button.deleteFlight" ).forEach( btn => {
btn.addEventListener( "click", ev => {
ev.preventDefault();
this.deleteFlight( strip.id );
});
});
}
boardIsVisible() {
return ( !this.getBoardElement().classList.contains( "hide" ) );
}
calculateTimeToGo( fromTimestamp:number, toTimestamp:number ) {
let timestamp = ( toTimestamp - fromTimestamp ) / 1000;
const hasElapsed = ( timestamp < 0 ) ? true : false;
if ( hasElapsed ) {
timestamp = -( timestamp );
}
const hours = ( timestamp < 3600 ) ? "00" : zeroAppend( Math.floor( timestamp / 3600 ), 2 );
const rMinutes = timestamp % 3600;
const minutes = ( timestamp < 60 ) ? "00" : zeroAppend( Math.floor( rMinutes / 60 ), 2 );
const seconds = zeroAppend( Math.floor( rMinutes % 60 ), 2 );
return {
"elapsedMarker": ( hasElapsed ) ? "+" : "-",
"hasElapsed": hasElapsed,
"hours": hours,
"minutes": minutes,
"seconds": seconds,
"time": `${hours}:${minutes}:${seconds}`,
"totalSeconds": timestamp
};
}
deleteStrip( flightId:string ) {
if ( this.#strips.hasOwnProperty( flightId ) ) {
this.#strips[ flightId ].element.remove();
this.#strips[ flightId ].isDeleted = true;
window.setTimeout( () => {
delete this.#strips[ flightId ];
}, 10000 );
}
}
deleteFlight( flightId:string ) {
this.deleteStrip( flightId );
fetch( '/api/atc/flight/' + flightId, {
method: 'DELETE',
headers: {
'Accept': '*/*',
'Content-Type': 'application/json'
},
"body": JSON.stringify({
"boardId": this.getBoardId()
})
});
}
getATC() {
return this.#atc;
}
getBoardElement() {
return this.#boardElement;
}
getBoardId(): string {
return this.getBoardElement().id;
}
getStripBoardElement() {
return this.#stripBoardElement;
}
getStrips() {
return this.#strips;
}
getStrip( id:string ) {
return this.#strips[ id ] || false;
}
getTemplate( key:string ) {
return this.#templates[ key ] || false;
}
getUnitIdsBeingMonitored() {
return this.#unitIdsBeingMonitored;
}
setTemplates( templates:{[key:string]: string} ) {
this.#templates = templates;
}
#setupAddFlight() {
const toggleIsAddFlightByClickEnabled = () => {
this.#isAddFlightByClickEnabled = ( !this.#isAddFlightByClickEnabled );
this.getBoardElement().classList.toggle( "add-flight-by-click", this.#isAddFlightByClickEnabled );
}
document.addEventListener( "unitSelection", ( ev:CustomEventInit ) => {
if ( this.#isAddFlightByClickEnabled !== true ) {
return;
}
this.addFlight( ev.detail );
toggleIsAddFlightByClickEnabled();
});
const form = <HTMLElement>this.getBoardElement().querySelector( "form.ol-strip-board-add-flight" );
const suggestions = <HTMLElement>form.querySelector( ".ol-auto-suggest" );
const unitName = <HTMLInputElement>form.querySelector( "input[name='unitName']" );
const toggleSuggestions = ( bool:boolean ) => {
suggestions.toggleAttribute( "data-has-suggestions", bool );
}
let searchTimeout:number|null;
unitName.addEventListener( "keyup", ev => {
if ( searchTimeout ) {
clearTimeout( searchTimeout );
}
const resetSuggestions = () => {
suggestions.innerHTML = "";
toggleSuggestions( false );
}
resetSuggestions();
searchTimeout = window.setTimeout( () => {
const searchString = unitName.value.toLowerCase();
if ( searchString === "" ) {
return;
}
const units = getUnitsManager().getSelectableAircraft();
const unitIdsBeingMonitored = this.getUnitIdsBeingMonitored();
const results = Object.keys( units ).reduce( ( acc:Unit[], unitId:any ) => {
const unit = units[ unitId ];
const baseData = unit.getData();
if ( !unitIdsBeingMonitored.includes( parseInt( unitId ) ) && baseData.unitName.toLowerCase().indexOf( searchString ) > -1 ) {
acc.push( unit );
}
return acc;
}, [] );
toggleSuggestions( results.length > 0 );
results.forEach( unit => {
const baseData = unit.getData();
const a = document.createElement( "a" );
a.innerText = baseData.unitName;
a.addEventListener( "click", ev => {
this.addFlight( unit );
resetSuggestions();
unitName.value = "";
});
suggestions.appendChild( a );
});
}, 1000 );
});
form.querySelectorAll( ".add-flight-by-click" ).forEach( el => {
el.addEventListener( "click", ev => {
ev.preventDefault();
toggleIsAddFlightByClickEnabled();
});
});
}
sortFlights( flights:FlightInterface[] ) {
flights.sort( ( a, b ) => {
const aVal = a.order;
const bVal = b.order;
return ( aVal > bVal ) ? 1 : -1;
});
return flights;
}
startUpdates() {
if ( !this.boardIsVisible() ) {
return;
}
this.#updateInterval = window.setInterval( () => {
if ( !getConnected() ) {
return;
}
this.update();
}, this.#updateIntervalDelay );
}
stopUpdates() {
clearInterval( this.#updateInterval );
}
timestampToLocaleTime( timestamp:number ) {
return ( timestamp === -1 ) ? "-" : new Date( timestamp ).toLocaleTimeString();
}
timeToGo( timestamp:number ) {
const timeData = this.calculateTimeToGo( this.getATC().getMissionDate().getTime(), timestamp );
return ( timestamp === -1 ) ? "-" : timeData.elapsedMarker + timeData.time;
}
protected update() {
console.warn( "No custom update method defined." );
}
updateClock() {
const missionTime = this.#atc.getMissionDate().getTime();
const timeDiff = new Date().getTime() - getMissionHandler().getDateAndTime().elapsedTime;
const nowDate = new Date( missionTime + timeDiff );
this.#clockElement.innerText = nowDate.toLocaleTimeString();
}
updateFlight( flightId:string, reqBody:object ) {
return fetch( '/api/atc/flight/' + flightId, {
method: 'PATCH',
headers: {
'Accept': '*/*',
'Content-Type': 'application/json'
},
"body": JSON.stringify( reqBody )
});
}
#_setupDemoData() {
fetch( '/api/atc/flight/', {
method: 'POST',
headers: {
'Accept': '*/*',
'Content-Type': 'application/json'
},
"body": JSON.stringify({
"boardId" : this.getBoardId(),
"name" : this.getBoardId() + " 1",
"unitId" : 1
})
});
// fetch( '/api/atc/flight/', {
// method: 'POST',
// headers: {
// 'Accept': '*/*',
// 'Content-Type': 'application/json'
// },
// "body": JSON.stringify({
// "boardId" : this.getBoardId(),
// "name" : this.getBoardId() + " 2",
// "unitId" : 2
// })
// });
// fetch( '/api/atc/flight/', {
// method: 'POST',
// headers: {
// 'Accept': '*/*',
// 'Content-Type': 'application/json'
// },
// "body": JSON.stringify({
// "boardId" : this.getBoardId(),
// "name" : this.getBoardId() + " 3",
// "unitId" : 9
// })
// });
}
}

View File

@@ -1,200 +0,0 @@
import { Dropdown } from "../../controls/dropdown";
import { ATC } from "../atc";
import { ATCBoard } from "../atcboard";
export class ATCBoardGround extends ATCBoard {
constructor( atc:ATC, element:HTMLElement ) {
super( atc, element );
}
update() {
const flights = this.sortFlights( Object.values( this.getATC().getDataHandler().getFlights( this.getBoardId() ) ) );
const stripBoard = this.getStripBoardElement();
const missionTime = this.getATC().getMissionDate().getTime();
for( const strip of stripBoard.children ) {
strip.toggleAttribute( "data-updating", true );
}
flights.forEach( flight => {
let strip = this.getStrip( flight.id );
if ( !strip ) {
const template = `<div class="ol-strip-board-strip" data-flight-id="${flight.id}" data-flight-status="${flight.status}">
<div class="handle"></div>
<div data-point="name">${flight.name}</div>
<div id="flight-status-${flight.id}" class="ol-select narrow" data-point="status">
<div class="ol-select-value">${flight.status}</div>
<div class="ol-select-options"></div>
</div>
<div data-point="takeoffTime"><input type="text" name="takeoffTime" value="${this.timestampToLocaleTime( flight.takeoffTime )}" /></div>
<div data-point="timeToGo">${this.timeToGo( flight.takeoffTime )}</div>
<button class="deleteFlight">&times;</button>
</div>`;
stripBoard.insertAdjacentHTML( "beforeend", template );
strip = {
"id": flight.id,
"element": <HTMLElement>stripBoard.lastElementChild,
"dropdowns": {},
"unitId": -1
};
strip.element.querySelectorAll( ".ol-select" ).forEach( select => {
switch( select.getAttribute( "data-point" ) ) {
case "status":
strip.dropdowns.status = new Dropdown( select.id, ( value:string, ev:MouseEvent ) => {
fetch( '/api/atc/flight/' + flight.id, {
method: 'PATCH',
headers: {
'Accept': '*/*',
'Content-Type': 'application/json'
},
"body": JSON.stringify({
"status": value
})
});
}, [
"unknown", "checkedin", "readytotaxi", "clearedtotaxi", "halted", "terminated"
]);
break;
}
});
strip.element.querySelectorAll( `input[type="text"]` ).forEach( input => {
if ( input instanceof HTMLInputElement ) {
input.addEventListener( "blur", ( ev ) => {
const target = ev.target;
if ( target instanceof HTMLInputElement ) {
if ( /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/.test( target.value ) ) {
target.value += ":00";
}
const value = target.value;
if ( value === target.dataset.previousValue ) {
return;
} else if ( value === "" ) {
this.#updateTakeoffTime( flight.id, -1 );
} else if ( /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/.test( value ) ) {
let [ hours, minutes, seconds ] = value.split( ":" ).map( str => parseInt( str ) );
const missionStart = this.getATC().getMissionStartDateTime();
this.#updateTakeoffTime( flight.id, new Date(
missionStart.getFullYear(),
missionStart.getMonth(),
missionStart.getDate(),
hours,
minutes,
seconds
).getTime() );
} else {
target.value === target.dataset.previousValue
}
}
});
}
});
this.addStrip( strip );
} else {
if ( flight.status !== strip.element.getAttribute( "data-flight-status" ) ) {
strip.element.setAttribute( "data-flight-status", flight.status );
strip.dropdowns.status.selectText( flight.status );
}
strip.element.querySelectorAll( `input[name="takeoffTime"]:not(:focus)` ).forEach( el => {
if ( el instanceof HTMLInputElement ) {
el.value = this.timestampToLocaleTime( flight.takeoffTime );
el.dataset.previousValue = el.value;
}
});
strip.element.querySelectorAll( `[data-point="timeToGo"]` ).forEach( el => {
if ( flight.takeoffTime > 0 && this.calculateTimeToGo( missionTime, flight.takeoffTime ).totalSeconds <= 120 ) {
strip.element.setAttribute( "data-time-warning", "level-1" );
}
if ( el instanceof HTMLElement ) {
el.innerText = this.timeToGo( flight.takeoffTime );
}
});
}
strip.element.toggleAttribute( "data-updating", false );
});
stripBoard.querySelectorAll( `[data-updating]` ).forEach( strip => {
this.deleteStrip( strip.getAttribute( "data-flight-id" ) || "" );
});
}
#updateTakeoffTime = function( flightId:string, time:number ) {
fetch( '/api/atc/flight/' + flightId, {
method: 'PATCH',
headers: {
'Accept': '*/*',
'Content-Type': 'application/json'
},
"body": JSON.stringify({
"takeoffTime": time
})
});
}
}

View File

@@ -1,194 +0,0 @@
import { getUnitsManager } from "../..";
import { Dropdown } from "../../controls/dropdown";
import { mToFt, msToKnots } from "../../other/utils";
import { ATC } from "../atc";
import { ATCBoard } from "../atcboard";
export class ATCBoardTower extends ATCBoard {
constructor( atc:ATC, element:HTMLElement ) {
super( atc, element );
}
update() {
const flights = this.sortFlights( Object.values( this.getATC().getDataHandler().getFlights( this.getBoardId() ) ) );
const missionTime = this.getATC().getMissionDate().getTime();
const selectableUnits = getUnitsManager().getSelectableAircraft();
const stripBoard = this.getStripBoardElement();
for( const strip of stripBoard.children ) {
strip.toggleAttribute( "data-updating", true );
}
flights.forEach( flight => {
let strip = this.getStrip( flight.id );
if ( strip.isDeleted === true ) {
return;
}
const flightData = {
latitude: -1,
longitude: -1,
altitude: -1,
heading: -1,
speed: -1,
...( selectableUnits.hasOwnProperty( flight.unitId ) ? selectableUnits[flight.unitId].getData() : {} )
};
if ( !strip ) {
const template = `<div class="ol-strip-board-strip" data-flight-id="${flight.id}" data-flight-status="${flight.status}">
<div class="handle"></div>
<div data-point="name"><a href="#" class="select-unit">${flight.name}</a></div>
<div data-point="assignedAltitude"><input type="text" name="assignedAltitude" value="${flight.assignedAltitude}" size="2" /> 000</div>
<div data-point="altitude">-</div>
<div data-point="assignedSpeed"><input type="text" name="assignedSpeed" value="${flight.assignedSpeed}" size="3" /></div>
<div data-point="speed">-</div>
<button class="deleteFlight">&times;</button>
</div>`;
stripBoard.insertAdjacentHTML( "beforeend", template );
strip = {
"id": flight.id,
"element": <HTMLElement>stripBoard.lastElementChild,
"dropdowns": {},
"unitId": flight.unitId
};
strip.element.querySelectorAll( `input[type="text"]` ).forEach( input => {
if ( input instanceof HTMLInputElement ) {
switch ( input.name ) {
case "assignedAltitude":
input.addEventListener( "change", ( ev ) => {
let val = parseInt( input.value.replace( /[^\d]/g, "" ) );
if ( isNaN( val ) || val < 0 || val > 40 ) {
val = 0;
}
this.updateFlight( flight.id, {
"assignedAltitude": val
});
});
break;
case "assignedSpeed":
input.addEventListener( "change", ( ev ) => {
let val = parseInt( input.value.replace( /[^\d]/g, "" ) );
if ( isNaN( val ) || val < 0 || val > 750 ) {
val = 0;
}
this.updateFlight( flight.id, {
"assignedSpeed": val
});
});
break;
}
}
});
strip.element.querySelectorAll( ".select-unit" ).forEach( el => {
el.addEventListener( "click", ev => {
ev.preventDefault();
getUnitsManager().selectUnit( flight.unitId );
});
});
this.addStrip( strip );
} else {
//
// Altitude
//
let assignedAltitude = <HTMLInputElement>strip.element.querySelector( `input[name="assignedAltitude"]`);
if ( !assignedAltitude.matches( ":focus" ) && assignedAltitude.value !== flight.assignedAltitude ) {
assignedAltitude.value = flight.assignedAltitude;
}
flightData.altitude = Math.floor( mToFt(flightData.altitude) );
strip.element.querySelectorAll( `[data-point="altitude"]` ).forEach( el => {
if ( el instanceof HTMLElement ) {
el.innerText = "" + flightData.altitude;
}
});
const altitudeDelta = ( flight.assignedAltitude === 0 ) ? 0 : ( flight.assignedAltitude * 1000 ) - flightData.altitude;
strip.element.toggleAttribute( "data-altitude-assigned", ( flight.assignedAltitude > 0 ) );
strip.element.toggleAttribute( "data-warning-altitude", ( altitudeDelta >= 300 || altitudeDelta <= -300 ) );
//
// Speed
//
let assignedSpeed = <HTMLInputElement>strip.element.querySelector( `input[name="assignedSpeed"]`);
if ( !assignedSpeed.matches( ":focus" ) && assignedSpeed.value !== flight.assignedSpeed ) {
assignedSpeed.value = flight.assignedSpeed;
}
flightData.speed = Math.floor( msToKnots(flightData.speed) );
strip.element.querySelectorAll( `[data-point="speed"]` ).forEach( el => {
if ( el instanceof HTMLElement ) {
el.innerText = "" + flightData.speed;
}
});
const speedDelta = ( flight.assignedSpeed === 0 ) ? 0 : flight.assignedSpeed - flightData.speed;
strip.element.toggleAttribute( "data-speed-assigned", ( flight.assignedSpeed > 0 ) );
strip.element.toggleAttribute( "data-warning-speed", ( speedDelta >= 25 || speedDelta <= -25 ) );
}
strip.element.toggleAttribute( "data-updating", false );
});
stripBoard.querySelectorAll( `[data-updating]` ).forEach( strip => {
this.deleteStrip( strip.getAttribute( "data-flight-id" ) || "" );
});
}
}

View File

@@ -1,57 +0,0 @@
import { getUnitsManager } from "..";
import { Panel } from "../panels/panel";
import { Unit } from "../unit/unit";
export class UnitDataTable extends Panel {
constructor(id: string) {
super(id);
this.hide();
}
update() {
var units = getUnitsManager().getUnits();
const unitsArray = Object.values(units).sort((a: Unit, b: Unit) => {
const aVal = a.getUnitName()?.toLowerCase();
const bVal = b.getUnitName()?.toLowerCase();
if (aVal > bVal) {
return 1;
} else if (bVal > aVal) {
return -1;
} else {
return 0;
}
});
function addRow(parentEl: HTMLElement, columns: string[]) {
const rowDiv = document.createElement("div");
for (const item of columns) {
const div = document.createElement("div");
div.innerText = item;
rowDiv.appendChild(div);
}
parentEl.appendChild(rowDiv);
}
const el = <HTMLElement> this.getElement().querySelector("#unit-list");
if (el) {
el.innerHTML = "";
addRow(el, ["Callsign", "Name", "Category", "AI/Human"])
for (const unit of unitsArray) {
const dataset = [unit.getUnitName(), unit.getName(), unit.getCategory(), (unit.getControlled()) ? "AI" : "Human"];
addRow(el, dataset);
}
}
}
}

View File

@@ -1,5 +1,13 @@
import { LatLng, LatLngBounds } from "leaflet";
export const UNITS_URI = "units";
export const WEAPONS_URI = "weapons";
export const LOGS_URI = "logs";
export const AIRBASES_URI = "airbases";
export const BULLSEYE_URI = "bullseyes";
export const MISSION_URI = "mission";
export const COMMANDS_URI = "commands";
export const NONE = "None";
export const GAME_MASTER = "Game master";
export const BLUE_COMMANDER = "Blue commander";
@@ -135,21 +143,19 @@ export const layers = {
/* Map constants */
export const IDLE = "Idle";
export const MOVE_UNIT = "Move unit";
export const BOMBING = "Bombing";
export const CARPET_BOMBING = "Carpet bombing";
export const FIRE_AT_AREA = "Fire at area";
export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area";
export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"];
export const visibilityControlsTypes: string[][] = [["human"], ["dcs"], ["aircraft"], ["groundunit-sam", "groundunit-sam-radar", "groundunit-sam-launcher"], ["groundunit-other", "groundunit-ewr"], ["navyunit"], ["airbase"]];
export const visibilityControlsTootlips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"];
export const visibilityControlsTooltips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"];
export const IADSTypes = ["AAA", "MANPADS", "SAM Site", "Radar"];
export const IADSDensities: {[key: string]: number}= {"AAA": 0.8, "MANPADS": 0.3, "SAM Site": 0.1, "Radar": 0.05};
export const SHOW_CONTACT_LINES = "Show unit contact lines";
export const HIDE_GROUP_MEMBERS = "Hide group members when zoomed out";
export const SHOW_UNIT_PATHS = "Show unit paths";
export const SHOW_UNIT_TARGETS = "Show unit targets";
export const SHOW_UNIT_LABELS = "Show unit labels";
export const SHOW_UNIT_PATHS = "Show unit paths";
export const SHOW_UNIT_TARGETS = "Show unit targets";
export enum DataIndexes {
startOfData = 0,

View File

@@ -1,16 +1,24 @@
import { getMap, getMissionHandler, getUnitsManager, setActiveCoalition } from "..";
import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "../constants/constants";
import { GAME_MASTER } from "../constants/constants";
import { Airbase } from "../mission/airbase";
import { dataPointMap } from "../other/utils";
import { Unit } from "../unit/unit";
import { ContextMenu } from "./contextmenu";
/** This context menu is shown to the user when the airbase marker is right clicked on the map.
* It allows the user to inspect information about the airbase, as well as allowing to spawn units from the airbase itself and land units on it. */
export class AirbaseContextMenu extends ContextMenu {
#airbase: Airbase | null = null;
constructor(id: string) {
super(id);
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
document.addEventListener("contextMenuSpawnAirbase", (e: any) => {
this.showSpawnMenu();
this.#showSpawnMenu();
})
document.addEventListener("contextMenuLandAirbase", (e: any) => {
@@ -20,27 +28,39 @@ export class AirbaseContextMenu extends ContextMenu {
})
}
/** Sets the airbase for which data will be shown in the context menu
*
* @param airbase The airbase for which data will be shown in the context menu. Note: the airbase must be present in the public/databases/airbases/<theatre>.json database.
*/
setAirbase(airbase: Airbase) {
this.#airbase = airbase;
this.setName(this.#airbase.getName());
this.setProperties(this.#airbase.getProperties());
this.setParkings(this.#airbase.getParkings());
this.setCoalition(this.#airbase.getCoalition());
this.enableLandButton(getUnitsManager().getSelectedUnitsTypes().length == 1 && ["Aircraft", "Helicopter"].includes(getUnitsManager().getSelectedUnitsTypes()[0]) && (getUnitsManager().getSelectedUnitsCoalition() === this.#airbase.getCoalition() || this.#airbase.getCoalition() === "neutral"))
this.enableSpawnButton(getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || this.#airbase.getCoalition() == getMissionHandler().getCommandedCoalition());
this.#setName(this.#airbase.getName());
this.#setProperties(this.#airbase.getProperties());
this.#setParkings(this.#airbase.getParkings());
this.#setCoalition(this.#airbase.getCoalition());
this.#showLandButton(getUnitsManager().getSelectedUnitsTypes().length == 1 && ["Aircraft", "Helicopter"].includes(getUnitsManager().getSelectedUnitsTypes()[0]) && (getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) === this.#airbase.getCoalition() || this.#airbase.getCoalition() === "neutral"))
this.#showSpawnButton(getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || this.#airbase.getCoalition() == getMissionHandler().getCommandedCoalition());
this.#setAirbaseData();
this.clip();
}
setName(airbaseName: string) {
/**
*
* @param airbaseName The name of the airbase
*/
#setName(airbaseName: string) {
var nameDiv = <HTMLElement>this.getContainer()?.querySelector("#airbase-name");
if (nameDiv != null)
nameDiv.innerText = airbaseName;
}
setProperties(airbaseProperties: string[]) {
/**
*
* @param airbaseProperties The properties of the airbase
*/
#setProperties(airbaseProperties: string[]) {
this.getContainer()?.querySelector("#airbase-properties")?.replaceChildren(...airbaseProperties.map((property: string) => {
var div = document.createElement("div");
div.innerText = property;
@@ -48,7 +68,11 @@ export class AirbaseContextMenu extends ContextMenu {
}),);
}
setParkings(airbaseParkings: string[]) {
/**
*
* @param airbaseParkings List of available parkings at the airbase
*/
#setParkings(airbaseParkings: string[]) {
this.getContainer()?.querySelector("#airbase-parking")?.replaceChildren(...airbaseParkings.map((parking: string) => {
var div = document.createElement("div");
div.innerText = parking;
@@ -56,31 +80,43 @@ export class AirbaseContextMenu extends ContextMenu {
}));
}
setCoalition(coalition: string) {
(<HTMLElement>this.getContainer()?.querySelector("#spawn-airbase-aircraft-button")).dataset.coalition = coalition;
/**
*
* @param coalition Coalition to which the airbase belongs
*/
#setCoalition(coalition: string) {
(this.getContainer()?.querySelector("#spawn-airbase-aircraft-button") as HTMLElement).dataset.coalition = coalition;
}
enableSpawnButton(enableSpawnButton: boolean) {
this.getContainer()?.querySelector("#spawn-airbase-aircraft-button")?.classList.toggle("hide", !enableSpawnButton);
/**
*
* @param showSpawnButton If true, the spawn button will be visibile
*/
#showSpawnButton(showSpawnButton: boolean) {
this.getContainer()?.querySelector("#spawn-airbase-aircraft-button")?.classList.toggle("hide", !showSpawnButton);
}
enableLandButton(enableLandButton: boolean) {
this.getContainer()?.querySelector("#land-here-button")?.classList.toggle("hide", !enableLandButton);
/**
*
* @param showLandButton If true, the land button will be visible
*/
#showLandButton(showLandButton: boolean) {
this.getContainer()?.querySelector("#land-here-button")?.classList.toggle("hide", !showLandButton);
}
showSpawnMenu() {
/** Shows the spawn context menu which allows the user to select a unit to ground spawn at the airbase
*
*/
#showSpawnMenu() {
if (this.#airbase != null) {
setActiveCoalition(this.#airbase.getCoalition());
getMap().showMapContextMenu(this.getX(), this.getY(), this.getLatLng());
getMap().getMapContextMenu().hideUpperBar();
getMap().getMapContextMenu().hideLowerBar();
getMap().getMapContextMenu().hideAltitudeSlider();
getMap().getMapContextMenu().showSubMenu("aircraft");
getMap().getMapContextMenu().setAirbaseName(this.#airbase.getName());
getMap().getMapContextMenu().setLatLng(this.#airbase.getLatLng());
getMap().showAirbaseSpawnMenu(this.getX(), this.getY(), this.getLatLng(), this.#airbase);
}
}
/** @todo needs commenting
*
*/
#setAirbaseData() {
if (this.#airbase && this.getContainer()) {
dataPointMap(this.getContainer() as HTMLElement, {

View File

@@ -0,0 +1,105 @@
import { LatLng } from "leaflet";
import { getActiveCoalition } from "..";
import { ContextMenu } from "./contextmenu";
import { AircraftSpawnMenu, HelicopterSpawnMenu } from "../controls/unitspawnmenu";
import { Airbase } from "../mission/airbase";
/** This context menu is shown when the user wants to spawn a new aircraft or helicopter from the ground at an airbase.
* It is shown by clicking on the "spawn" button of a AirbaseContextMenu. */
export class AirbaseSpawnContextMenu extends ContextMenu {
#aircraftSpawnMenu: AircraftSpawnMenu;
#helicopterSpawnMenu: HelicopterSpawnMenu;
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
/* Create the spawn menus for the different unit types */
this.#aircraftSpawnMenu = new AircraftSpawnMenu("airbase-aircraft-spawn-menu");
this.#helicopterSpawnMenu = new HelicopterSpawnMenu("airbase-helicopter-spawn-menu");
this.#aircraftSpawnMenu.getAltitudeSlider().hide();
this.#helicopterSpawnMenu.getAltitudeSlider().hide();
/* Event listeners */
document.addEventListener("mapContextMenuShow", (e: any) => {
if (this.getVisibleSubMenu() !== e.detail.type)
this.#showSubMenu(e.detail.type);
else
this.#hideSubMenus();
});
this.#aircraftSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
this.#helicopterSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
this.#aircraftSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
this.#helicopterSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
this.hide();
}
/** Show the context menu
*
* @param x X screen coordinate of the top left corner of the context menu
* @param y Y screen coordinate of the top left corner of the context menu
*/
show(x: number, y: number) {
super.show(x, y, new LatLng(0, 0));
this.#aircraftSpawnMenu.setAirbase(undefined);
this.#helicopterSpawnMenu.setAirbase(undefined);
this.#aircraftSpawnMenu.setCountries();
this.#helicopterSpawnMenu.setCountries();
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
}
/** Sets the airbase at which the new unit will be spawned
*
* @param airbase The airbase at which the new unit will be spawned. Note: if the airbase has no suitable parking spots, the airplane may be spawned on the runway, or spawning may fail.
*/
setAirbase(airbase: Airbase) {
this.#aircraftSpawnMenu.setAirbase(airbase);
this.#helicopterSpawnMenu.setAirbase(airbase);
}
/** Shows the submenu depending on unit selection
*
* @param type Submenu type, either "aircraft" or "helicopter"
*/
#showSubMenu(type: string) {
this.getContainer()?.querySelector("#airbase-aircraft-spawn-menu")?.classList.toggle("hide", type !== "aircraft");
this.getContainer()?.querySelector("#airbase-aircraft-spawn-button")?.classList.toggle("is-open", type === "aircraft");
this.getContainer()?.querySelector("#airbase-helicopter-spawn-menu")?.classList.toggle("hide", type !== "helicopter");
this.getContainer()?.querySelector("#airbase-helicopter-spawn-button")?.classList.toggle("is-open", type === "helicopter");
(this.getContainer()?.querySelectorAll(".deploy-unit-button"))?.forEach((element: Node) => { (element as HTMLButtonElement).disabled = true; })
this.#aircraftSpawnMenu.reset();
this.#aircraftSpawnMenu.setCountries();
this.#helicopterSpawnMenu.reset();
this.#helicopterSpawnMenu.setCountries();
this.setVisibleSubMenu(type);
this.clip();
}
/** Hide all the open submenus
*
*/
#hideSubMenus() {
this.getContainer()?.querySelector("#airbase-aircraft-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#airbase-aircraft-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#airbase-helicopter-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#airbase-helicopter-spawn-button")?.classList.toggle("is-open", false);
this.#aircraftSpawnMenu.reset();
this.#helicopterSpawnMenu.reset();
this.setVisibleSubMenu(null);
this.clip();
}
}

View File

@@ -1,14 +1,15 @@
import { LatLng } from "leaflet";
import { getMap, getMissionHandler, getUnitsManager } from "..";
import { GAME_MASTER, IADSTypes } from "../constants/constants";
import { CoalitionArea } from "../map/coalitionarea";
import { CoalitionArea } from "../map/coalitionarea/coalitionarea";
import { ContextMenu } from "./contextmenu";
import { Dropdown } from "./dropdown";
import { Slider } from "./slider";
import { Switch } from "./switch";
import { groundUnitDatabase } from "../unit/groundunitdatabase";
import { Dropdown } from "../controls/dropdown";
import { Slider } from "../controls/slider";
import { Switch } from "../controls/switch";
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
import { createCheckboxOption, getCheckboxOptions } from "../other/utils";
/** This context menu allows the user to edit or delete a CoalitionArea. Moreover, it allows the user to create a IADS automatically using the CoalitionArea as bounds. */
export class CoalitionAreaContextMenu extends ContextMenu {
#coalitionSwitch: Switch;
#coalitionArea: CoalitionArea | null = null;
@@ -18,16 +19,25 @@ export class CoalitionAreaContextMenu extends ContextMenu {
#iadsErasDropdown: Dropdown;
#iadsRangesDropdown: Dropdown;
constructor(id: string) {
super(id);
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
/* Create the coalition switch */
this.#coalitionSwitch = new Switch("coalition-area-switch", (value: boolean) => this.#onSwitchClick(value));
this.#coalitionSwitch.setValue(false);
/* Create the controls of the IADS creation submenu */
this.#iadsTypesDropdown = new Dropdown("iads-units-type-options", () => { });
this.#iadsErasDropdown = new Dropdown("iads-era-options", () => {});
this.#iadsRangesDropdown = new Dropdown("iads-range-options", () => {});
this.#iadsDensitySlider = new Slider("iads-density-slider", 5, 100, "%", (value: number) => { });
this.#iadsDistributionSlider = new Slider("iads-distribution-slider", 5, 100, "%", (value: number) => { });
/* Set the default parameters of the sliders */
this.#iadsDensitySlider.setIncrement(5);
this.#iadsDensitySlider.setValue(50);
this.#iadsDensitySlider.setActive(true);
@@ -37,9 +47,9 @@ export class CoalitionAreaContextMenu extends ContextMenu {
document.addEventListener("coalitionAreaContextMenuShow", (e: any) => {
if (this.getVisibleSubMenu() !== e.detail.type)
this.showSubMenu(e.detail.type);
this.#showSubMenu(e.detail.type);
else
this.hideSubMenus();
this.#hideSubMenus();
});
document.addEventListener("coalitionAreaBringToBack", (e: any) => {
@@ -62,6 +72,12 @@ export class CoalitionAreaContextMenu extends ContextMenu {
this.hide();
}
/**
*
* @param x X screen coordinate of the top left corner of the context menu
* @param y Y screen coordinate of the top left corner of the context menu
* @param latlng Leaflet latlng object of the mouse click
*/
show(x: number, y: number, latlng: LatLng) {
super.show(x, y, latlng);
@@ -85,26 +101,10 @@ export class CoalitionAreaContextMenu extends ContextMenu {
this.#coalitionSwitch.hide()
}
showSubMenu(type: string) {
this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", type !== "iads");
this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", type === "iads");
this.clip();
this.setVisibleSubMenu(type);
}
hideSubMenus() {
this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", false);
this.clip();
this.setVisibleSubMenu(null);
}
getCoalitionArea() {
return this.#coalitionArea;
}
/** Set the CoalitionArea object the user will be able to edit using this menu
*
* @param coalitionArea The CoalitionArea object to edit
*/
setCoalitionArea(coalitionArea: CoalitionArea) {
this.#coalitionArea = coalitionArea;
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => {
@@ -113,6 +113,41 @@ export class CoalitionAreaContextMenu extends ContextMenu {
this.#coalitionSwitch.setValue(this.getCoalitionArea()?.getCoalition() === "red");
}
/** Get the CoalitionArea object the contextmenu is editing
*
* @returns The CoalitionArea the contextmenu is editing
*/
getCoalitionArea() {
return this.#coalitionArea;
}
/** Show a submenu of the contextmenu
*
* @param type The submenu type, currently only "iads"
*/
#showSubMenu(type: string) {
this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", type !== "iads");
this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", type === "iads");
this.clip();
this.setVisibleSubMenu(type);
}
/** Hide all submenus
*
*/
#hideSubMenus() {
this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", false);
this.clip();
this.setVisibleSubMenu(null);
}
/** Callback event called when the coalition switch is clicked to change the coalition of the CoalitionArea
*
* @param value Switch position (false: blue, true: red)
*/
#onSwitchClick(value: boolean) {
if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER) {
this.getCoalitionArea()?.setCoalition(value ? "red" : "blue");

View File

@@ -1,52 +1,78 @@
import { LatLng } from "leaflet";
/** Base class for map contextmenus. By default it is empty and requires to be extended. */
export class ContextMenu {
#container: HTMLElement | null;
#latlng: LatLng = new LatLng(0, 0);
#x: number = 0;
#y: number = 0;
#visibleSubMenu: string | null = null;
#hidden: boolean = true;
constructor(id: string) {
this.#container = document.getElementById(id);
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
this.#container = document.getElementById(ID);
this.hide();
}
/** Show the contextmenu on top of the map, usually at the location where the user has clicked on it.
*
* @param x X screen coordinate of the top left corner of the context menu
* @param y Y screen coordinate of the top left corner of the context menu
* @param latlng Leaflet latlng object of the mouse click
*/
show(x: number, y: number, latlng: LatLng) {
this.#latlng = latlng;
this.#container?.classList.toggle("hide", false);
this.#x = x;
this.#y = y;
this.clip();
this.#hidden = false;
}
/** Hide the contextmenu
*
*/
hide() {
this.#container?.classList.toggle("hide", true);
this.#hidden = true;
}
/**
*
* @returns The HTMLElement that contains the contextmenu
*/
getContainer() {
return this.#container;
}
/**
*
* @returns The Leaflet latlng object associated to the click that caused the contextmenu to be shown
*/
getLatLng() {
return this.#latlng;
}
/**
*
* @returns The x coordinate of the top left corner of the menu
*/
getX() {
return this.#x;
}
/**
*
* @returns The y coordinate of the top left corner of the menu
*/
getY() {
return this.#y;
}
getHidden() {
return this.#hidden;
}
/** Clips the contextmenu, meaning it moves it on the screen to make sure it does not overflow the window.
*
*/
clip() {
if (this.#container != null) {
if (this.#x + this.#container.offsetWidth < window.innerWidth)
@@ -61,10 +87,18 @@ export class ContextMenu {
}
}
/** Sets the currently visible submenu
*
* @param menu The name of the currently visibile submenu, or null if no submenu is visible
*/
setVisibleSubMenu(menu: string | null) {
this.#visibleSubMenu = menu;
}
/**
*
* @returns The name of the currently visible submenu
*/
getVisibleSubMenu() {
return this.#visibleSubMenu;
}

View File

@@ -0,0 +1,222 @@
import { LatLng } from "leaflet";
import { getActiveCoalition, getMap, getMissionHandler, setActiveCoalition } from "..";
import { spawnExplosion, spawnSmoke } from "../server/server";
import { ContextMenu } from "./contextmenu";
import { Switch } from "../controls/switch";
import { GAME_MASTER } from "../constants/constants";
import { CoalitionArea } from "../map/coalitionarea/coalitionarea";
import { AircraftSpawnMenu, GroundUnitSpawnMenu, HelicopterSpawnMenu, NavyUnitSpawnMenu } from "../controls/unitspawnmenu";
import { Airbase } from "../mission/airbase";
import { SmokeMarker } from "../map/markers/smokemarker";
/** The MapContextMenu is the main contextmenu shown to the user whenever it rightclicks on the map. It is the primary interaction method for the user.
* It allows to spawn units, create explosions and smoke, and edit CoalitionAreas.
*/
export class MapContextMenu extends ContextMenu {
#coalitionSwitch: Switch;
#aircraftSpawnMenu: AircraftSpawnMenu;
#helicopterSpawnMenu: HelicopterSpawnMenu;
#groundUnitSpawnMenu: GroundUnitSpawnMenu;
#navyUnitSpawnMenu: NavyUnitSpawnMenu;
#coalitionArea: CoalitionArea | null = null;
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
/* Create the coalition switch */
this.#coalitionSwitch = new Switch("coalition-switch", (value: boolean) => this.#onSwitchClick(value));
this.#coalitionSwitch.setValue(false);
this.#coalitionSwitch.getContainer()?.addEventListener("contextmenu", (e) => this.#onSwitchRightClick());
/* Create the spawn menus for the different unit types */
this.#aircraftSpawnMenu = new AircraftSpawnMenu("aircraft-spawn-menu");
this.#helicopterSpawnMenu = new HelicopterSpawnMenu("helicopter-spawn-menu");
this.#groundUnitSpawnMenu = new GroundUnitSpawnMenu("groundunit-spawn-menu");
this.#navyUnitSpawnMenu = new NavyUnitSpawnMenu("navyunit-spawn-menu");
/* Event listeners */
document.addEventListener("mapContextMenuShow", (e: any) => {
if (this.getVisibleSubMenu() !== e.detail.type)
this.#showSubMenu(e.detail.type);
else
this.#hideSubMenus(e.detail.type);
});
document.addEventListener("contextMenuDeploySmoke", (e: any) => {
this.hide();
spawnSmoke(e.detail.color, this.getLatLng());
var marker = new SmokeMarker(this.getLatLng(), e.detail.color);
marker.addTo(getMap());
});
document.addEventListener("contextMenuExplosion", (e: any) => {
this.hide();
spawnExplosion(e.detail.strength, this.getLatLng());
});
document.addEventListener("editCoalitionArea", (e: any) => {
this.hide();
if (this.#coalitionArea) {
getMap().deselectAllCoalitionAreas();
this.#coalitionArea.setSelected(true);
}
});
document.addEventListener("commandModeOptionsChanged", (e: any) => {
//this.#refreshOptions();
});
this.#aircraftSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
this.#helicopterSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
this.#groundUnitSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
this.#navyUnitSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
this.#aircraftSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
this.#helicopterSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
this.#groundUnitSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
this.#navyUnitSpawnMenu.getContainer().addEventListener("hide", () => this.hide());
this.hide();
}
/** Show the contextmenu on top of the map, usually at the location where the user has clicked on it.
*
* @param x X screen coordinate of the top left corner of the context menu
* @param y Y screen coordinate of the top left corner of the context menu
* @param latlng Leaflet latlng object of the mouse click
*/
show(x: number, y: number, latlng: LatLng) {
super.show(x, y, latlng);
this.#aircraftSpawnMenu.setLatLng(latlng);
this.#helicopterSpawnMenu.setLatLng(latlng);
this.#groundUnitSpawnMenu.setLatLng(latlng);
this.#navyUnitSpawnMenu.setLatLng(latlng);
this.#aircraftSpawnMenu.setCountries();
this.#helicopterSpawnMenu.setCountries();
this.#groundUnitSpawnMenu.setCountries();
this.#navyUnitSpawnMenu.setCountries();
/* Only a Game Master can choose the coalition of a new unit */
if (getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER)
this.#coalitionSwitch.hide()
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
if (getActiveCoalition() == "blue")
this.#coalitionSwitch.setValue(false);
else if (getActiveCoalition() == "red")
this.#coalitionSwitch.setValue(true);
else
this.#coalitionSwitch.setValue(undefined);
/* Hide the coalition area button. It will be visible if a coalition area is set */
this.getContainer()?.querySelector("#coalition-area-button")?.classList.toggle("hide", true);
}
/** If the user rightclicked on a CoalitionArea, it will be given the ability to edit it.
*
* @param coalitionArea The CoalitionArea the user can edit
*/
setCoalitionArea(coalitionArea: CoalitionArea) {
this.#coalitionArea = coalitionArea;
this.getContainer()?.querySelector("#coalition-area-button")?.classList.toggle("hide", false);
}
/** Shows the submenu depending on unit selection
*
* @param type Submenu type, either "aircraft", "helicopter", "groundunit", "navyunit", "smoke", or "explosion"
*/
#showSubMenu(type: string) {
if (type === "more")
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide");
else if (["aircraft", "helicopter", "groundunit"].includes(type))
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", type !== "aircraft");
this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", type === "aircraft");
this.getContainer()?.querySelector("#helicopter-spawn-menu")?.classList.toggle("hide", type !== "helicopter");
this.getContainer()?.querySelector("#helicopter-spawn-button")?.classList.toggle("is-open", type === "helicopter");
this.getContainer()?.querySelector("#groundunit-spawn-menu")?.classList.toggle("hide", type !== "groundunit");
this.getContainer()?.querySelector("#groundunit-spawn-button")?.classList.toggle("is-open", type === "groundunit");
this.getContainer()?.querySelector("#navyunit-spawn-menu")?.classList.toggle("hide", type !== "navyunit");
this.getContainer()?.querySelector("#navyunit-spawn-button")?.classList.toggle("is-open", type === "navyunit");
this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", type !== "smoke");
this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", type === "smoke");
this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", type !== "explosion");
this.getContainer()?.querySelector("#explosion-spawn-button")?.classList.toggle("is-open", type === "explosion");
(this.getContainer()?.querySelectorAll(".deploy-unit-button"))?.forEach((element: Node) => { (element as HTMLButtonElement).disabled = true; })
this.#aircraftSpawnMenu.reset();
this.#aircraftSpawnMenu.setCountries();
this.#helicopterSpawnMenu.reset();
this.#helicopterSpawnMenu.setCountries();
this.#groundUnitSpawnMenu.reset();
this.#groundUnitSpawnMenu.setCountries();
this.#navyUnitSpawnMenu.reset();
this.#navyUnitSpawnMenu.setCountries();
this.setVisibleSubMenu(type);
this.clip();
}
/** Hide all the submenus
*
* @param type The type of the currenlt open submenu.
*/
#hideSubMenus(type: string) {
/* Close the lower options bar if the currently open submenu does not required it */
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", ["aircraft", "helicopter", "groundunit"].includes(type));
this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#helicopter-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#helicopter-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#groundunit-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#groundunit-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#navyunit-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#navyunit-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#explosion-spawn-button")?.classList.toggle("is-open", false);
this.#aircraftSpawnMenu.reset();
this.#helicopterSpawnMenu.reset();
this.#groundUnitSpawnMenu.reset();
this.#navyUnitSpawnMenu.reset();
this.setVisibleSubMenu(null);
this.clip();
}
/** Callback called when the user left clicks on the coalition switch
*
* @param value Switch position (false: "blue", true: "red")
*/
#onSwitchClick(value: boolean) {
value ? setActiveCoalition("red") : setActiveCoalition("blue");
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
this.#aircraftSpawnMenu.setCountries();
this.#helicopterSpawnMenu.setCountries();
this.#groundUnitSpawnMenu.setCountries();
this.#navyUnitSpawnMenu.setCountries();
}
/** Callback called when the user rightclicks on the coalition switch. This will select the "neutral" coalition.
*
*/
#onSwitchRightClick() {
this.#coalitionSwitch.setValue(undefined);
setActiveCoalition("neutral");
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
this.#aircraftSpawnMenu.setCountries();
this.#helicopterSpawnMenu.setCountries();
this.#groundUnitSpawnMenu.setCountries();
this.#navyUnitSpawnMenu.setCountries();
}
}

View File

@@ -0,0 +1,32 @@
import { deg2rad, ftToM } from "../other/utils";
import { ContextMenu } from "./contextmenu";
/** The UnitContextMenu is shown when the user rightclicks on a unit. It dynamically presents the user with possible actions to perform on the unit. */
export class UnitContextMenu extends ContextMenu {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
}
/** Set the options that will be presented to the user in the contextmenu
*
* @param options Dictionary element containing the text and tooltip of the options shown in the menu
* @param callback Callback that will be called when the user clicks on one of the options
*/
setOptions(options: { [key: string]: {text: string, tooltip: string }}, callback: CallableFunction) {
this.getContainer()?.replaceChildren(...Object.keys(options).map((key: string, idx: number) => {
const option = options[key];
var button = document.createElement("button");
var el = document.createElement("div");
el.title = option.tooltip;
el.innerText = option.text;
el.id = key;
button.addEventListener("click", () => callback(key));
button.appendChild(el);
return (button);
}));
}
}

View File

@@ -2,8 +2,11 @@ export class Control {
#container: HTMLElement | null;
expectedValue: any = null;
constructor(ID: string) {
this.#container = document.getElementById(ID);
constructor(container: string | null, options?: any) {
if (typeof container === "string")
this.#container = document.getElementById(container);
else
this.#container = this.createElement(options);
}
show() {
@@ -31,4 +34,8 @@ export class Control {
checkExpectedValue(value: any) {
return this.expectedValue === null || value === this.expectedValue;
}
createElement(options?: any): HTMLElement | null {
return null;
}
}

View File

@@ -1,36 +1,41 @@
export class Dropdown {
#element: HTMLElement;
#container: HTMLElement;
#options: HTMLElement;
#value: HTMLElement;
#callback: CallableFunction;
#defaultValue: string;
#optionsList: string[] = [];
#index: number = 0;
#hidden: boolean = false;
constructor(ID: string, callback: CallableFunction, options: string[] | null = null) {
this.#element = <HTMLElement>document.getElementById(ID);
this.#options = <HTMLElement>this.#element.querySelector(".ol-select-options");
this.#value = <HTMLElement>this.#element.querySelector(".ol-select-value");
constructor(ID: string | null, callback: CallableFunction, options: string[] | null = null, defaultText?: string) {
if (ID === null)
this.#container = this.#createElement(defaultText);
else
this.#container = document.getElementById(ID) as HTMLElement;
this.#options = this.#container.querySelector(".ol-select-options") as HTMLElement;
this.#value = this.#container.querySelector(".ol-select-value") as HTMLElement;
this.#defaultValue = this.#value.innerText;
this.#callback = callback;
if (options != null) {
this.setOptions(options);
}
if (options != null) this.setOptions(options);
this.#value.addEventListener("click", (ev) => {
this.#toggle();
});
this.#value.addEventListener("click", (ev) => { this.#toggle(); });
document.addEventListener("click", (ev) => {
if (!(this.#value.contains(ev.target as Node) || this.#options.contains(ev.target as Node) || this.#element.contains(ev.target as Node))) {
this.#close();
if (!(this.#value.contains(ev.target as Node) || this.#options.contains(ev.target as Node) || this.#container.contains(ev.target as Node))) {
this.close();
}
});
this.#options.classList.add("ol-scrollable");
}
getContainer() {
return this.#container;
}
setOptions(optionsList: string[], sortAlphabetically: boolean = true) {
this.#optionsList = optionsList.sort();
if (this.#optionsList.length == 0) {
@@ -79,7 +84,7 @@ export class Dropdown {
this.#value.replaceChildren();
this.#value.appendChild(el);
this.#index = idx;
this.#close();
this.close();
this.#callback(option);
return true;
}
@@ -102,32 +107,66 @@ export class Dropdown {
this.selectValue(index);
}
forceValue(value: string) {
var el = document.createElement("div");
el.classList.add("ol-ellipsed");
el.innerText = value;
this.#value.replaceChildren();
this.#value.appendChild(el);
this.close();
}
getIndex() {
return this.#index;
}
#clip() {
clip() {
const options = this.#options;
const bounds = options.getBoundingClientRect();
this.#element.dataset.position = (bounds.bottom > window.innerHeight) ? "top" : "";
this.#container.dataset.position = (bounds.bottom > window.innerHeight) ? "top" : "";
}
#close() {
this.#element.classList.remove("is-open");
this.#element.dataset.position = "";
close() {
this.#container.classList.remove("is-open");
this.#container.dataset.position = "";
}
#open() {
this.#element.classList.add("is-open");
open() {
this.#container.classList.add("is-open");
this.#options.classList.toggle("scrollbar-visible", this.#options.scrollHeight > this.#options.clientHeight);
this.#clip();
this.clip();
}
show() {
this.#container.classList.remove("hide");
this.#hidden = false;
}
hide() {
this.#container.classList.add("hide");
this.#hidden = true;
}
isHidden() {
return this.#hidden;
}
#toggle() {
if (this.#element.classList.contains("is-open")) {
this.#close();
} else {
this.#open();
}
this.#container.classList.contains("is-open")? this.close(): this.open();
}
#createElement(defaultText: string | undefined) {
var div = document.createElement("div");
div.classList.add("ol-select");
var value = document.createElement("div");
value.classList.add("ol-select-value");
value.innerText = defaultText? defaultText: "";
var options = document.createElement("div");
options.classList.add("ol-select-options");
div.append(value, options);
return div;
}
}

View File

@@ -1,591 +0,0 @@
import { LatLng } from "leaflet";
import { getActiveCoalition, getMap, getMissionHandler, getUnitsManager, setActiveCoalition } from "..";
import { spawnExplosion, spawnSmoke } from "../server/server";
import { aircraftDatabase } from "../unit/aircraftdatabase";
import { groundUnitDatabase } from "../unit/groundunitdatabase";
import { helicopterDatabase } from "../unit/helicopterdatabase";
import { ContextMenu } from "./contextmenu";
import { Dropdown } from "./dropdown";
import { Switch } from "./switch";
import { Slider } from "./slider";
import { ftToM } from "../other/utils";
import { GAME_MASTER } from "../constants/constants";
import { navyUnitDatabase } from "../unit/navyunitdatabase";
import { CoalitionArea } from "../map/coalitionarea";
export class MapContextMenu extends ContextMenu {
#coalitionSwitch: Switch;
#aircraftRoleDropdown: Dropdown;
#aircraftLabelDropdown: Dropdown;
#aircraftCountDropdown: Dropdown;
#aircraftLoadoutDropdown: Dropdown;
#aircraftSpawnAltitudeSlider: Slider;
#helicopterRoleDropdown: Dropdown;
#helicopterLabelDropdown: Dropdown;
#helicopterCountDropdown: Dropdown;
#helicopterLoadoutDropdown: Dropdown;
#helicopterSpawnAltitudeSlider: Slider;
#groundUnitTypeDropdown: Dropdown;
#groundUnitLabelDropdown: Dropdown;
#groundUnitCountDropdown: Dropdown;
#navyUnitTypeDropdown: Dropdown;
#navyUnitLabelDropdown: Dropdown;
#navyUnitCountDropdown: Dropdown;
#spawnOptions = { role: "", name: "", latlng: new LatLng(0, 0), coalition: "blue", loadout: "", airbaseName: "", altitude: 0, count: 1 };
#coalitionArea: CoalitionArea | null = null;
constructor(id: string) {
super(id);
this.#coalitionSwitch = new Switch("coalition-switch", (value: boolean) => this.#onSwitchClick(value));
this.#coalitionSwitch.setValue(false);
this.#coalitionSwitch.getContainer()?.addEventListener("contextmenu", (e) => this.#onSwitchRightClick(e));
/* Aircraft menu */
this.#aircraftRoleDropdown = new Dropdown("aircraft-role-options", (role: string) => this.#setAircraftRole(role));
this.#aircraftLabelDropdown = new Dropdown("aircraft-label-options", (type: string) => this.#setAircraftLabel(type));
this.#aircraftCountDropdown = new Dropdown("aircraft-count-options", (type: string) => this.#setAircraftCount(type));
this.#aircraftCountDropdown.setOptions(["1", "2", "3", "4"]);
this.#aircraftCountDropdown.setValue("1");
this.#aircraftLoadoutDropdown = new Dropdown("aircraft-loadout-options", (loadout: string) => this.#setAircraftLoadout(loadout));
this.#aircraftSpawnAltitudeSlider = new Slider("aircraft-spawn-altitude-slider", 0, 50000, "ft", (value: number) => {this.#spawnOptions.altitude = ftToM(value);});
this.#aircraftSpawnAltitudeSlider.setIncrement(500);
this.#aircraftSpawnAltitudeSlider.setValue(20000);
this.#aircraftSpawnAltitudeSlider.setActive(true);
/* Helicopter menu */
this.#helicopterRoleDropdown = new Dropdown("helicopter-role-options", (role: string) => this.#setHelicopterRole(role));
this.#helicopterLabelDropdown = new Dropdown("helicopter-label-options", (type: string) => this.#setHelicopterLabel(type));
this.#helicopterCountDropdown = new Dropdown("helicopter-count-options", (type: string) => this.#setHelicopterCount(type));
this.#helicopterCountDropdown.setOptions(["1", "2", "3", "4"]);
this.#helicopterCountDropdown.setValue("1");
this.#helicopterLoadoutDropdown = new Dropdown("helicopter-loadout-options", (loadout: string) => this.#setHelicopterLoadout(loadout));
this.#helicopterSpawnAltitudeSlider = new Slider("helicopter-spawn-altitude-slider", 0, 10000, "ft", (value: number) => {this.#spawnOptions.altitude = ftToM(value);});
this.#helicopterSpawnAltitudeSlider.setIncrement(50);
this.#helicopterSpawnAltitudeSlider.setValue(5000);
this.#helicopterSpawnAltitudeSlider.setActive(true);
var count = [];
for (let i = 1; i < 10; i++) count.push(String(i));
/* Ground unit menu */
this.#groundUnitTypeDropdown = new Dropdown("groundunit-type-options", (type: string) => this.#setGroundUnitType(type));
this.#groundUnitLabelDropdown = new Dropdown("groundunit-label-options", (name: string) => this.#setGroundUnitLabel(name));
this.#groundUnitCountDropdown = new Dropdown("groundunit-count-options", (count: string) => this.#setGroundUnitCount(count));
this.#groundUnitCountDropdown.setOptions(count);
this.#groundUnitCountDropdown.setValue("1");
/* Navy unit menu */
this.#navyUnitTypeDropdown = new Dropdown("navyunit-type-options", (type: string) => this.#setNavyUnitType(type));
this.#navyUnitLabelDropdown = new Dropdown("navyunit-label-options", (name: string) => this.#setNavyUnitLabel(name));
this.#navyUnitCountDropdown = new Dropdown("navyunit-count-options", (count: string) => this.#setNavyUnitCount(count));
this.#navyUnitCountDropdown.setOptions(count);
this.#navyUnitCountDropdown.setValue("1");
document.addEventListener("mapContextMenuShow", (e: any) => {
if (this.getVisibleSubMenu() !== e.detail.type)
this.showSubMenu(e.detail.type);
else
this.hideSubMenus(e.detail.type);
});
document.addEventListener("contextMenuDeployAircrafts", () => {
this.#spawnOptions.coalition = getActiveCoalition();
if (this.#spawnOptions) {
const liveryID = aircraftDatabase.getByName(this.#spawnOptions.name)?.liveryID;
var unitTable = {unitType: this.#spawnOptions.name, location: this.#spawnOptions.latlng, altitude: this.#spawnOptions.altitude, loadout: this.#spawnOptions.loadout, liveryID: liveryID? liveryID: ""};
var units = [];
for (let i = 1; i < parseInt(this.#aircraftCountDropdown.getValue()) + 1; i++) {
units.push(unitTable);
}
if (getUnitsManager().spawnUnits("Aircraft", units, getActiveCoalition(), false, this.#spawnOptions.airbaseName)) {
getMap().addTemporaryMarker(this.#spawnOptions.latlng, this.#spawnOptions.name, getActiveCoalition());
this.hide();
}
}
});
document.addEventListener("contextMenuDeployHelicopters", () => {
this.#spawnOptions.coalition = getActiveCoalition();
if (this.#spawnOptions) {
const liveryID = aircraftDatabase.getByName(this.#spawnOptions.name)?.liveryID;
var unitTable = {unitType: this.#spawnOptions.name, location: this.#spawnOptions.latlng, altitude: this.#spawnOptions.altitude, loadout: this.#spawnOptions.loadout, liveryID: liveryID? liveryID: ""};
var units = [];
for (let i = 1; i < parseInt(this.#helicopterCountDropdown.getValue()) + 1; i++) {
units.push(unitTable);
}
if (getUnitsManager().spawnUnits("Helicopter", units, getActiveCoalition(), false, this.#spawnOptions.airbaseName)) {
getMap().addTemporaryMarker(this.#spawnOptions.latlng, this.#spawnOptions.name, getActiveCoalition());
this.hide();
}
}
});
document.addEventListener("contextMenuDeployGroundUnits", () => {
this.#spawnOptions.coalition = getActiveCoalition();
if (this.#spawnOptions) {
const liveryID = aircraftDatabase.getByName(this.#spawnOptions.name)?.liveryID;
var unitTable = {unitType: this.#spawnOptions.name, location: this.#spawnOptions.latlng, liveryID: liveryID? liveryID: ""};
var units = [];
for (let i = 1; i < parseInt(this.#groundUnitCountDropdown.getValue()) + 1; i++) {
units.push(JSON.parse(JSON.stringify(unitTable)));
unitTable.location.lat += 0.0001;
}
if (getUnitsManager().spawnUnits("GroundUnit", units, getActiveCoalition(), false)) {
getMap().addTemporaryMarker(this.#spawnOptions.latlng, this.#spawnOptions.name, getActiveCoalition());
this.hide();
}
}
});
document.addEventListener("contextMenuDeployNavyUnits", () => {
this.#spawnOptions.coalition = getActiveCoalition();
if (this.#spawnOptions) {
const liveryID = aircraftDatabase.getByName(this.#spawnOptions.name)?.liveryID;
var unitTable = {unitType: this.#spawnOptions.name, location: this.#spawnOptions.latlng, liveryID: liveryID? liveryID: ""};
var units = [];
for (let i = 1; i < parseInt(this.#navyUnitCountDropdown.getValue()) + 1; i++) {
units.push(JSON.parse(JSON.stringify(unitTable)));
unitTable.location.lat += 0.0001;
}
if (getUnitsManager().spawnUnits("NavyUnit", units, getActiveCoalition(), false)) {
getMap().addTemporaryMarker(this.#spawnOptions.latlng, this.#spawnOptions.name, getActiveCoalition());
this.hide();
}
}
});
document.addEventListener("contextMenuDeploySmoke", (e: any) => {
this.hide();
spawnSmoke(e.detail.color, this.getLatLng());
});
document.addEventListener("contextMenuExplosion", (e: any) => {
this.hide();
spawnExplosion(e.detail.strength, this.getLatLng());
});
document.addEventListener("editCoalitionArea", (e: any) => {
this.hide();
if (this.#coalitionArea) {
getMap().deselectAllCoalitionAreas();
this.#coalitionArea.setSelected(true);
}
});
document.addEventListener("commandModeOptionsChanged", (e: any) => {
this.#refreshOptions();
});
this.hide();
}
show(x: number, y: number, latlng: LatLng) {
super.show(x, y, latlng);
this.showUpperBar();
this.showAltitudeSlider();
this.#spawnOptions.airbaseName = "";
this.#spawnOptions.latlng = latlng;
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
if (getActiveCoalition() == "blue")
this.#coalitionSwitch.setValue(false);
else if (getActiveCoalition() == "red")
this.#coalitionSwitch.setValue(true);
else
this.#coalitionSwitch.setValue(undefined);
if (getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER)
this.#coalitionSwitch.hide()
this.getContainer()?.querySelector("#coalition-area-button")?.classList.toggle("hide", true);
}
showSubMenu(type: string) {
if (type === "more")
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide");
else if (["aircraft", "groundunit"].includes(type))
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", type !== "aircraft");
this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", type === "aircraft");
this.getContainer()?.querySelector("#helicopter-spawn-menu")?.classList.toggle("hide", type !== "helicopter");
this.getContainer()?.querySelector("#helicopter-spawn-button")?.classList.toggle("is-open", type === "helicopter");
this.getContainer()?.querySelector("#groundunit-spawn-menu")?.classList.toggle("hide", type !== "groundunit");
this.getContainer()?.querySelector("#groundunit-spawn-button")?.classList.toggle("is-open", type === "groundunit");
this.getContainer()?.querySelector("#navyunit-spawn-menu")?.classList.toggle("hide", type !== "navyunit");
this.getContainer()?.querySelector("#navyunit-spawn-button")?.classList.toggle("is-open", type === "navyunit");
this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", type !== "smoke");
this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", type === "smoke");
this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", type !== "explosion");
this.getContainer()?.querySelector("#explosion-spawn-button")?.classList.toggle("is-open", type === "explosion");
(this.getContainer()?.querySelectorAll(".deploy-unit-button"))?.forEach((element: Node) => {(element as HTMLButtonElement).disabled = true;})
this.#resetAircraftRole();
this.#resetAircraftLabel();
this.#resetHelicopterRole();
this.#resetHelicopterLabel();
this.#resetGroundUnitType();
this.#resetGroundUnitLabel();
this.#resetNavyUnitType();
this.#resetNavyUnitLabel();
this.#aircraftCountDropdown.setValue("1");
this.#helicopterCountDropdown.setValue("1");
this.#groundUnitCountDropdown.setValue("1");
this.clip();
if (type === "aircraft") {
this.#spawnOptions.altitude = ftToM(this.#aircraftSpawnAltitudeSlider.getValue());
}
else if (type === "helicopter") {
this.#spawnOptions.altitude = ftToM(this.#helicopterSpawnAltitudeSlider.getValue());
}
this.setVisibleSubMenu(type);
}
hideSubMenus(type: string) {
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", ["aircraft", "groundunit"].includes(type));
this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#helicopter-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#helicopter-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#groundunit-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#groundunit-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#navyunit-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#navyunit-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#explosion-spawn-button")?.classList.toggle("is-open", false);
this.#resetAircraftRole();
this.#resetAircraftLabel();
this.#resetHelicopterRole();
this.#resetHelicopterLabel();
this.#resetHelicopterRole();
this.#resetHelicopterLabel();
this.#resetGroundUnitType();
this.#resetGroundUnitLabel();
this.#resetNavyUnitType();
this.#resetNavyUnitLabel();
this.clip();
this.setVisibleSubMenu(null);
}
showUpperBar() {
this.getContainer()?.querySelector(".upper-bar")?.classList.toggle("hide", false);
}
hideUpperBar() {
this.getContainer()?.querySelector(".upper-bar")?.classList.toggle("hide", true);
}
showLowerBar() {
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", false);
}
hideLowerBar() {
this.getContainer()?.querySelector("#more-optionsbutton-bar")?.classList.toggle("hide", true);
}
showAltitudeSlider() {
this.getContainer()?.querySelector("#aircraft-spawn-altitude-slider")?.classList.toggle("hide", false);
}
hideAltitudeSlider() {
this.getContainer()?.querySelector("#aircraft-spawn-altitude-slider")?.classList.toggle("hide", true);
}
setAirbaseName(airbaseName: string) {
this.#spawnOptions.airbaseName = airbaseName;
}
setLatLng(latlng: LatLng) {
this.#spawnOptions.latlng = latlng;
}
setCoalitionArea(coalitionArea: CoalitionArea) {
this.#coalitionArea = coalitionArea;
this.getContainer()?.querySelector("#coalition-area-button")?.classList.toggle("hide", false);
}
#onSwitchClick(value: boolean) {
value? setActiveCoalition("red"): setActiveCoalition("blue");
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
}
#onSwitchRightClick(e: any) {
this.#coalitionSwitch.setValue(undefined);
setActiveCoalition("neutral");
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
}
#refreshOptions() {
if (!aircraftDatabase.getRoles().includes(this.#aircraftRoleDropdown.getValue()))
this.#resetAircraftRole();
if (!aircraftDatabase.getByRole(this.#aircraftRoleDropdown.getValue()).map((blueprint) => { return blueprint.label }).includes(this.#aircraftLabelDropdown.getValue()))
this.#resetAircraftLabel();
if (!helicopterDatabase.getRoles().includes(this.#helicopterRoleDropdown.getValue()))
this.#resetHelicopterRole();
if (!helicopterDatabase.getByRole(this.#helicopterRoleDropdown.getValue()).map((blueprint) => { return blueprint.label }).includes(this.#helicopterLabelDropdown.getValue()))
this.#resetHelicopterLabel();
if (!groundUnitDatabase.getRoles().includes(this.#groundUnitTypeDropdown.getValue()))
this.#resetGroundUnitType();
if (!groundUnitDatabase.getByType(this.#groundUnitTypeDropdown.getValue()).map((blueprint) => { return blueprint.label }).includes(this.#groundUnitLabelDropdown.getValue()))
this.#resetGroundUnitLabel();
if (!navyUnitDatabase.getRoles().includes(this.#navyUnitTypeDropdown.getValue()))
this.#resetNavyUnitType();
if (!navyUnitDatabase.getByType(this.#navyUnitTypeDropdown.getValue()).map((blueprint) => { return blueprint.label }).includes(this.#aircraftLabelDropdown.getValue()))
this.#resetNavyUnitLabel();
}
/********* Aircraft spawn menu *********/
#setAircraftRole(role: string) {
this.#spawnOptions.role = role;
this.#resetAircraftLabel();
this.#aircraftLabelDropdown.setOptions(aircraftDatabase.getByRole(role).map((blueprint) => { return blueprint.label }));
this.#aircraftLabelDropdown.selectValue(0);
this.clip();
this.#computeSpawnPoints();
}
#resetAircraftRole() {
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-loadout-list")).replaceChildren();
this.#aircraftRoleDropdown.reset();
this.#aircraftLabelDropdown.reset();
this.#aircraftRoleDropdown.setOptions(aircraftDatabase.getRoles());
this.clip();
}
#setAircraftLabel(label: string) {
this.#resetAircraftLabel();
var name = aircraftDatabase.getByLabel(label)?.name || null;
if (name != null) {
this.#spawnOptions.name = name;
this.#aircraftLoadoutDropdown.setOptions(aircraftDatabase.getLoadoutNamesByRole(name, this.#spawnOptions.role));
this.#aircraftLoadoutDropdown.selectValue(0);
var image = (<HTMLImageElement>this.getContainer()?.querySelector("#aircraft-unit-image"));
image.src = `images/units/${aircraftDatabase.getByLabel(label)?.filename}`;
image.classList.toggle("hide", false);
}
this.clip();
this.#computeSpawnPoints();
}
#resetAircraftLabel() {
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-loadout-list")).replaceChildren();
this.#aircraftLoadoutDropdown.reset();
(<HTMLImageElement>this.getContainer()?.querySelector("#aircraft-unit-image")).classList.toggle("hide", true);
this.clip();
}
#setAircraftCount(count: string) {
this.#spawnOptions.count = parseInt(count);
this.clip();
this.#computeSpawnPoints();
}
#setAircraftLoadout(loadoutName: string) {
var loadout = aircraftDatabase.getLoadoutByName(this.#spawnOptions.name, loadoutName);
if (loadout) {
this.#spawnOptions.loadout = loadout.code;
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
var items = loadout.items.map((item: any) => { return `${item.quantity}x ${item.name}`; });
items.length == 0 ? items.push("Empty loadout") : "";
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-loadout-list")).replaceChildren(
...items.map((item: any) => {
var div = document.createElement('div');
div.innerText = item;
return div;
})
)
}
this.clip();
}
/********* Helicopter spawn menu *********/
#setHelicopterRole(role: string) {
this.#spawnOptions.role = role;
this.#resetHelicopterLabel();
this.#helicopterLabelDropdown.setOptions(helicopterDatabase.getByRole(role).map((blueprint) => { return blueprint.label }));
this.#helicopterLabelDropdown.selectValue(0);
this.clip();
this.#computeSpawnPoints();
}
#resetHelicopterRole() {
(<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
(<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-loadout-list")).replaceChildren();
this.#helicopterRoleDropdown.reset();
this.#helicopterLabelDropdown.reset();
this.#helicopterRoleDropdown.setOptions(helicopterDatabase.getRoles());
this.clip();
}
#setHelicopterLabel(label: string) {
this.#resetHelicopterLabel();
var name = helicopterDatabase.getByLabel(label)?.name || null;
if (name != null) {
this.#spawnOptions.name = name;
this.#helicopterLoadoutDropdown.setOptions(helicopterDatabase.getLoadoutNamesByRole(name, this.#spawnOptions.role));
this.#helicopterLoadoutDropdown.selectValue(0);
var image = (<HTMLImageElement>this.getContainer()?.querySelector("#helicopter-unit-image"));
image.src = `images/units/${helicopterDatabase.getByLabel(label)?.filename}`;
image.classList.toggle("hide", false);
}
this.clip();
this.#computeSpawnPoints();
}
#resetHelicopterLabel() {
(<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
(<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-loadout-list")).replaceChildren();
this.#helicopterLoadoutDropdown.reset();
(<HTMLImageElement>this.getContainer()?.querySelector("#helicopter-unit-image")).classList.toggle("hide", true);
this.clip();
}
#setHelicopterCount(count: string) {
this.#spawnOptions.count = parseInt(count);
this.clip();
this.#computeSpawnPoints();
}
#setHelicopterLoadout(loadoutName: string) {
var loadout = helicopterDatabase.getLoadoutByName(this.#spawnOptions.name, loadoutName);
if (loadout) {
this.#spawnOptions.loadout = loadout.code;
(<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
var items = loadout.items.map((item: any) => { return `${item.quantity}x ${item.name}`; });
items.length == 0 ? items.push("Empty loadout") : "";
(<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-loadout-list")).replaceChildren(
...items.map((item: any) => {
var div = document.createElement('div');
div.innerText = item;
return div;
})
)
}
this.clip();
}
/********* Groundunit spawn menu *********/
#setGroundUnitType(role: string) {
this.#resetGroundUnitLabel();
const types = groundUnitDatabase.getByType(role).map((blueprint) => { return blueprint.label });
this.#groundUnitLabelDropdown.setOptions(types);
this.#groundUnitLabelDropdown.selectValue(0);
this.clip();
this.#computeSpawnPoints();
}
#resetGroundUnitType() {
(<HTMLButtonElement>this.getContainer()?.querySelector("#groundunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
this.#groundUnitTypeDropdown.reset();
this.#groundUnitLabelDropdown.reset();
const types = groundUnitDatabase.getTypes();
this.#groundUnitTypeDropdown.setOptions(types);
this.clip();
}
#setGroundUnitLabel(label: string) {
this.#resetGroundUnitLabel();
var type = groundUnitDatabase.getByLabel(label)?.name || null;
if (type != null) {
this.#spawnOptions.name = type;
(<HTMLButtonElement>this.getContainer()?.querySelector("#groundunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
}
this.clip();
this.#computeSpawnPoints();
}
#resetGroundUnitLabel() {
(<HTMLButtonElement>this.getContainer()?.querySelector("#groundunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
this.clip();
}
#setGroundUnitCount(count: string) {
this.#spawnOptions.count = parseInt(count);
this.clip();
this.#computeSpawnPoints();
}
/********* Navyunit spawn menu *********/
#setNavyUnitType(role: string) {
this.#resetNavyUnitLabel();
const types = navyUnitDatabase.getByType(role).map((blueprint) => { return blueprint.label });
this.#navyUnitLabelDropdown.setOptions(types);
this.#navyUnitLabelDropdown.selectValue(0);
this.clip();
this.#computeSpawnPoints();
}
#resetNavyUnitType() {
(<HTMLButtonElement>this.getContainer()?.querySelector("#navyunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
this.#navyUnitTypeDropdown.reset();
this.#navyUnitLabelDropdown.reset();
const types = navyUnitDatabase.getTypes();
this.#navyUnitTypeDropdown.setOptions(types);
this.clip();
}
#setNavyUnitLabel(label: string) {
this.#resetNavyUnitLabel();
var type = navyUnitDatabase.getByLabel(label)?.name || null;
if (type != null) {
this.#spawnOptions.name = type;
(<HTMLButtonElement>this.getContainer()?.querySelector("#navyunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
}
this.clip();
this.#computeSpawnPoints();
}
#resetNavyUnitLabel() {
(<HTMLButtonElement>this.getContainer()?.querySelector("#navyunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = true;
this.clip();
}
#setNavyUnitCount(count: string) {
this.#spawnOptions.count = parseInt(count);
this.clip();
this.#computeSpawnPoints();
}
#computeSpawnPoints() {
if (getMissionHandler() && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER){
var aircraftCount = parseInt(this.#aircraftCountDropdown.getValue());
var aircraftSpawnPoints = aircraftCount * aircraftDatabase.getSpawnPointsByLabel(this.#aircraftLabelDropdown.getValue());
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).dataset.points = `${aircraftSpawnPoints}`;
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = aircraftSpawnPoints >= getMissionHandler().getAvailableSpawnPoints();
var helicopterCount = parseInt(this.#helicopterCountDropdown.getValue());
var helicopterSpawnPoints = helicopterCount * helicopterDatabase.getSpawnPointsByLabel(this.#helicopterLabelDropdown.getValue());
(<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-spawn-menu")?.querySelector(".deploy-unit-button")).dataset.points = `${helicopterSpawnPoints}`;
(<HTMLButtonElement>this.getContainer()?.querySelector("#helicopter-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = helicopterSpawnPoints >= getMissionHandler().getAvailableSpawnPoints();
var groundUnitCount = parseInt(this.#groundUnitCountDropdown.getValue());
var groundUnitSpawnPoints = groundUnitCount * groundUnitDatabase.getSpawnPointsByLabel(this.#groundUnitLabelDropdown.getValue());
(<HTMLButtonElement>this.getContainer()?.querySelector("#groundunit-spawn-menu")?.querySelector(".deploy-unit-button")).dataset.points = `${groundUnitSpawnPoints}`;
(<HTMLButtonElement>this.getContainer()?.querySelector("#groundunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = groundUnitSpawnPoints >= getMissionHandler().getAvailableSpawnPoints();
var navyUnitCount = parseInt(this.#navyUnitCountDropdown.getValue());
var navyUnitSpawnPoints = navyUnitCount * navyUnitDatabase.getSpawnPointsByLabel(this.#navyUnitLabelDropdown.getValue());
(<HTMLButtonElement>this.getContainer()?.querySelector("#navyunit-spawn-menu")?.querySelector(".deploy-unit-button")).dataset.points = `${navyUnitSpawnPoints}`;
(<HTMLButtonElement>this.getContainer()?.querySelector("#navyunit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = navyUnitSpawnPoints >= getMissionHandler().getAvailableSpawnPoints();
}
}
}

View File

@@ -13,8 +13,9 @@ export class Slider extends Control {
#dragged: boolean = false;
#value: number = 0;
constructor(ID: string, minValue: number, maxValue: number, unitOfMeasure: string, callback: CallableFunction) {
super(ID);
constructor(ID: string | null, minValue: number, maxValue: number, unitOfMeasure: string, callback: CallableFunction, options?: any) {
super(ID, options);
this.#callback = callback;
this.#unitOfMeasure = unitOfMeasure;
this.#slider = this.getContainer()?.querySelector("input") as HTMLInputElement;
@@ -62,7 +63,12 @@ export class Slider extends Control {
setValue(newValue: number, ignoreExpectedValue: boolean = true) {
if (!this.getDragged() && (ignoreExpectedValue || this.checkExpectedValue(newValue))) {
this.#value = newValue;
if (this.#value !== newValue) {
this.#value = newValue;
if (this.#callback)
this.#callback(this.getValue());
}
if (this.#slider != null)
this.#slider.value = String((newValue - this.#minValue) / (this.#maxValue - this.#minValue) * parseFloat(this.#slider.max));
this.#update();
@@ -120,4 +126,31 @@ export class Slider extends Control {
}
}
}
createElement(options?: any): HTMLElement | null {
var containerEl = document.createElement("div");
containerEl.classList.add("ol-slider-container", "flight-control-ol-slider");
var dl = document.createElement("dl");
dl.classList.add("ol-data-grid");
var dt = document.createElement("dt");
dt.innerText = (options !== undefined && options.title !== undefined)? options.title: "";
var dd = document.createElement("dd");
var sliderEl = document.createElement("div");
sliderEl.classList.add("ol-slider-value");
dd.append(sliderEl);
dl.append(dt, dd);
var input = document.createElement("input") as HTMLInputElement;
input.type = "range";
input.min = "0";
input.max = "100";
input.value = "0"
input.classList.add("ol-slider");
containerEl.append(dl, input);
return containerEl;
}
}

View File

@@ -4,6 +4,7 @@ export class Switch extends Control {
#value: boolean | undefined = false;
#callback: CallableFunction | null = null;
// TODO: allow for null ID so that the element is created automatically
constructor(ID: string, callback: CallableFunction, initialValue?: boolean) {
super(ID);
this.getContainer()?.addEventListener('click', (e) => this.#onToggle());

View File

@@ -1,56 +0,0 @@
import { deg2rad, ftToM } from "../other/utils";
import { ContextMenu } from "./contextmenu";
export class UnitContextMenu extends ContextMenu {
#customFormationCallback: CallableFunction | null = null;
constructor(id: string) {
super(id);
document.addEventListener("applyCustomFormation", () => {
var dialog = document.getElementById("custom-formation-dialog");
if (dialog) {
dialog.classList.add("hide");
var clock = 1;
while (clock < 8) {
if ((<HTMLInputElement>dialog.querySelector(`#formation-${clock}`)).checked)
break
clock++;
}
var angleDeg = 360 - (clock - 1) * 45;
var angleRad = deg2rad(angleDeg);
var distance = ftToM(parseInt((<HTMLInputElement>dialog.querySelector(`#distance`)?.querySelector("input")).value));
var upDown = ftToM(parseInt((<HTMLInputElement>dialog.querySelector(`#up-down`)?.querySelector("input")).value));
// X: front-rear, positive front
// Y: top-bottom, positive top
// Z: left-right, positive right
var x = distance * Math.cos(angleRad);
var y = upDown;
var z = distance * Math.sin(angleRad);
if (this.#customFormationCallback)
this.#customFormationCallback({ "x": x, "y": y, "z": z })
}
})
}
setCustomFormationCallback(callback: CallableFunction) {
this.#customFormationCallback = callback;
}
setOptions(options: { [key: string]: {text: string, tooltip: string }}, callback: CallableFunction) {
this.getContainer()?.replaceChildren(...Object.keys(options).map((key: string, idx: number) => {
const option = options[key];
var button = document.createElement("button");
var el = document.createElement("div");
el.title = option.tooltip;
el.innerText = option.text;
el.id = key;
button.addEventListener("click", () => callback(key));
button.appendChild(el);
return (button);
}));
}
}

View File

@@ -0,0 +1,538 @@
import { LatLng } from "leaflet";
import { Dropdown } from "./dropdown";
import { Slider } from "./slider";
import { UnitDatabase } from "../unit/databases/unitdatabase";
import { getActiveCoalition, getMap, getMissionHandler, getUnitsManager } from "..";
import { GAME_MASTER } from "../constants/constants";
import { UnitSpawnOptions } from "../@types/unitdatabase";
import { Airbase } from "../mission/airbase";
import { ftToM } from "../other/utils";
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
import { helicopterDatabase } from "../unit/databases/helicopterdatabase";
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
export class UnitSpawnMenu {
#container: HTMLElement;
#unitDatabase: UnitDatabase;
#countryCodes: any;
#orderByRole: boolean;
#spawnOptions: UnitSpawnOptions = {
roleType: "",
name: "",
latlng: new LatLng(0, 0),
coalition: "blue",
count: 1,
country: "",
loadout: undefined,
airbase: undefined,
liveryID: undefined,
altitude: undefined
};
/* Controls */
#unitRoleTypeDropdown: Dropdown;
#unitLabelDropdown: Dropdown;
#unitCountDropdown: Dropdown;
#unitLoadoutDropdown: Dropdown;
#unitCountryDropdown: Dropdown;
#unitLiveryDropdown: Dropdown;
#unitSpawnAltitudeSlider: Slider;
/* HTML Elements */
#deployUnitButtonEl: HTMLButtonElement;
#unitLoadoutPreviewEl: HTMLDivElement;
#unitImageEl: HTMLImageElement;
#unitLoadoutListEl: HTMLDivElement;
constructor(ID: string, unitDatabase: UnitDatabase, orderByRole: boolean) {
this.#container = document.getElementById(ID) as HTMLElement;
this.#unitDatabase = unitDatabase;
this.#orderByRole = orderByRole;
/* Create the dropdowns and the altitude slider */
this.#unitRoleTypeDropdown = new Dropdown(null, (roleType: string) => this.#setUnitRoleType(roleType), undefined, "Unit type");
this.#unitLabelDropdown = new Dropdown(null, (label: string) => this.#setUnitLabel(label), undefined, "Unit label");
this.#unitLoadoutDropdown = new Dropdown(null, (loadout: string) => this.#setUnitLoadout(loadout), undefined, "Unit loadout");
this.#unitCountDropdown = new Dropdown(null, (count: string) => this.#setUnitCount(count), undefined, "Unit count");
this.#unitCountryDropdown = new Dropdown(null, () => { /* Custom button implementation */ }, undefined, "Unit country");
this.#unitLiveryDropdown = new Dropdown(null, (livery: string) => this.#setUnitLivery(livery), undefined, "Unit livery");
this.#unitSpawnAltitudeSlider = new Slider(null, 0, 1000, "ft", (value: number) => { this.#spawnOptions.altitude = ftToM(value); }, { title: "Spawn altitude" });
/* The unit label and unit count are in the same "row" for clarity and compactness */
var unitLabelCountContainerEl = document.createElement("div");
unitLabelCountContainerEl.classList.add("unit-label-count-container");
var divider = document.createElement("div");
divider.innerText = "x";
unitLabelCountContainerEl.append(this.#unitLabelDropdown.getContainer(), divider, this.#unitCountDropdown.getContainer());
/* Create the unit image and loadout elements */
this.#unitLoadoutPreviewEl = document.createElement("div");
this.#unitLoadoutPreviewEl.classList.add("unit-loadout-preview");
this.#unitImageEl = document.createElement("img");
this.#unitImageEl.classList.add("unit-image", "hide");
this.#unitLoadoutListEl = document.createElement("div");
this.#unitLoadoutListEl.classList.add("unit-loadout-list");
this.#unitLoadoutPreviewEl.append(this.#unitImageEl, this.#unitLoadoutListEl);
/* Create the divider and the advanced options collapsible div */
var advancedOptionsDiv = document.createElement("div");
advancedOptionsDiv.classList.add("contextmenu-advanced-options", "hide");
var advancedOptionsToggle = document.createElement("div");
advancedOptionsToggle.classList.add("contextmenu-advanced-options-toggle");
var advancedOptionsText = document.createElement("div");
advancedOptionsText.innerText = "Advanced options";
var advancedOptionsHr = document.createElement("hr");
advancedOptionsToggle.append(advancedOptionsText, advancedOptionsHr);
advancedOptionsToggle.addEventListener("click", () => {
advancedOptionsDiv.classList.toggle("hide");
this.#container.dispatchEvent(new Event("resize"));
});
advancedOptionsDiv.append(this.#unitCountryDropdown.getContainer(), this.#unitLiveryDropdown.getContainer(),
this.#unitLoadoutPreviewEl, this.#unitSpawnAltitudeSlider.getContainer() as HTMLElement);
/* Create the unit deploy button */
this.#deployUnitButtonEl = document.createElement("button");
this.#deployUnitButtonEl.classList.add("deploy-unit-button");
this.#deployUnitButtonEl.disabled = true;
this.#deployUnitButtonEl.innerText = "Deploy unit";
this.#deployUnitButtonEl.setAttribute("data-coalition", "blue");
this.#deployUnitButtonEl.addEventListener("click", () => {
this.deployUnits(this.#spawnOptions, parseInt(this.#unitCountDropdown.getValue()));
});
/* Assemble all components */
this.#container.append(this.#unitRoleTypeDropdown.getContainer(), unitLabelCountContainerEl, this.#unitLoadoutDropdown.getContainer(),
advancedOptionsToggle, advancedOptionsDiv, this.#deployUnitButtonEl);
/* Load the country codes from the public folder */
var xhr = new XMLHttpRequest();
xhr.open('GET', 'images/countries/codes.json', true);
xhr.responseType = 'json';
xhr.onload = () => {
var status = xhr.status;
if (status === 200)
this.#countryCodes = xhr.response;
else
console.error(`Error retrieving country codes`)
};
xhr.send();
/* Event listeners */
this.#container.addEventListener("unitRoleTypeChanged", () => {
this.#deployUnitButtonEl.disabled = true;
this.#unitLabelDropdown.reset();
this.#unitLoadoutListEl.replaceChildren();
this.#unitLoadoutDropdown.reset();
this.#unitImageEl.classList.toggle("hide", true);
this.#unitLiveryDropdown.reset();
if (this.#orderByRole)
this.#unitLabelDropdown.setOptions(this.#unitDatabase.getByRole(this.#spawnOptions.roleType).map((blueprint) => { return blueprint.label }));
else
this.#unitLabelDropdown.setOptions(this.#unitDatabase.getByType(this.#spawnOptions.roleType).map((blueprint) => { return blueprint.label }));
this.#container.dispatchEvent(new Event("resize"));
this.#spawnOptions.name = "";
this.#spawnOptions.loadout = undefined;
this.#spawnOptions.liveryID = undefined;
this.#computeSpawnPoints();
})
this.#container.addEventListener("unitLabelChanged", () => {
this.#deployUnitButtonEl.disabled = false;
if (!this.#unitLoadoutDropdown.isHidden()) {
this.#unitLoadoutDropdown.setOptions(this.#unitDatabase.getLoadoutNamesByRole(this.#spawnOptions.name, this.#spawnOptions.roleType));
this.#unitLoadoutDropdown.selectValue(0);
}
this.#unitImageEl.src = `images/units/${this.#unitDatabase.getByName(this.#spawnOptions.name)?.filename}`;
this.#unitImageEl.classList.toggle("hide", false);
this.#setUnitLiveryOptions();
this.#container.dispatchEvent(new Event("resize"));
this.#computeSpawnPoints();
})
this.#container.addEventListener("unitLoadoutChanged", () => {
var items = this.#spawnOptions.loadout?.items.map((item: any) => { return `${item.quantity}x ${item.name}`; });
if (items != undefined) {
items.length == 0 ? items.push("Empty loadout") : "";
this.#unitLoadoutListEl.replaceChildren(
...items.map((item: any) => {
var div = document.createElement('div');
div.innerText = item;
return div;
})
);
}
this.#container.dispatchEvent(new Event("resize"));
})
this.#container.addEventListener("unitCountChanged", () => {
this.#computeSpawnPoints();
})
this.#container.addEventListener("unitCountryChanged", () => {
this.#setUnitLiveryOptions();
})
this.#container.addEventListener("unitLiveryChanged", () => {
})
}
getContainer() {
return this.#container;
}
reset() {
this.#deployUnitButtonEl.disabled = true;
this.#unitRoleTypeDropdown.reset();
this.#unitLabelDropdown.reset();
this.#unitLiveryDropdown.reset();
if (this.#orderByRole)
this.#unitRoleTypeDropdown.setOptions(this.#unitDatabase.getRoles());
else
this.#unitRoleTypeDropdown.setOptions(this.#unitDatabase.getTypes());
this.#unitLoadoutListEl.replaceChildren();
this.#unitLoadoutDropdown.reset();
this.#unitImageEl.classList.toggle("hide", true);
this.setCountries();
this.#container.dispatchEvent(new Event("resize"));
}
setCountries() {
var coalitions = getMissionHandler().getCoalitions();
var countries = Object.values(coalitions[getActiveCoalition() as keyof typeof coalitions]);
this.#unitCountryDropdown.setOptionsElements(this.#createCountryButtons(this.#unitCountryDropdown, countries, (country: string) => { this.#setUnitCountry(country) }));
if (countries.length > 0 && !countries.includes(this.#spawnOptions.country)) {
this.#unitCountryDropdown.forceValue(this.#getFormattedCountry(countries[0]));
this.#setUnitCountry(countries[0]);
}
}
refreshOptions() {
//if (!this.#unitDatabase.getTypes().includes(this.#unitTypeDropdown.getValue()))
// this.reset();
//if (!this.#unitDatabase.getByType(this.#unitTypeDropdown.getValue()).map((blueprint) => { return blueprint.label }).includes(this.#unitLabelDropdown.getValue()))
// this.resetUnitLabel();
}
setAirbase(airbase: Airbase | undefined) {
this.#spawnOptions.airbase = airbase;
}
setLatLng(latlng: LatLng) {
this.#spawnOptions.latlng = latlng;
}
setMaxUnitCount(maxUnitCount: number) {
/* Create the unit count options */
var countOptions: string[] = [];
for (let i = 1; i <= maxUnitCount; i++)
countOptions.push(i.toString());
this.#unitCountDropdown.setOptions(countOptions);
this.#unitCountDropdown.selectValue(0);
}
getRoleTypeDrodown() {
return this.#unitRoleTypeDropdown;
}
getLabelDropdown() {
return this.#unitLabelDropdown;
}
getCountDropdown() {
return this.#unitCountDropdown;
}
getLoadoutDropdown() {
return this.#unitLoadoutDropdown;
}
getCountryDropdown() {
return this.#unitCountDropdown;
}
getLiveryDropdown() {
return this.#unitLiveryDropdown;
}
getLoadoutPreview() {
return this.#unitLoadoutPreviewEl;
}
getAltitudeSlider() {
return this.#unitSpawnAltitudeSlider;
}
#setUnitRoleType(roleType: string) {
this.#spawnOptions.roleType = roleType;
this.#container.dispatchEvent(new Event("unitRoleTypeChanged"));
}
#setUnitLabel(label: string) {
var name = this.#unitDatabase.getByLabel(label)?.name || null;
if (name != null)
this.#spawnOptions.name = name;
this.#container.dispatchEvent(new Event("unitLabelChanged"));
}
#setUnitLoadout(loadoutName: string) {
var loadout = this.#unitDatabase.getLoadoutByName(this.#spawnOptions.name, loadoutName);
if (loadout)
this.#spawnOptions.loadout = loadout;
this.#container.dispatchEvent(new Event("unitLoadoutChanged"));
}
#setUnitCount(count: string) {
this.#spawnOptions.count = parseInt(count);
this.#container.dispatchEvent(new Event("unitCountChanged"));
}
#setUnitCountry(country: string) {
this.#spawnOptions.country = country;
this.#container.dispatchEvent(new Event("unitCountryChanged"));
}
#setUnitLivery(liveryName: string) {
var liveries = this.#unitDatabase.getByName(this.#spawnOptions.name)?.liveries;
if (liveryName === "Default") {
this.#spawnOptions.liveryID = "";
}
else {
if (liveries !== undefined) {
for (let liveryID in liveries)
if (liveries[liveryID].name === liveryName)
this.#spawnOptions.liveryID = liveryID;
}
}
this.#container.dispatchEvent(new Event("unitLiveryChanged"));
}
#setUnitLiveryOptions() {
if (this.#spawnOptions.name !== "" && this.#spawnOptions.country !== "") {
var liveries = this.#unitDatabase.getLiveryNamesByName(this.#spawnOptions.name);
var countryLiveries: string[] = ["Default"];
liveries.forEach((livery: any) => {
var nationLiveryCodes = this.#countryCodes[this.#spawnOptions.country].liveryCodes;
if (livery.countries === "All" || livery.countries.some((country: string) => { return nationLiveryCodes.includes(country) }))
countryLiveries.push(livery.name);
});
this.#unitLiveryDropdown.setOptions(countryLiveries);
this.#unitLiveryDropdown.selectValue(0);
}
}
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) {
/* Virtual function must be overloaded by inheriting classes */
}
#createCountryButtons(parent: Dropdown, countries: string[], callback: CallableFunction) {
return Object.values(countries).map((country: string) => {
var el = document.createElement("div");
var button = document.createElement("button");
button.classList.add("country-dropdown-element");
el.appendChild(button);
button.addEventListener("click", () => {
callback(country);
parent.forceValue(this.#getFormattedCountry(country));
parent.close();
});
if (this.#countryCodes[country] !== undefined) {
var code = this.#countryCodes[country].flagCode;
if (code !== undefined) {
var img = document.createElement("img");
img.src = `images/countries/${code.toLowerCase()}.svg`;
button.appendChild(img);
}
}
else {
console.log("Unknown country " + country);
}
var text = document.createElement("div");
text.innerText = this.#getFormattedCountry(country);
button.appendChild(text);
return el;
});
}
#getFormattedCountry(country: string) {
var formattedCountry = "";
if (this.#countryCodes[country] !== undefined && this.#countryCodes[country].displayName !== undefined)
formattedCountry = this.#countryCodes[country].displayName;
else
formattedCountry = country.charAt(0).toUpperCase() + country.slice(1).toLowerCase();
return formattedCountry;
}
#computeSpawnPoints() {
if (getMissionHandler() && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) {
var unitCount = parseInt(this.#unitCountDropdown.getValue());
var unitSpawnPoints = unitCount * this.#unitDatabase.getSpawnPointsByLabel(this.#unitLabelDropdown.getValue());
this.#deployUnitButtonEl.dataset.points = `${unitSpawnPoints}`;
this.#deployUnitButtonEl.disabled = unitSpawnPoints >= getMissionHandler().getAvailableSpawnPoints();
}
}
}
export class AircraftSpawnMenu extends UnitSpawnMenu {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID, aircraftDatabase, true);
this.setMaxUnitCount(4);
this.getAltitudeSlider().setMinMax(0, 50000);
this.getAltitudeSlider().setIncrement(500);
this.getAltitudeSlider().setValue(20000);
}
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) {
spawnOptions.coalition = getActiveCoalition();
if (spawnOptions) {
var unitTable = {
unitType: spawnOptions.name,
location: spawnOptions.latlng,
altitude: spawnOptions.altitude? spawnOptions.altitude: 0,
loadout: spawnOptions.loadout? spawnOptions.loadout.name: "",
liveryID: spawnOptions.liveryID? spawnOptions.liveryID: ""
};
var units = [];
for (let i = 1; i < unitsCount + 1; i++) {
units.push(unitTable);
}
getUnitsManager().spawnUnits("Aircraft", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
if (res.commandHash !== undefined)
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
});
this.getContainer().dispatchEvent(new Event("hide"));
}
}
}
export class HelicopterSpawnMenu extends UnitSpawnMenu {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID, helicopterDatabase, true);
this.setMaxUnitCount(4);
this.getAltitudeSlider().setMinMax(0, 10000);
this.getAltitudeSlider().setIncrement(100);
this.getAltitudeSlider().setValue(5000);
}
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) {
spawnOptions.coalition = getActiveCoalition();
if (spawnOptions) {
var unitTable = {
unitType: spawnOptions.name,
location: spawnOptions.latlng,
altitude: spawnOptions.altitude? spawnOptions.altitude: 0,
loadout: spawnOptions.loadout? spawnOptions.loadout.name: "",
liveryID: spawnOptions.liveryID? spawnOptions.liveryID: ""
};
var units = [];
for (let i = 1; i < unitsCount + 1; i++) {
units.push(unitTable);
}
getUnitsManager().spawnUnits("Helicopter", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
if (res.commandHash !== undefined)
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
});
this.getContainer().dispatchEvent(new Event("hide"));
}
}
}
export class GroundUnitSpawnMenu extends UnitSpawnMenu {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID, groundUnitDatabase, false);
this.setMaxUnitCount(20);
this.getAltitudeSlider().hide();
this.getLoadoutDropdown().hide();
this.getLoadoutPreview().classList.add("hide");
}
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) {
spawnOptions.coalition = getActiveCoalition();
if (spawnOptions) {
var unitTable = {
unitType: spawnOptions.name,
location: spawnOptions.latlng,
liveryID: spawnOptions.liveryID? spawnOptions.liveryID: ""
};
var units = [];
for (let i = 0; i < unitsCount; i++) {
units.push(JSON.parse(JSON.stringify(unitTable)));
unitTable.location.lat += i > 0? 0.0001: 0;
}
getUnitsManager().spawnUnits("GroundUnit", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
if (res.commandHash !== undefined)
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
});
this.getContainer().dispatchEvent(new Event("hide"));
}
}
}
export class NavyUnitSpawnMenu extends UnitSpawnMenu {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID, navyUnitDatabase, false);
this.setMaxUnitCount(4);
this.getAltitudeSlider().hide();
this.getLoadoutDropdown().hide();
this.getLoadoutPreview().classList.add("hide");
}
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) {
spawnOptions.coalition = getActiveCoalition();
if (spawnOptions) {
var unitTable = {
unitType: spawnOptions.name,
location: spawnOptions.latlng,
liveryID: spawnOptions.liveryID? spawnOptions.liveryID: ""
};
var units = [];
for (let i = 0; i < unitsCount; i++) {
units.push(JSON.parse(JSON.stringify(unitTable)));
unitTable.location.lat += i > 0? 0.0001: 0;
}
getUnitsManager().spawnUnits("NavyUnit", units, getActiveCoalition(), false, spawnOptions.airbase ? spawnOptions.airbase.getName() : "", spawnOptions.country, (res: any) => {
if (res.commandHash !== undefined)
getMap().addTemporaryMarker(spawnOptions.latlng, spawnOptions.name, getActiveCoalition(), res.commandHash);
});
this.getContainer().dispatchEvent(new Event("hide"));
}
}
}

View File

@@ -2,33 +2,34 @@ import { Map } from "./map/map"
import { UnitsManager } from "./unit/unitsmanager";
import { UnitInfoPanel } from "./panels/unitinfopanel";
import { ConnectionStatusPanel } from "./panels/connectionstatuspanel";
import { MissionHandler } from "./mission/missionhandler";
import { MissionManager } from "./mission/missionhandler";
import { UnitControlPanel } from "./panels/unitcontrolpanel";
import { MouseInfoPanel } from "./panels/mouseinfopanel";
import { AIC } from "./aic/aic";
import { ATC } from "./atc/atc";
import { FeatureSwitches } from "./features/featureswitches";
import { LogPanel } from "./panels/logpanel";
import { getConfig, getPaused, setAddress, setCredentials, setPaused, startUpdate, toggleDemoEnabled } from "./server/server";
import { UnitDataTable } from "./atc/unitdatatable";
import { keyEventWasInInput } from "./other/utils";
import { Popup } from "./popups/popup";
import { Dropdown } from "./controls/dropdown";
import { HotgroupPanel } from "./panels/hotgrouppanel";
import { SVGInjector } from "@tanem/svg-injector";
import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "./constants/constants";
import { ServerStatusPanel } from "./panels/serverstatuspanel";
import { WeaponsManager } from "./weapon/weaponsmanager";
import { ConfigParameters } from "./@types/dom";
import { CommandModeToolbar } from "./toolbars/commandmodetoolbar";
import { PrimaryToolbar } from "./toolbars/primarytoolbar";
/* Global data */
var activeCoalition: string = "blue";
/* Main leaflet map, extended by custom methods */
var map: Map;
/* Managers */
var unitsManager: UnitsManager;
var weaponsManager: WeaponsManager;
var missionHandler: MissionHandler;
var aic: AIC;
var atc: ATC;
var missionHandler: MissionManager;
/* UI Panels */
var unitInfoPanel: UnitInfoPanel;
var connectionStatusPanel: ConnectionStatusPanel;
var serverStatusPanel: ServerStatusPanel;
@@ -37,22 +38,21 @@ var mouseInfoPanel: MouseInfoPanel;
var logPanel: LogPanel;
var hotgroupPanel: HotgroupPanel;
/* UI Toolbars */
var primaryToolbar: PrimaryToolbar;
var commandModeToolbar: CommandModeToolbar;
/* Popups */
var infoPopup: Popup;
var activeCoalition: string = "blue";
var unitDataTable: UnitDataTable;
var featureSwitches;
function setup() {
featureSwitches = new FeatureSwitches();
/* Initialize base functionalitites */
map = new Map('map-container');
unitsManager = new UnitsManager();
weaponsManager = new WeaponsManager();
map = new Map('map-container');
missionHandler = new MissionHandler();
missionHandler = new MissionManager();
/* Panels */
unitInfoPanel = new UnitInfoPanel("unit-info-panel");
@@ -63,39 +63,28 @@ function setup() {
hotgroupPanel = new HotgroupPanel("hotgroup-panel");
logPanel = new LogPanel("log-panel");
/* Toolbars */
primaryToolbar = new PrimaryToolbar("primary-toolbar");
commandModeToolbar = new CommandModeToolbar("command-mode-toolbar");
/* Popups */
infoPopup = new Popup("info-popup");
/* Controls */
new Dropdown("app-icon", () => { });
/* Unit data table */
unitDataTable = new UnitDataTable("unit-data-table");
/* AIC */
let aicFeatureSwitch = featureSwitches.getSwitch("aic");
if (aicFeatureSwitch?.isEnabled()) {
aic = new AIC();
}
/* ATC */
let atcFeatureSwitch = featureSwitches.getSwitch("atc");
if (atcFeatureSwitch?.isEnabled()) {
atc = new ATC();
atc.startUpdates();
}
/* Setup event handlers */
setupEvents();
/* Load the config file */
getConfig(readConfig);
/* Load the config file from the app server*/
getConfig((config: ConfigParameters) => readConfig(config));
}
function readConfig(config: any) {
if (config && config["address"] != undefined && config["port"] != undefined) {
const address = config["address"];
const port = config["port"];
/** Loads the configuration parameters
*
* @param config ConfigParameters, defines the address and port of the Olympus REST server
*/
function readConfig(config: ConfigParameters) {
if (config && config.address != undefined && config.port != undefined) {
const address = config.address;
const port = config.port;
if (typeof address === 'string' && typeof port == 'number')
setAddress(address == "*" ? window.location.hostname : address, <number>port);
}
@@ -104,9 +93,14 @@ function readConfig(config: any) {
}
}
function setupEvents() {
/* Generic clicks */
/** Setup the global window events
*
*/
function setupEvents() {
/* Generic clicks. The "data-on-click" html parameter is used to call a generic callback from the html code.
It is used by all the statically defined elements of the UI. Dynamically generated elements should directly generate the events instead.
*/
document.addEventListener("click", (ev) => {
if (ev instanceof MouseEvent && ev.target instanceof HTMLElement) {
const target = ev.target;
@@ -137,15 +131,9 @@ function setupEvents() {
return;
}
switch (ev.code) {
case "KeyL":
document.body.toggleAttribute("data-hide-labels");
break;
case "KeyT":
toggleDemoEnabled();
break;
case "Quote":
unitDataTable.toggle();
break
case "Space":
setPaused(!getPaused());
break;
@@ -154,7 +142,7 @@ function setupEvents() {
getMap().handleMapPanning(ev);
break;
case "Digit1": case "Digit2": case "Digit3": case "Digit4": case "Digit5": case "Digit6": case "Digit7": case "Digit8": case "Digit9":
// Using the substring because the key will be invalid when pressing the Shift key
/* Using the substring because the key will be invalid when pressing the Shift key */
if (ev.ctrlKey && ev.shiftKey)
getUnitsManager().selectedUnitsAddToHotgroup(parseInt(ev.code.substring(5)));
else if (ev.ctrlKey && !ev.shiftKey)
@@ -177,20 +165,18 @@ function setupEvents() {
}
});
// TODO: move from here in dedicated class
document.addEventListener("closeDialog", (ev: CustomEventInit) => {
ev.detail._element.closest(".ol-dialog").classList.add("hide");
});
document.addEventListener("toggleElements", (ev: CustomEventInit) => {
document.querySelectorAll(ev.detail.selector).forEach(el => {
el.classList.toggle("hide");
})
});
/* Try and connect with the Olympus REST server */
document.addEventListener("tryConnection", () => {
const form = document.querySelector("#splash-content")?.querySelector("#authentication-form");
const username = (<HTMLInputElement>(form?.querySelector("#username"))).value;
const password = (<HTMLInputElement>(form?.querySelector("#password"))).value;
const username = (form?.querySelector("#username") as HTMLInputElement).value;
const password = (form?.querySelector("#password") as HTMLInputElement).value;
/* Update the user credentials */
setCredentials(username, password);
/* Start periodically requesting updates */
@@ -199,10 +185,12 @@ function setupEvents() {
setLoginStatus("connecting");
})
/* Reload the page, used to mimic a restart of the app */
document.addEventListener("reloadPage", () => {
location.reload();
})
/* Inject the svgs with the corresponding svg code. This allows to dynamically manipulate the svg, like changing colors */
document.querySelectorAll("[inject-svg]").forEach((el: Element) => {
var img = el as HTMLImageElement;
var isLoaded = img.complete;
@@ -215,14 +203,11 @@ function setupEvents() {
})
}
/* Getters */
export function getMap() {
return map;
}
export function getUnitDataTable() {
return unitDataTable;
}
export function getUnitsManager() {
return unitsManager;
}
@@ -231,7 +216,6 @@ export function getWeaponsManager() {
return weaponsManager;
}
export function getMissionHandler() {
return missionHandler;
}
@@ -264,11 +248,23 @@ export function getHotgroupPanel() {
return hotgroupPanel;
}
export function getInfoPopup() {
return infoPopup;
}
/** Set the active coalition, i.e. the currently controlled coalition. A game master can change the active coalition, while a commander is bound to his/her coalition
*
* @param newActiveCoalition
*/
export function setActiveCoalition(newActiveCoalition: string) {
if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER)
activeCoalition = newActiveCoalition;
}
/**
*
* @returns The active coalition
*/
export function getActiveCoalition() {
if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER)
return activeCoalition;
@@ -282,15 +278,15 @@ export function getActiveCoalition() {
}
}
/** Set a message in the login splash screen
*
* @param status The message to show in the login splash screen
*/
export function setLoginStatus(status: string) {
const el = document.querySelector("#login-status") as HTMLElement;
if (el)
el.dataset["status"] = status;
}
export function getInfoPopup() {
return infoPopup;
}
window.onload = setup;

View File

@@ -1,8 +1,8 @@
import { DomUtil, LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions } from "leaflet";
import { getMap, getMissionHandler, getUnitsManager } from "..";
import { getMap, getMissionHandler, getUnitsManager } from "../..";
import { CoalitionAreaHandle } from "./coalitionareahandle";
import { CoalitionAreaMiddleHandle } from "./coalitionareamiddlehandle";
import { BLUE_COMMANDER, RED_COMMANDER } from "../constants/constants";
import { BLUE_COMMANDER, RED_COMMANDER } from "../../constants/constants";
export class CoalitionArea extends Polygon {
#coalition: string = "blue";

View File

@@ -1,5 +1,5 @@
import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "./custommarker";
import { CustomMarker } from "../markers/custommarker";
export class CoalitionAreaHandle extends CustomMarker {
constructor(latlng: LatLng) {

View File

@@ -1,5 +1,5 @@
import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "./custommarker";
import { CustomMarker } from "../markers/custommarker";
export class CoalitionAreaMiddleHandle extends CustomMarker {
constructor(latlng: LatLng) {

View File

@@ -1,5 +1,5 @@
import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "./custommarker";
import { CustomMarker } from "../markers/custommarker";
export class DrawingCursor extends CustomMarker {
constructor() {

View File

@@ -1,22 +1,23 @@
import * as L from "leaflet"
import { getMissionHandler, getUnitsManager } from "..";
import { getInfoPopup, getMissionHandler, getUnitsManager } from "..";
import { BoxSelect } from "./boxselect";
import { MapContextMenu } from "../controls/mapcontextmenu";
import { UnitContextMenu } from "../controls/unitcontextmenu";
import { AirbaseContextMenu } from "../controls/airbasecontextmenu";
import { MapContextMenu } from "../contextmenus/mapcontextmenu";
import { UnitContextMenu } from "../contextmenus/unitcontextmenu";
import { AirbaseContextMenu } from "../contextmenus/airbasecontextmenu";
import { Dropdown } from "../controls/dropdown";
import { Airbase } from "../mission/airbase";
import { Unit } from "../unit/unit";
import { bearing, createCheckboxOption } from "../other/utils";
import { DestinationPreviewMarker } from "./destinationpreviewmarker";
import { TemporaryUnitMarker } from "./temporaryunitmarker";
import { DestinationPreviewMarker } from "./markers/destinationpreviewmarker";
import { TemporaryUnitMarker } from "./markers/temporaryunitmarker";
import { ClickableMiniMap } from "./clickableminimap";
import { SVGInjector } from '@tanem/svg-injector'
import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTootlips, FIRE_AT_AREA, MOVE_UNIT, CARPET_BOMBING, BOMBING, SHOW_CONTACT_LINES, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, visibilityControlsTypes } from "../constants/constants";
import { TargetMarker } from "./targetmarker";
import { CoalitionArea } from "./coalitionarea";
import { CoalitionAreaContextMenu } from "../controls/coalitionareacontextmenu";
import { DrawingCursor } from "./drawingcursor";
import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTooltips, MOVE_UNIT, SHOW_CONTACT_LINES, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, visibilityControlsTypes, SHOW_UNIT_LABELS } from "../constants/constants";
import { TargetMarker } from "./markers/targetmarker";
import { CoalitionArea } from "./coalitionarea/coalitionarea";
import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu";
import { DrawingCursor } from "./coalitionarea/drawingcursor";
import { AirbaseSpawnContextMenu } from "../contextmenus/airbasespawnmenu";
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
@@ -45,7 +46,7 @@ export class Map extends L.Map {
#temporaryMarkers: TemporaryUnitMarker[] = [];
#selecting: boolean = false;
#isZooming: boolean = false;
#destinationGroupRotation: number = 0;
#computeDestinationRotation: boolean = false;
#destinationRotationCenter: L.LatLng | null = null;
@@ -54,18 +55,25 @@ export class Map extends L.Map {
#targetCursor: TargetMarker = new TargetMarker(new L.LatLng(0, 0), { interactive: false });
#destinationPreviewCursors: DestinationPreviewMarker[] = [];
#drawingCursor: DrawingCursor = new DrawingCursor();
#longPressHandled: boolean = false;
#longPressTimer: number = 0;
#mapContextMenu: MapContextMenu = new MapContextMenu("map-contextmenu");
#unitContextMenu: UnitContextMenu = new UnitContextMenu("unit-contextmenu");
#airbaseContextMenu: AirbaseContextMenu = new AirbaseContextMenu("airbase-contextmenu");
#airbaseSpawnMenu: AirbaseSpawnContextMenu = new AirbaseSpawnContextMenu("airbase-spawn-contextmenu");
#coalitionAreaContextMenu: CoalitionAreaContextMenu = new CoalitionAreaContextMenu("coalition-area-contextmenu");
#mapSourceDropdown: Dropdown;
#mapVisibilityOptionsDropdown: Dropdown;
#optionButtons: { [key: string]: HTMLButtonElement[] } = {}
#visibiityOptions: { [key: string]: boolean } = {}
#visibilityOptions: { [key: string]: boolean } = {}
constructor(ID: string) {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
/* Init the leaflet map */
//@ts-ignore Needed because the boxSelect option is non-standard
super(ID, { zoomSnap: 0, zoomDelta: 0.25, preferCanvas: true, doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true, zoomAnimation: true, maxBoundsViscosity: 1.0, minZoom: 7, keyboard: true, keyboardPanDelta: 0 });
@@ -89,7 +97,7 @@ export class Map extends L.Map {
this.#mapSourceDropdown = new Dropdown("map-type", (layerName: string) => this.setLayer(layerName), this.getLayers());
/* Visibility options dropdown */
this.#mapVisibilityOptionsDropdown = new Dropdown("map-visibility-options", () => {});
this.#mapVisibilityOptionsDropdown = new Dropdown("map-visibility-options", (value: string) => { });
/* Init the state machine */
this.#state = IDLE;
@@ -132,7 +140,6 @@ export class Map extends L.Map {
})
}
});
document.addEventListener("toggleCoalitionAreaDraw", (ev: CustomEventInit) => {
this.getMapContextMenu().hide();
@@ -150,9 +157,13 @@ export class Map extends L.Map {
this.#panToUnit(this.#centerUnit);
});
document.addEventListener("mapVisibilityOptionsChanged", () => {
this.getContainer().toggleAttribute("data-hide-labels", !this.getVisibilityOptions()[SHOW_UNIT_LABELS]);
});
/* Pan interval */
this.#panInterval = window.setInterval(() => {
if (this.#panLeft || this.#panDown || this.#panRight || this.#panLeft)
if (this.#panUp || this.#panDown || this.#panRight || this.#panLeft)
this.panBy(new L.Point(((this.#panLeft ? -1 : 0) + (this.#panRight ? 1 : 0)) * this.#deafultPanDelta,
((this.#panUp ? -1 : 0) + (this.#panDown ? 1 : 0)) * this.#deafultPanDelta));
}, 20);
@@ -160,19 +171,19 @@ export class Map extends L.Map {
/* Option buttons */
this.#optionButtons["visibility"] = visibilityControls.map((option: string, index: number) => {
var typesArrayString = `"${visibilityControlsTypes[index][0]}"`;
visibilityControlsTypes[index].forEach((type: string, idx: number) => {if (idx > 0) typesArrayString = `${typesArrayString}, "${type}"`});
return this.#createOptionButton(option, `visibility/${option.toLowerCase()}.svg`, visibilityControlsTootlips[index], "toggleMarkerVisibility", `{"types": [${typesArrayString}]}`);
visibilityControlsTypes[index].forEach((type: string, idx: number) => { if (idx > 0) typesArrayString = `${typesArrayString}, "${type}"` });
return this.#createOptionButton(option, `visibility/${option.toLowerCase()}.svg`, visibilityControlsTooltips[index], "toggleMarkerVisibility", `{"types": [${typesArrayString}]}`);
});
document.querySelector("#unit-visibility-control")?.append(...this.#optionButtons["visibility"]);
/* Create the checkboxes to select the advanced visibility options */
this.#visibiityOptions[SHOW_CONTACT_LINES] = false;
this.#visibiityOptions[HIDE_GROUP_MEMBERS] = true;
this.#visibiityOptions[SHOW_UNIT_PATHS] = true;
this.#visibiityOptions[SHOW_UNIT_TARGETS] = true;
this.#mapVisibilityOptionsDropdown.setOptionsElements(Object.keys(this.#visibiityOptions).map((option: string) => {
return createCheckboxOption(option, option, this.#visibiityOptions[option], (ev: any) => {
this.#visibilityOptions[SHOW_CONTACT_LINES] = false;
this.#visibilityOptions[HIDE_GROUP_MEMBERS] = true;
this.#visibilityOptions[SHOW_UNIT_PATHS] = true;
this.#visibilityOptions[SHOW_UNIT_TARGETS] = true;
this.#visibilityOptions[SHOW_UNIT_LABELS] = true;
this.#mapVisibilityOptionsDropdown.setOptionsElements(Object.keys(this.#visibilityOptions).map((option: string) => {
return createCheckboxOption(option, option, this.#visibilityOptions[option], (ev: any) => {
this.#setVisibilityOption(option, ev);
});
}));
@@ -237,6 +248,7 @@ export class Map extends L.Map {
this.hideMapContextMenu();
this.hideUnitContextMenu();
this.hideAirbaseContextMenu();
this.hideAirbaseSpawnMenu();
this.hideCoalitionAreaContextMenu();
}
@@ -282,6 +294,20 @@ export class Map extends L.Map {
this.#airbaseContextMenu.hide();
}
showAirbaseSpawnMenu(x: number, y: number, latlng: L.LatLng, airbase: Airbase) {
this.hideAllContextMenus();
this.#airbaseSpawnMenu.show(x, y);
this.#airbaseSpawnMenu.setAirbase(airbase);
}
getAirbaseSpawnMenu() {
return this.#airbaseSpawnMenu;
}
hideAirbaseSpawnMenu() {
this.#airbaseSpawnMenu.hide();
}
showCoalitionAreaContextMenu(x: number, y: number, latlng: L.LatLng, coalitionArea: CoalitionArea) {
this.hideAllContextMenus();
this.#coalitionAreaContextMenu.show(x, y, latlng);
@@ -399,29 +425,11 @@ export class Map extends L.Map {
}
}
addTemporaryMarker(latlng: L.LatLng, name: string, coalition: string) {
var marker = new TemporaryUnitMarker(latlng, name, coalition);
addTemporaryMarker(latlng: L.LatLng, name: string, coalition: string, commandHash?: string) {
var marker = new TemporaryUnitMarker(latlng, name, coalition, commandHash);
marker.addTo(this);
this.#temporaryMarkers.push(marker);
}
removeTemporaryMarker(latlng: L.LatLng) {
// TODO something more refined than this
var dist: number | null = null;
var closest: L.Marker | null = null;
var i: number = 0;
this.#temporaryMarkers.forEach((marker: L.Marker, idx: number) => {
var t = latlng.distanceTo(marker.getLatLng());
if (dist == null || t < dist) {
dist = t;
closest = marker;
i = idx;
}
});
if (closest && dist != null && dist < 100) {
this.removeLayer(closest);
this.#temporaryMarkers.splice(i, 1);
}
return marker;
}
getSelectedCoalitionArea() {
@@ -435,7 +443,7 @@ export class Map extends L.Map {
}
getVisibilityOptions() {
return this.#visibiityOptions;
return this.#visibilityOptions;
}
/* Event handlers */
@@ -465,15 +473,23 @@ export class Map extends L.Map {
}
#onContextMenu(e: any) {
/* A long press will show the point action context menu */
window.clearInterval(this.#longPressTimer);
if (this.#longPressHandled) {
this.#longPressHandled = false;
return;
}
this.hideMapContextMenu();
if (this.#state === IDLE) {
if (this.#state == IDLE) {
this.showMapContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng);
var clickedCoalitionArea = null;
/* Coalition areas are ordered in the #coalitionAreas array according to their zindex. Select the upper one */
for (let coalitionArea of this.#coalitionAreas) {
if (coalitionArea.getBounds().contains(e.latlng)) {
if (coalitionArea.getSelected())
if (coalitionArea.getSelected())
clickedCoalitionArea = coalitionArea;
else
this.getMapContextMenu().setCoalitionArea(coalitionArea);
@@ -488,22 +504,11 @@ export class Map extends L.Map {
getUnitsManager().selectedUnitsClearDestinations();
}
getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, this.#shiftKey, this.#destinationGroupRotation)
this.#destinationGroupRotation = 0;
this.#destinationRotationCenter = null;
this.#computeDestinationRotation = false;
}
else if (this.#state === BOMBING) {
getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates());
}
else if (this.#state === CARPET_BOMBING) {
getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates());
}
else if (this.#state === FIRE_AT_AREA) {
getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates());
}
else {
this.setState(IDLE);
}
@@ -537,6 +542,56 @@ export class Map extends L.Map {
this.#destinationRotationCenter = this.getMouseCoordinates();
}
}
this.#longPressTimer = window.setTimeout(() => {
if (e.originalEvent.button != 2 || e.originalEvent.ctrlKey)
return;
this.hideMapContextMenu();
this.#longPressHandled = true;
var options: { [key: string]: { text: string, tooltip: string } } = {};
const selectedUnits = getUnitsManager().getSelectedUnits();
const selectedUnitTypes = getUnitsManager().getSelectedUnitsTypes();
if (selectedUnitTypes.length === 1 && ["Aircraft", "Helicopter"].includes(selectedUnitTypes[0])) {
if (selectedUnits.every((unit: Unit) => { return unit.canFulfillRole(["CAS", "Strike"]) })) {
options["bomb"] = { text: "Precision bombing", tooltip: "Precision bombing of a specific point" };
options["carpet-bomb"] = { text: "Carpet bombing", tooltip: "Carpet bombing close to a point" };
} else {
getInfoPopup().setText(`Selected units can not perform point actions.`);
}
}
else if (selectedUnitTypes.length === 1 && ["GroundUnit", "NavyUnit"].includes(selectedUnitTypes[0])) {
if (selectedUnits.every((unit: Unit) => { return ["Gun Artillery", "Rocket Artillery", "Infantry", "IFV", "Tank", "Cruiser", "Destroyer", "Frigate"].includes(unit.getType()) }))
options["fire-at-area"] = { text: "Fire at area", tooltip: "Fire at a large area" };
else
getInfoPopup().setText(`Selected units can not perform point actions.`);
}
else {
getInfoPopup().setText(`Multiple unit types selected, no common actions available.`);
}
if (Object.keys(options).length > 0) {
this.showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng);
this.getUnitContextMenu().setOptions(options, (option: string) => {
this.hideUnitContextMenu();
if (option === "bomb") {
getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates());
}
else if (option === "carpet-bomb") {
getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates());
}
else if (option === "fire-at-area") {
getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates());
}
});
}
}, 150);
this.#longPressHandled = false;
}
#onMouseUp(e: any) {
@@ -554,9 +609,6 @@ export class Map extends L.Map {
this.#destinationGroupRotation = -bearing(this.#destinationRotationCenter.lat, this.#destinationRotationCenter.lng, this.getMouseCoordinates().lat, this.getMouseCoordinates().lng);
this.#updateDestinationCursors();
}
else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) {
this.#targetCursor.setLatLng(this.getMouseCoordinates());
}
else if (this.#state === COALITIONAREA_DRAW_POLYGON) {
this.#drawingCursor.setLatLng(e.latlng);
/* Update the polygon being drawn with the current position of the mouse cursor */
@@ -659,7 +711,7 @@ export class Map extends L.Map {
else {
Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(groupLatLng, this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => {
if (idx < this.#destinationPreviewCursors.length)
this.#destinationPreviewCursors[idx].setLatLng(this.#shiftKey? latlng : this.getMouseCoordinates());
this.#destinationPreviewCursors[idx].setLatLng(this.#shiftKey ? latlng : this.getMouseCoordinates());
})
};
}
@@ -712,19 +764,19 @@ export class Map extends L.Map {
/* Hide all the unnecessary cursors depending on the active state */
if (this.#state !== IDLE) this.#hideDefaultCursor();
if (this.#state !== MOVE_UNIT) this.#hideDestinationCursors();
if (![BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) this.#hideTargetCursor();
//if (![BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) this.#hideTargetCursor();
if (this.#state !== COALITIONAREA_DRAW_POLYGON) this.#hideDrawingCursor();
/* Show the active cursor depending on the active state */
if (this.#state === IDLE) this.#showDefaultCursor();
else if (this.#state === MOVE_UNIT) this.#showDestinationCursors();
else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) this.#showTargetCursor();
//else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) this.#showTargetCursor();
else if (this.#state === COALITIONAREA_DRAW_POLYGON) this.#showDrawingCursor();
}
}
#setVisibilityOption(option: string, ev: any) {
this.#visibiityOptions[option] = ev.currentTarget.checked;
this.#visibilityOptions[option] = ev.currentTarget.checked;
document.dispatchEvent(new CustomEvent("mapVisibilityOptionsChanged"));
}
}

View File

@@ -0,0 +1,31 @@
import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
import { CustomMarker } from "./custommarker";
import { SVGInjector } from "@tanem/svg-injector";
import { getMap } from "../..";
export class SmokeMarker extends CustomMarker {
#color: string;
constructor(latlng: LatLngExpression, color: string, options?: MarkerOptions) {
super(latlng, options);
this.setZIndexOffset(9999);
this.#color = color;
window.setTimeout(() => { this.removeFrom(getMap()); }, 300000) /* Remove the smoke after 5 minutes */
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [52, 52],
iconAnchor: [26, 52],
className: "leaflet-smoke-marker",
}));
var el = document.createElement("div");
el.classList.add("ol-smoke-icon");
el.setAttribute("data-color", this.#color);
var img = document.createElement("img");
img.src = "/resources/theme/images/markers/smoke.svg";
img.onload = () => SVGInjector(img);
el.appendChild(img);
this.getElement()?.appendChild(el);
}
}

View File

@@ -1,16 +1,38 @@
import { CustomMarker } from "./custommarker";
import { DivIcon, LatLng } from "leaflet";
import { SVGInjector } from "@tanem/svg-injector";
import { getMarkerCategoryByName, getUnitDatabaseByCategory } from "../other/utils";
import { getMarkerCategoryByName, getUnitDatabaseByCategory } from "../../other/utils";
import { isCommandExecuted } from "../../server/server";
import { getMap } from "../..";
export class TemporaryUnitMarker extends CustomMarker {
#name: string;
#coalition: string;
#commandHash: string|undefined = undefined;
#timer: number = 0;
constructor(latlng: LatLng, name: string, coalition: string) {
constructor(latlng: LatLng, name: string, coalition: string, commandHash?: string) {
super(latlng, {interactive: false});
this.#name = name;
this.#coalition = coalition;
this.#commandHash = commandHash;
if (commandHash !== undefined)
this.setCommandHash(commandHash)
}
setCommandHash(commandHash: string) {
this.#commandHash = commandHash;
this.#timer = window.setInterval(() => {
if (this.#commandHash !== undefined) {
isCommandExecuted((res: any) => {
if (res.commandExecuted) {
this.removeFrom(getMap());
window.clearInterval(this.#timer);
}
}, this.#commandHash)
}
}, 1000);
}
createIcon() {

View File

@@ -1,5 +1,5 @@
import { DivIcon } from 'leaflet';
import { CustomMarker } from '../map/custommarker';
import { CustomMarker } from '../map/markers/custommarker';
import { SVGInjector } from '@tanem/svg-injector';
export interface AirbaseOptions {

View File

@@ -1,5 +1,5 @@
import { DivIcon } from "leaflet";
import { CustomMarker } from "../map/custommarker";
import { CustomMarker } from "../map/markers/custommarker";
import { SVGInjector } from "@tanem/svg-injector";
export class Bullseye extends CustomMarker {

View File

@@ -1,17 +1,17 @@
import { LatLng } from "leaflet";
import { getInfoPopup, getMap } from "..";
import { Airbase, AirbaseChartData } from "./airbase";
import { Airbase } from "./airbase";
import { Bullseye } from "./bullseye";
import { BLUE_COMMANDER, GAME_MASTER, NONE, RED_COMMANDER } from "../constants/constants";
import { refreshAll, setCommandModeOptions } from "../server/server";
import { Dropdown } from "../controls/dropdown";
import { groundUnitDatabase } from "../unit/groundunitdatabase";
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
import { createCheckboxOption, getCheckboxOptions } from "../other/utils";
import { aircraftDatabase } from "../unit/aircraftdatabase";
import { helicopterDatabase } from "../unit/helicopterdatabase";
import { navyUnitDatabase } from "../unit/navyunitdatabase";
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
import { helicopterDatabase } from "../unit/databases/helicopterdatabase";
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
export class MissionHandler {
export class MissionManager {
#bullseyes: { [name: string]: Bullseye } = {};
#airbases: { [name: string]: Airbase } = {};
#theatre: string = "";
@@ -79,7 +79,7 @@ export class MissionHandler {
this.#dateAndTime = data.mission.dateAndTime;
/* Set the coalition countries */
//this.#coalitions = data.mission.coalitions;
this.#coalitions = data.mission.coalitions;
/* Set the command mode options */
this.#setcommandModeOptions(data.mission.commandModeOptions);

View File

@@ -1,12 +1,14 @@
import { LatLng, Point, Polygon } from "leaflet";
import * as turf from "@turf/turf";
import { UnitDatabase } from "../unit/unitdatabase";
import { aircraftDatabase } from "../unit/aircraftdatabase";
import { helicopterDatabase } from "../unit/helicopterdatabase";
import { groundUnitDatabase } from "../unit/groundunitdatabase";
import { UnitDatabase } from "../unit/databases/unitdatabase";
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
import { helicopterDatabase } from "../unit/databases/helicopterdatabase";
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
import { Buffer } from "buffer";
import { ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants";
import { Dropdown } from "../controls/dropdown";
import { UnitBlueprint } from "../@types/unitdatabase";
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) {
const φ1 = deg2rad(lat1); // φ, λ in radians
@@ -296,12 +298,14 @@ export function getMarkerCategoryByName(name: string) {
}
export function getUnitDatabaseByCategory(category: string) {
if (category == "aircraft")
if (category.toLowerCase() == "aircraft")
return aircraftDatabase;
else if (category == "helicopter")
else if (category.toLowerCase() == "helicopter")
return helicopterDatabase;
else if (category.includes("groundunit"))
else if (category.toLowerCase().includes("groundunit"))
return groundUnitDatabase;
else if (category.toLowerCase().includes("navyunit"))
return navyUnitDatabase;
else
return null;
}

View File

@@ -1,7 +1,11 @@
import { Panel } from "./panel";
export class ConnectionStatusPanel extends Panel {
constructor(ID: string) {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
}

View File

@@ -3,7 +3,11 @@ import { Unit } from "../unit/unit";
import { Panel } from "./panel";
export class HotgroupPanel extends Panel {
constructor(ID: string) {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
document.addEventListener("unitDeath", () => this.refreshHotgroups());
}

View File

@@ -7,7 +7,11 @@ export class LogPanel extends Panel {
#scrolledDown: boolean = true;
#logs: {[key: string]: string} = {};
constructor(ID: string) {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
document.addEventListener("toggleLogPanel", () => {
@@ -15,6 +19,7 @@ export class LogPanel extends Panel {
this.#open = !this.#open;
this.#queuedMessages = 0;
this.#updateHeader();
this.#calculateHeight();
if (this.#scrolledDown)
this.#scrollDown();
@@ -85,6 +90,9 @@ export class LogPanel extends Panel {
#calculateHeight() {
const mouseInfoPanel = getMouseInfoPanel();
this.getElement().style.height = `${mouseInfoPanel.getElement().offsetTop - this.getElement().offsetTop - 10}px`;
if (this.#open)
this.getElement().style.height = `${mouseInfoPanel.getElement().offsetTop - this.getElement().offsetTop - 10}px`;
else
this.getElement().style.height = "fit-content";
}
}

View File

@@ -12,7 +12,11 @@ export class MouseInfoPanel extends Panel {
#measureLine: Polyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1, interactive: false });
#measureBox: HTMLElement;
constructor(ID: string) {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
this.#measureIcon = new Icon({ iconUrl: 'resources/theme/images/icons/pin.svg', iconAnchor: [16, 32] });
@@ -124,9 +128,9 @@ export class MouseInfoPanel extends Panel {
this.#drawMeasureLine();
}
#drawMeasure(imgId: string | null, textId: string, value: LatLng | null, mousePosition: LatLng) {
var el = this.getElement().querySelector(`#${textId}`) as HTMLElement;
var img = imgId != null ? this.getElement().querySelector(`#${imgId}`) as HTMLElement : null;
#drawMeasure(imgID: string | null, textID: string, value: LatLng | null, mousePosition: LatLng) {
var el = this.getElement().querySelector(`#${textID}`) as HTMLElement;
var img = imgID != null ? this.getElement().querySelector(`#${imgID}`) as HTMLElement : null;
if (value) {
if (el != null) {
el.classList.remove("hide");
@@ -156,9 +160,9 @@ export class MouseInfoPanel extends Panel {
}
}
#drawCoordinates(imgId: string, textId: string, value: string) {
const el = this.getElement().querySelector(`#${textId}`) as HTMLElement;
const img = this.getElement().querySelector(`#${imgId}`) as HTMLElement;
#drawCoordinates(imgID: string, textID: string, value: string) {
const el = this.getElement().querySelector(`#${textID}`) as HTMLElement;
const img = this.getElement().querySelector(`#${imgID}`) as HTMLElement;
if (img && el) {
el.dataset.value = value.substring(1);
img.dataset.label = value[0];

View File

@@ -2,8 +2,12 @@ export class Panel {
#element: HTMLElement
#visible: boolean = true;
constructor(ID: string) {
this.#element = <HTMLElement>document.getElementById(ID);
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
this.#element = document.getElementById(ID) as HTMLElement;
}
show() {

View File

@@ -1,7 +1,11 @@
import { Panel } from "./panel";
export class ServerStatusPanel extends Panel {
constructor(ID: string) {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
}

View File

@@ -2,7 +2,7 @@ import { SVGInjector } from "@tanem/svg-injector";
import { getUnitsManager } from "..";
import { Dropdown } from "../controls/dropdown";
import { Slider } from "../controls/slider";
import { aircraftDatabase } from "../unit/aircraftdatabase";
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
import { Unit } from "../unit/unit";
import { Panel } from "./panel";
import { Switch } from "../controls/switch";
@@ -25,15 +25,19 @@ export class UnitControlPanel extends Panel {
#units: Unit[] = [];
#selectedUnitsTypes: string[] = [];
constructor(ID: string) {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
/* Unit control sliders */
this.#altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => { getUnitsManager().selectedUnitsSetAltitude(ftToM(value)); });
this.#altitudeTypeSwitch = new Switch("altitude-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetAltitudeType(value? "AGL": "ASL"); });
this.#altitudeTypeSwitch = new Switch("altitude-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetAltitudeType(value? "ASL": "AGL"); });
this.#speedSlider = new Slider("speed-slider", 0, 100, "kts", (value: number) => { getUnitsManager().selectedUnitsSetSpeed(knotsToMs(value)); });
this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetSpeedType(value? "GS": "CAS"); });
this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetSpeedType(value? "CAS": "GS"); });
/* Option buttons */
// Reversing the ROEs so that the least "aggressive" option is always on the left
@@ -164,8 +168,8 @@ export class UnitControlPanel extends Panel {
var onOff = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getOnOff()});
var followRoads = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getFollowRoads()});
this.#altitudeTypeSwitch.setValue(desiredAltitudeType != undefined? desiredAltitudeType == "AGL": undefined, false);
this.#speedTypeSwitch.setValue(desiredSpeedType != undefined? desiredSpeedType == "GS": undefined, false);
this.#altitudeTypeSwitch.setValue(desiredAltitudeType != undefined? desiredAltitudeType == "ASL": undefined, false);
this.#speedTypeSwitch.setValue(desiredSpeedType != undefined? desiredSpeedType == "CAS": undefined, false);
this.#speedSlider.setMinMax(minSpeedValues[this.#selectedUnitsTypes[0]], maxSpeedValues[this.#selectedUnitsTypes[0]]);
this.#altitudeSlider.setMinMax(minAltitudeValues[this.#selectedUnitsTypes[0]], maxAltitudeValues[this.#selectedUnitsTypes[0]]);
@@ -225,7 +229,7 @@ export class UnitControlPanel extends Panel {
const unit = units[0];
const roles = aircraftDatabase.getByName(unit.getName())?.loadouts?.map((loadout) => {return loadout.roles})
const tanker = roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Tanker");
const tanker = roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Refueling");
const AWACS = roles != undefined && Array.prototype.concat.apply([], roles)?.includes("AWACS");
const radioMHz = Math.floor(unit.getRadio().frequency / 1000000);
const radioDecimals = (unit.getRadio().frequency / 1000000 - radioMHz) * 1000;

View File

@@ -1,41 +1,29 @@
import { getUnitsManager } from "..";
import { Ammo } from "../@types/unit";
import { ConvertDDToDMS, rad2deg } from "../other/utils";
import { aircraftDatabase } from "../unit/aircraftdatabase";
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
import { Unit } from "../unit/unit";
import { Panel } from "./panel";
export class UnitInfoPanel extends Panel {
#altitude: HTMLElement;
#currentTask: HTMLElement;
#fuelBar: HTMLElement;
#fuelPercentage: HTMLElement;
#groundSpeed: HTMLElement;
#groupName: HTMLElement;
#heading: HTMLElement;
#name: HTMLElement;
#latitude: HTMLElement;
#longitude: HTMLElement;
#loadoutContainer: HTMLElement;
#silhouette: HTMLImageElement;
#unitControl: HTMLElement;
#unitLabel: HTMLElement;
#unitName: HTMLElement;
constructor(ID: string) {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
this.#altitude = (this.getElement().querySelector("#altitude")) as HTMLElement;
this.#currentTask = (this.getElement().querySelector("#current-task")) as HTMLElement;
this.#groundSpeed = (this.getElement().querySelector("#ground-speed")) as HTMLElement;
this.#fuelBar = (this.getElement().querySelector("#fuel-bar")) as HTMLElement;
this.#fuelPercentage = (this.getElement().querySelector("#fuel-percentage")) as HTMLElement;
this.#groupName = (this.getElement().querySelector("#group-name")) as HTMLElement;
this.#heading = (this.getElement().querySelector("#heading")) as HTMLElement;
this.#name = (this.getElement().querySelector("#name")) as HTMLElement;
this.#latitude = (this.getElement().querySelector("#latitude")) as HTMLElement;
this.#loadoutContainer = (this.getElement().querySelector("#loadout-container")) as HTMLElement;
this.#longitude = (this.getElement().querySelector("#longitude")) as HTMLElement;
this.#silhouette = (this.getElement().querySelector("#loadout-silhouette")) as HTMLImageElement;
this.#unitControl = (this.getElement().querySelector("#unit-control")) as HTMLElement;
this.#unitLabel = (this.getElement().querySelector("#unit-label")) as HTMLElement;
@@ -52,14 +40,12 @@ export class UnitInfoPanel extends Panel {
#onUnitUpdate(unit: Unit) {
if (this.getElement() != null && this.getVisible() && unit.getSelected()) {
const baseData = unit.getData();
/* Set the unit info */
this.#unitLabel.innerText = aircraftDatabase.getByName(baseData.name)?.label || baseData.name;
this.#unitName.innerText = baseData.unitName;
this.#unitLabel.innerText = aircraftDatabase.getByName(unit.getName())?.label || unit.getName();
this.#unitName.innerText = unit.getUnitName();
if (unit.getHuman())
this.#unitControl.innerText = "Human";
else if (baseData.controlled)
else if (unit.getControlled())
this.#unitControl.innerText = "Olympus controlled";
else
this.#unitControl.innerText = "DCS Controlled";
@@ -68,11 +54,11 @@ export class UnitInfoPanel extends Panel {
this.#currentTask.dataset.currentTask = unit.getTask() !== "" ? unit.getTask() : "No task";
this.#currentTask.dataset.coalition = unit.getCoalition();
this.#silhouette.src = `/images/units/${unit.getDatabase()?.getByName(baseData.name)?.filename}`;
this.#silhouette.classList.toggle("hide", unit.getDatabase()?.getByName(baseData.name)?.filename == undefined || unit.getDatabase()?.getByName(baseData.name)?.filename == '');
this.#silhouette.src = `/images/units/${unit.getDatabase()?.getByName(unit.getName())?.filename}`;
this.#silhouette.classList.toggle("hide", unit.getDatabase()?.getByName(unit.getName())?.filename == undefined || unit.getDatabase()?.getByName(unit.getName())?.filename == '');
/* Add the loadout elements */
const items = <HTMLElement>this.#loadoutContainer.querySelector("#loadout-items");
const items = this.#loadoutContainer.querySelector("#loadout-items") as HTMLElement;
if (items) {
const ammo = Object.values(unit.getAmmo());

View File

@@ -1,19 +1,13 @@
import { LatLng } from 'leaflet';
import { getConnectionStatusPanel, getInfoPopup, getLogPanel, getMissionHandler, getServerStatusPanel, getUnitsManager, getWeaponsManager, setLoginStatus } from '..';
import { GeneralSettings, Radio, TACAN } from '../@types/unit';
import { NONE, ROEs, emissionsCountermeasures, reactionsToThreat } from '../constants/constants';
import { AIRBASES_URI, BULLSEYE_URI, COMMANDS_URI, LOGS_URI, MISSION_URI, NONE, ROEs, UNITS_URI, WEAPONS_URI, emissionsCountermeasures, reactionsToThreat } from '../constants/constants';
var connected: boolean = false;
var paused: boolean = false;
var REST_ADDRESS = "http://localhost:30000/olympus";
var DEMO_ADDRESS = window.location.href + "demo";
const UNITS_URI = "units";
const WEAPONS_URI = "weapons";
const LOGS_URI = "logs";
const AIRBASES_URI = "airbases";
const BULLSEYE_URI = "bullseyes";
const MISSION_URI = "mission";
var username = "";
var password = "";
@@ -38,13 +32,15 @@ export function setCredentials(newUsername: string, newPassword: string) {
password = newPassword;
}
export function GET(callback: CallableFunction, uri: string, options?: { time?: number }, responseType?: string) {
export function GET(callback: CallableFunction, uri: string, options?: ServerRequestOptions, responseType?: string) {
var xmlHttp = new XMLHttpRequest();
/* Assemble the request options string */
var optionsString = '';
if (options?.time != undefined)
optionsString = `time=${options.time}`;
if (options?.commandHash != undefined)
optionsString = `commandHash=${options.commandHash}`;
/* On the connection */
xmlHttp.open("GET", `${demoEnabled ? DEMO_ADDRESS : REST_ADDRESS}/${uri}${optionsString ? `?${optionsString}` : ''}`, true);
@@ -92,8 +88,9 @@ export function POST(request: object, callback: CallableFunction) {
xmlHttp.setRequestHeader("Content-Type", "application/json");
if (username && password)
xmlHttp.setRequestHeader("Authorization", "Basic " + btoa(`${username}:${password}`));
xmlHttp.onreadystatechange = () => {
callback();
xmlHttp.onload = (res: any) => {
var res = JSON.parse(xmlHttp.responseText);
callback(res);
};
xmlHttp.send(JSON.stringify(request));
}
@@ -140,185 +137,189 @@ export function getWeapons(callback: CallableFunction, refresh: boolean = false)
GET(callback, WEAPONS_URI, { time: refresh ? 0 : lastUpdateTimes[WEAPONS_URI] }, 'arraybuffer');
}
export function addDestination(ID: number, path: any) {
export function isCommandExecuted(callback: CallableFunction, commandHash: string) {
GET(callback, COMMANDS_URI, { commandHash: commandHash});
}
export function addDestination(ID: number, path: any, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "path": path }
var data = { "setPath": command }
POST(data, () => { });
POST(data, callback);
}
export function spawnSmoke(color: string, latlng: LatLng) {
export function spawnSmoke(color: string, latlng: LatLng, callback: CallableFunction = () => {}) {
var command = { "color": color, "location": latlng };
var data = { "smoke": command }
POST(data, () => { });
POST(data, callback);
}
export function spawnExplosion(intensity: number, latlng: LatLng) {
export function spawnExplosion(intensity: number, latlng: LatLng, callback: CallableFunction = () => {}) {
var command = { "intensity": intensity, "location": latlng };
var data = { "explosion": command }
POST(data, () => { });
POST(data, callback);
}
export function spawnAircrafts(units: any, coalition: string, airbaseName: string, immediate: boolean, spawnPoints: number) {
var command = { "units": units, "coalition": coalition, "airbaseName": airbaseName, "immediate": immediate, "spawnPoints": spawnPoints };
export function spawnAircrafts(units: any, coalition: string, airbaseName: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
var command = { "units": units, "coalition": coalition, "airbaseName": airbaseName, "country": country, "immediate": immediate, "spawnPoints": spawnPoints };
var data = { "spawnAircrafts": command }
POST(data, () => { });
POST(data, callback);
}
export function spawnHelicopters(units: any, coalition: string, airbaseName: string, immediate: boolean, spawnPoints: number) {
var command = { "units": units, "coalition": coalition, "airbaseName": airbaseName, "immediate": immediate, "spawnPoints": spawnPoints };
export function spawnHelicopters(units: any, coalition: string, airbaseName: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
var command = { "units": units, "coalition": coalition, "airbaseName": airbaseName, "country": country, "immediate": immediate, "spawnPoints": spawnPoints };
var data = { "spawnHelicopters": command }
POST(data, () => { });
POST(data, callback);
}
export function spawnGroundUnits(units: any, coalition: string, immediate: boolean, spawnPoints: number) {
var command = { "units": units, "coalition": coalition, "immediate": immediate, "spawnPoints": spawnPoints };;
export function spawnGroundUnits(units: any, coalition: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
var command = { "units": units, "coalition": coalition, "country": country, "immediate": immediate, "spawnPoints": spawnPoints };;
var data = { "spawnGroundUnits": command }
POST(data, () => { });
POST(data, callback);
}
export function spawnNavyUnits(units: any, coalition: string, immediate: boolean, spawnPoints: number) {
var command = { "units": units, "coalition": coalition, "immediate": immediate, "spawnPoints": spawnPoints };
export function spawnNavyUnits(units: any, coalition: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
var command = { "units": units, "coalition": coalition, "country": country, "immediate": immediate, "spawnPoints": spawnPoints };
var data = { "spawnNavyUnits": command }
POST(data, () => { });
POST(data, callback);
}
export function attackUnit(ID: number, targetID: number) {
export function attackUnit(ID: number, targetID: number, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "targetID": targetID };
var data = { "attackUnit": command }
POST(data, () => { });
POST(data, callback);
}
export function followUnit(ID: number, targetID: number, offset: { "x": number, "y": number, "z": number }) {
export function followUnit(ID: number, targetID: number, offset: { "x": number, "y": number, "z": number }, callback: CallableFunction = () => {}) {
// X: front-rear, positive front
// Y: top-bottom, positive bottom
// Z: left-right, positive right
var command = { "ID": ID, "targetID": targetID, "offsetX": offset["x"], "offsetY": offset["y"], "offsetZ": offset["z"] };
var data = { "followUnit": command }
POST(data, () => { });
POST(data, callback);
}
export function cloneUnit(ID: number, latlng: LatLng) {
var command = { "ID": ID, "location": latlng };
var data = { "cloneUnit": command }
POST(data, () => { });
export function cloneUnits(units: {ID: number, location: LatLng}[], deleteOriginal: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
var command = { "units": units, "deleteOriginal": deleteOriginal, "spawnPoints": spawnPoints };
var data = { "cloneUnits": command }
POST(data, callback);
}
export function deleteUnit(ID: number, explosion: boolean, immediate: boolean) {
export function deleteUnit(ID: number, explosion: boolean, immediate: boolean, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "explosion": explosion, "immediate": immediate };
var data = { "deleteUnit": command }
POST(data, () => { });
POST(data, callback);
}
export function landAt(ID: number, latlng: LatLng) {
export function landAt(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "location": latlng };
var data = { "landAt": command }
POST(data, () => { });
POST(data, callback);
}
export function changeSpeed(ID: number, speedChange: string) {
export function changeSpeed(ID: number, speedChange: string, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "change": speedChange }
var data = { "changeSpeed": command }
POST(data, () => { });
POST(data, callback);
}
export function setSpeed(ID: number, speed: number) {
export function setSpeed(ID: number, speed: number, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "speed": speed }
var data = { "setSpeed": command }
POST(data, () => { });
POST(data, callback);
}
export function setSpeedType(ID: number, speedType: string) {
export function setSpeedType(ID: number, speedType: string, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "speedType": speedType }
var data = { "setSpeedType": command }
POST(data, () => { });
POST(data, callback);
}
export function changeAltitude(ID: number, altitudeChange: string) {
export function changeAltitude(ID: number, altitudeChange: string, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "change": altitudeChange }
var data = { "changeAltitude": command }
POST(data, () => { });
POST(data, callback);
}
export function setAltitudeType(ID: number, altitudeType: string) {
export function setAltitudeType(ID: number, altitudeType: string, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "altitudeType": altitudeType }
var data = { "setAltitudeType": command }
POST(data, () => { });
POST(data, callback);
}
export function setAltitude(ID: number, altitude: number) {
export function setAltitude(ID: number, altitude: number, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "altitude": altitude }
var data = { "setAltitude": command }
POST(data, () => { });
POST(data, callback);
}
export function createFormation(ID: number, isLeader: boolean, wingmenIDs: number[]) {
export function createFormation(ID: number, isLeader: boolean, wingmenIDs: number[], callback: CallableFunction = () => {}) {
var command = { "ID": ID, "wingmenIDs": wingmenIDs, "isLeader": isLeader }
var data = { "setLeader": command }
POST(data, () => { });
POST(data, callback);
}
export function setROE(ID: number, ROE: string) {
export function setROE(ID: number, ROE: string, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "ROE": ROEs.indexOf(ROE) }
var data = { "setROE": command }
POST(data, () => { });
POST(data, callback);
}
export function setReactionToThreat(ID: number, reactionToThreat: string) {
export function setReactionToThreat(ID: number, reactionToThreat: string, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "reactionToThreat": reactionsToThreat.indexOf(reactionToThreat) }
var data = { "setReactionToThreat": command }
POST(data, () => { });
POST(data, callback);
}
export function setEmissionsCountermeasures(ID: number, emissionCountermeasure: string) {
export function setEmissionsCountermeasures(ID: number, emissionCountermeasure: string, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "emissionsCountermeasures": emissionsCountermeasures.indexOf(emissionCountermeasure) }
var data = { "setEmissionsCountermeasures": command }
POST(data, () => { });
POST(data, callback);
}
export function setOnOff(ID: number, onOff: boolean) {
export function setOnOff(ID: number, onOff: boolean, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "onOff": onOff }
var data = { "setOnOff": command }
POST(data, () => { });
POST(data, callback);
}
export function setFollowRoads(ID: number, followRoads: boolean) {
export function setFollowRoads(ID: number, followRoads: boolean, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "followRoads": followRoads }
var data = { "setFollowRoads": command }
POST(data, () => { });
POST(data, callback);
}
export function refuel(ID: number) {
export function refuel(ID: number, callback: CallableFunction = () => {}) {
var command = { "ID": ID };
var data = { "refuel": command }
POST(data, () => { });
POST(data, callback);
}
export function bombPoint(ID: number, latlng: LatLng) {
export function bombPoint(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "location": latlng }
var data = { "bombPoint": command }
POST(data, () => { });
POST(data, callback);
}
export function carpetBomb(ID: number, latlng: LatLng) {
export function carpetBomb(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "location": latlng }
var data = { "carpetBomb": command }
POST(data, () => { });
POST(data, callback);
}
export function bombBuilding(ID: number, latlng: LatLng) {
export function bombBuilding(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "location": latlng }
var data = { "bombBuilding": command }
POST(data, () => { });
POST(data, callback);
}
export function fireAtArea(ID: number, latlng: LatLng) {
export function fireAtArea(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
var command = { "ID": ID, "location": latlng }
var data = { "fireAtArea": command }
POST(data, () => { });
POST(data, callback);
}
export function setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings) {
export function setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings, callback: CallableFunction = () => {}) {
var command = {
"ID": ID,
"isTanker": isTanker,
@@ -329,10 +330,10 @@ export function setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolea
};
var data = { "setAdvancedOptions": command };
POST(data, () => { });
POST(data, callback);
}
export function setCommandModeOptions(restrictSpawns: boolean, restrictToCoalition: boolean, spawnPoints: {blue: number, red: number}, eras: string[], setupTime: number) {
export function setCommandModeOptions(restrictSpawns: boolean, restrictToCoalition: boolean, spawnPoints: {blue: number, red: number}, eras: string[], setupTime: number, callback: CallableFunction = () => {}) {
var command = {
"restrictSpawns": restrictSpawns,
"restrictToCoalition": restrictToCoalition,
@@ -342,7 +343,7 @@ export function setCommandModeOptions(restrictSpawns: boolean, restrictToCoaliti
};
var data = { "setCommandModeOptions": command };
POST(data, () => { });
POST(data, callback);
}
export function startUpdate() {

View File

@@ -0,0 +1,5 @@
import { Toolbar } from "./toolbar";
export class CommandModeToolbar extends Toolbar {
// TODO move here all code about the command mode toolbar
}

View File

@@ -0,0 +1,13 @@
import { Dropdown } from "../controls/dropdown";
import { Toolbar } from "./toolbar";
export class PrimaryToolbar extends Toolbar {
constructor(ID: string) {
super(ID);
// TODO move here all code about primary toolbar
/* The content of the dropdown is entirely defined in the .ejs file */
new Dropdown("app-icon", () => { });
}
}

View File

@@ -0,0 +1,38 @@
export class Toolbar {
#element: HTMLElement
#visible: boolean = true;
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
this.#element = document.getElementById(ID) as HTMLElement;
}
show() {
this.#element.classList.toggle("hide", false);
this.#visible = true;
}
hide() {
this.#element.classList.toggle("hide", true);
this.#visible = false;
}
toggle() {
// Simple way to track if currently visible
if (this.#visible)
this.hide();
else
this.show();
}
getElement() {
return this.#element;
}
getVisible(){
return this.#visible;
}
}

View File

@@ -1,5 +1,5 @@
import { getMissionHandler } from "..";
import { GAME_MASTER } from "../constants/constants";
import { getMissionHandler } from "../..";
import { GAME_MASTER } from "../../constants/constants";
import { UnitDatabase } from "./unitdatabase"
export class AircraftDatabase extends UnitDatabase {

View File

@@ -1,5 +1,5 @@
import { getMissionHandler} from "..";
import { GAME_MASTER } from "../constants/constants";
import { getMissionHandler } from "../..";
import { GAME_MASTER } from "../../constants/constants";
import { UnitDatabase } from "./unitdatabase"
export class GroundUnitDatabase extends UnitDatabase {

View File

@@ -1,5 +1,5 @@
import { getMissionHandler } from "..";
import { GAME_MASTER } from "../constants/constants";
import { getMissionHandler } from "../..";
import { GAME_MASTER } from "../../constants/constants";
import { UnitDatabase } from "./unitdatabase"
export class HelicopterDatabase extends UnitDatabase {

View File

@@ -1,5 +1,5 @@
import { getMissionHandler } from "..";
import { GAME_MASTER } from "../constants/constants";
import { getMissionHandler } from "../..";
import { GAME_MASTER } from "../../constants/constants";
import { UnitDatabase } from "./unitdatabase"
export class NavyUnitDatabase extends UnitDatabase {

View File

@@ -1,6 +1,7 @@
import { LatLng } from "leaflet";
import { getMissionHandler, getUnitsManager } from "..";
import { GAME_MASTER } from "../constants/constants";
import { getMissionHandler, getUnitsManager } from "../..";
import { GAME_MASTER } from "../../constants/constants";
import { UnitBlueprint } from "../../@types/unitdatabase";
export class UnitDatabase {
blueprints: { [key: string]: UnitBlueprint } = {};
@@ -170,6 +171,15 @@ export class UnitDatabase {
return loadoutsByRole;
}
/* Get the livery names for a specific unit */
getLiveryNamesByName(name: string) {
var liveries = this.blueprints[name].liveries;
if (liveries !== undefined)
return Object.values(liveries);
else
return [];
}
/* Get the loadout content from the unit name and loadout name */
getLoadoutByName(name: string, loadoutName: string) {
var loadouts = this.blueprints[name].loadouts;

View File

@@ -1,17 +1,18 @@
import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map, Point } from 'leaflet';
import { getMap, getMissionHandler, getUnitsManager, getWeaponsManager } from '..';
import { enumToCoalition, enumToEmissioNCountermeasure, getMarkerCategoryByName, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg, bearing, deg2rad } from '../other/utils';
import { enumToCoalition, enumToEmissioNCountermeasure, getMarkerCategoryByName, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg, bearing, deg2rad, ftToM } from '../other/utils';
import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures, setSpeedType, setAltitudeType, setOnOff, setFollowRoads, bombPoint, carpetBomb, bombBuilding, fireAtArea } from '../server/server';
import { CustomMarker } from '../map/custommarker';
import { CustomMarker } from '../map/markers/custommarker';
import { SVGInjector } from '@tanem/svg-injector';
import { UnitDatabase } from './unitdatabase';
import { TargetMarker } from '../map/targetmarker';
import { BOMBING, CARPET_BOMBING, DLINK, DataIndexes, FIRE_AT_AREA, GAME_MASTER, HIDE_GROUP_MEMBERS, IDLE, IRST, MOVE_UNIT, OPTIC, RADAR, ROEs, RWR, SHOW_CONTACT_LINES, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states } from '../constants/constants';
import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN, ObjectIconOptions } from '../@types/unit';
import { UnitDatabase } from './databases/unitdatabase';
import { TargetMarker } from '../map/markers/targetmarker';
import { DLINK, DataIndexes, GAME_MASTER, HIDE_GROUP_MEMBERS, IDLE, IRST, MOVE_UNIT, OPTIC, RADAR, ROEs, RWR, SHOW_CONTACT_LINES, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states } from '../constants/constants';
import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN, ObjectIconOptions, UnitData } from '../@types/unit';
import { DataExtractor } from '../server/dataextractor';
import { groundUnitDatabase } from './groundunitdatabase';
import { navyUnitDatabase } from './navyunitdatabase';
import { groundUnitDatabase } from './databases/groundunitdatabase';
import { navyUnitDatabase } from './databases/navyunitdatabase';
import { Weapon } from '../weapon/weapon';
import { LoadoutBlueprint } from '../@types/unitdatabase';
var pathIcon = new Icon({
iconUrl: '/resources/theme/images/markers/marker-icon.png',
@@ -42,9 +43,9 @@ export class Unit extends CustomMarker {
#followRoads: boolean = false;
#fuel: number = 0;
#desiredSpeed: number = 0;
#desiredSpeedType: string = "GS";
#desiredSpeedType: string = "CAS";
#desiredAltitude: number = 0;
#desiredAltitudeType: string = "AGL";
#desiredAltitudeType: string = "ASL";
#leaderID: number = 0;
#formationOffset: Offset = {
x: 0,
@@ -76,7 +77,7 @@ export class Unit extends CustomMarker {
};
#ammo: Ammo[] = [];
#contacts: Contact[] = [];
#activePath: LatLng[] = [];
#activePath: LatLng[] = [];
#isLeader: boolean = false;
#selectable: boolean;
@@ -94,43 +95,43 @@ export class Unit extends CustomMarker {
#hotgroup: number | null = null;
#detectionMethods: number[] = [];
getAlive() {return this.#alive};
getHuman() {return this.#human};
getControlled() {return this.#controlled};
getCoalition() {return this.#coalition};
getCountry() {return this.#country};
getName() {return this.#name};
getUnitName() {return this.#unitName};
getGroupName() {return this.#groupName};
getState() {return this.#state};
getTask() {return this.#task};
getHasTask() {return this.#hasTask};
getPosition() {return this.#position};
getSpeed() {return this.#speed};
getHeading() {return this.#heading};
getIsTanker() {return this.#isTanker};
getIsAWACS() {return this.#isAWACS};
getOnOff() {return this.#onOff};
getFollowRoads() {return this.#followRoads};
getFuel() {return this.#fuel};
getDesiredSpeed() {return this.#desiredSpeed};
getDesiredSpeedType() {return this.#desiredSpeedType};
getDesiredAltitude() {return this.#desiredAltitude};
getDesiredAltitudeType() {return this.#desiredAltitudeType};
getLeaderID() {return this.#leaderID};
getFormationOffset() {return this.#formationOffset};
getTargetID() {return this.#targetID};
getTargetPosition() {return this.#targetPosition};
getROE() {return this.#ROE};
getReactionToThreat() {return this.#reactionToThreat};
getEmissionsCountermeasures() {return this.#emissionsCountermeasures};
getTACAN() {return this.#TACAN};
getRadio() {return this.#radio};
getGeneralSettings() {return this.#generalSettings};
getAmmo() {return this.#ammo};
getContacts() {return this.#contacts};
getActivePath() {return this.#activePath};
getIsLeader() {return this.#isLeader};
getAlive() { return this.#alive };
getHuman() { return this.#human };
getControlled() { return this.#controlled };
getCoalition() { return this.#coalition };
getCountry() { return this.#country };
getName() { return this.#name };
getUnitName() { return this.#unitName };
getGroupName() { return this.#groupName };
getState() { return this.#state };
getTask() { return this.#task };
getHasTask() { return this.#hasTask };
getPosition() { return this.#position };
getSpeed() { return this.#speed };
getHeading() { return this.#heading };
getIsTanker() { return this.#isTanker };
getIsAWACS() { return this.#isAWACS };
getOnOff() { return this.#onOff };
getFollowRoads() { return this.#followRoads };
getFuel() { return this.#fuel };
getDesiredSpeed() { return this.#desiredSpeed };
getDesiredSpeedType() { return this.#desiredSpeedType };
getDesiredAltitude() { return this.#desiredAltitude };
getDesiredAltitudeType() { return this.#desiredAltitudeType };
getLeaderID() { return this.#leaderID };
getFormationOffset() { return this.#formationOffset };
getTargetID() { return this.#targetID };
getTargetPosition() { return this.#targetPosition };
getROE() { return this.#ROE };
getReactionToThreat() { return this.#reactionToThreat };
getEmissionsCountermeasures() { return this.#emissionsCountermeasures };
getTACAN() { return this.#TACAN };
getRadio() { return this.#radio };
getGeneralSettings() { return this.#generalSettings };
getAmmo() { return this.#ammo };
getContacts() { return this.#contacts };
getActivePath() { return this.#activePath };
getIsLeader() { return this.#isLeader };
static getConstructor(type: string) {
if (type === "GroundUnit") return GroundUnit;
@@ -156,7 +157,7 @@ export class Unit extends CustomMarker {
this.on('contextmenu', (e) => this.#onContextMenu(e));
this.on('mouseover', () => { if (this.belongsToCommandedCoalition()) this.setHighlighted(true); })
this.on('mouseout', () => { this.setHighlighted(false); })
getMap().on("zoomend", () => {this.#onZoom();})
getMap().on("zoomend", () => { this.#onZoom(); })
/* Deselect units if they are hidden */
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
@@ -165,13 +166,13 @@ export class Unit extends CustomMarker {
document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => {
window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300);
});
});
document.addEventListener("mapVisibilityOptionsChanged", (ev: CustomEventInit) => {
this.#updateMarker();
if (this.getSelected())
this.drawLines();
});
});
}
getCategory() {
@@ -222,12 +223,12 @@ export class Unit extends CustomMarker {
case DataIndexes.radio: this.#radio = dataExtractor.extractRadio(); break;
case DataIndexes.generalSettings: this.#generalSettings = dataExtractor.extractGeneralSettings(); break;
case DataIndexes.ammo: this.#ammo = dataExtractor.extractAmmo(); break;
case DataIndexes.contacts: this.#contacts = dataExtractor.extractContacts(); document.dispatchEvent(new CustomEvent("contactsUpdated", {detail: this})); break;
case DataIndexes.contacts: this.#contacts = dataExtractor.extractContacts(); document.dispatchEvent(new CustomEvent("contactsUpdated", { detail: this })); break;
case DataIndexes.activePath: this.#activePath = dataExtractor.extractActivePath(); break;
case DataIndexes.isLeader: this.#isLeader = dataExtractor.extractBool(); break;
}
}
/* Dead units can't be selected */
this.setSelected(this.getSelected() && this.#alive && !this.getHidden())
@@ -244,7 +245,7 @@ export class Unit extends CustomMarker {
this.#drawTarget();
}
getData() {
getData(): UnitData {
return {
category: this.getCategory(),
ID: this.ID,
@@ -312,11 +313,6 @@ export class Unit extends CustomMarker {
}
}
getLiveryID(): string {
const liveryID = this.getDatabase()?.getByName(this.getName())?.liveryID;
return liveryID? liveryID: "";
}
setAlive(newAlive: boolean) {
if (newAlive != this.#alive)
document.dispatchEvent(new CustomEvent("unitDeath", { detail: this }));
@@ -327,7 +323,7 @@ export class Unit extends CustomMarker {
/* Only alive units can be selected. Some units are not selectable (weapons) */
if ((this.#alive || !selected) && this.getSelectable() && this.getSelected() != selected && this.belongsToCommandedCoalition()) {
this.#selected = selected;
if (selected) {
document.dispatchEvent(new CustomEvent("unitSelection", { detail: this }));
this.#updateMarker();
@@ -346,7 +342,7 @@ export class Unit extends CustomMarker {
else
this.#updateMarker();
}
}
}
@@ -390,13 +386,18 @@ export class Unit extends CustomMarker {
belongsToCommandedCoalition() {
if (getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER && getMissionHandler().getCommandedCoalition() !== this.#coalition)
return false;
return true;
return true;
}
getType() {
return "";
}
getSpawnPoints() {
return this.getDatabase()?.getSpawnPointsByName(this.getName());
}
/********************** Icon *************************/
createIcon(): void {
/* Set the icon */
@@ -438,7 +439,15 @@ export class Unit extends CustomMarker {
var unitIcon = document.createElement("div");
unitIcon.classList.add("unit-icon");
var img = document.createElement("img");
img.src = `/resources/theme/images/units/${this.getMarkerCategory()}.svg`;
var imgSrc;
/* If a unit does not belong to the commanded coalition or it is not visually detected, show it with the generic aircraft square */
if (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)))
imgSrc = this.getMarkerCategory();
else
imgSrc = "aircraft";
img.src = `/resources/theme/images/units/${imgSrc}.svg`;
img.onload = () => SVGInjector(img);
unitIcon.appendChild(img);
unitIcon.toggleAttribute("data-rotate-to-heading", iconOptions.rotateToHeading);
@@ -503,12 +512,12 @@ export class Unit extends CustomMarker {
updateVisibility() {
const hiddenUnits = getUnitsManager().getHiddenTypes();
var hidden = ((this.#human && hiddenUnits.includes("human")) ||
(this.#controlled == false && hiddenUnits.includes("dcs")) ||
(hiddenUnits.includes(this.getMarkerCategory())) ||
(hiddenUnits.includes(this.#coalition)) ||
(!this.belongsToCommandedCoalition() && (this.#detectionMethods.length == 0 || (this.#detectionMethods.length == 1 && this.#detectionMethods[0] === RWR))) ||
(getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] && !this.#isLeader && this.getCategory() == "GroundUnit" && getMap().getZoom() < 13 && (this.belongsToCommandedCoalition() || (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0)))) &&
!(this.getSelected());
(this.#controlled == false && hiddenUnits.includes("dcs")) ||
(hiddenUnits.includes(this.getMarkerCategory())) ||
(hiddenUnits.includes(this.#coalition)) ||
(!this.belongsToCommandedCoalition() && (this.#detectionMethods.length == 0 || (this.#detectionMethods.length == 1 && this.#detectionMethods[0] === RWR))) ||
(getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] && !this.#isLeader && this.getCategory() == "GroundUnit" && getMap().getZoom() < 13 && (this.belongsToCommandedCoalition() || (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0)))) &&
!(this.getSelected());
this.setHidden(hidden || !this.#alive);
}
@@ -518,9 +527,9 @@ export class Unit extends CustomMarker {
/* Add the marker if not present */
if (!getMap().hasLayer(this) && !this.getHidden()) {
if (getMap().isZooming())
this.once("zoomend", () => {this.addTo(getMap())})
else
if (getMap().isZooming())
this.once("zoomend", () => { this.addTo(getMap()) })
else
this.addTo(getMap());
}
@@ -563,10 +572,22 @@ export class Unit extends CustomMarker {
return loadouts.some((loadout: LoadoutBlueprint) => {
return (roles as string[]).some((role: string) => { return loadout.roles.includes(role) });
});
} else
} else
return false;
}
isInViewport() {
const mapBounds = getMap().getBounds();
const unitPos = this.getPosition();
return (unitPos.lng > mapBounds.getWest()
&& unitPos.lng < mapBounds.getEast()
&& unitPos.lat > mapBounds.getSouth()
&& unitPos.lat < mapBounds.getNorth());
}
/********************** Unit commands *************************/
addDestination(latlng: L.LatLng) {
if (!this.#human) {
@@ -699,8 +720,6 @@ export class Unit extends CustomMarker {
/***********************************************/
onAdd(map: Map): this {
super.onAdd(map);
/* If this is the first time adding this unit to the map, remove the temporary marker */
getMap().removeTemporaryMarker(new LatLng(this.#position.lat, this.#position.lng));
return this;
}
@@ -708,24 +727,28 @@ export class Unit extends CustomMarker {
#onClick(e: any) {
if (!this.#preventClick) {
if (getMap().getState() === IDLE || getMap().getState() === MOVE_UNIT || e.originalEvent.ctrlKey) {
if (!e.originalEvent.ctrlKey)
if (!e.originalEvent.ctrlKey)
getUnitsManager().deselectAllUnits();
this.setSelected( !this.getSelected() );
if ( this.getSelected() ) {
document.dispatchEvent( new CustomEvent( "unitSelection", { "detail": this }));
} else {
document.dispatchEvent( new CustomEvent( "unitDeselection", { "detail": this }));
}
this.setSelected(!this.getSelected());
const detail = { "detail": { "unit": this } };
if (this.getSelected())
document.dispatchEvent(new CustomEvent("unitSelected", detail));
else
document.dispatchEvent(new CustomEvent("unitDeselection", { "detail": this }));
}
}
this.#timer = window.setTimeout(() => {
this.#preventClick = false;
}, 200);
this.#timer = window.setTimeout(() => { this.#preventClick = false; }, 200);
}
#onDoubleClick(e: any) {
const unitsManager = getUnitsManager();
Object.values(unitsManager.getUnits()).forEach((unit: Unit) => {
if (unit.getAlive() === true && unit.getName() === this.getName() && unit.isInViewport())
unitsManager.selectUnit(unit.ID, false);
});
clearTimeout(this.#timer);
this.#preventClick = true;
}
@@ -748,23 +771,6 @@ export class Unit extends CustomMarker {
}
}
if ((selectedUnits.length === 0 && this.getCategory() == "Aircraft") || (selectedUnitTypes.length === 1 && ["Aircraft"].includes(selectedUnitTypes[0]))) {
if (selectedUnits.concat([this]).every((unit: Unit) => { return unit.canFulfillRole(["CAS", "Strike"]) })) {
options["bomb"] = { text: "Precision bombing", tooltip: "Precision bombing of a specific point" };
options["carpet-bomb"] = { text: "Carpet bombing", tooltip: "Carpet bombing close to a point" };
}
}
if ((selectedUnits.length === 0 && this.getCategory() == "GroundUnit") || selectedUnitTypes.length === 1 && ["GroundUnit"].includes(selectedUnitTypes[0])) {
if (selectedUnits.concat([this]).every((unit: Unit) => { return ["Gun Artillery", "Rocket Artillery", "Infantry", "IFV", "Tank"].includes(this.getType()) }))
options["fire-at-area"] = { text: "Fire at area", tooltip: "Fire at a large area" };
}
if ((selectedUnits.length === 0 && this.getCategory() == "NavyUnit") || selectedUnitTypes.length === 1 && ["NavyUnit"].includes(selectedUnitTypes[0])) {
if (selectedUnits.concat([this]).every((unit: Unit) => { return ["Cruiser", "Destroyer", "Frigate"].includes(this.getType()) }))
options["fire-at-area"] = { text: "Fire at area", tooltip: "Fire at a large area" };
}
if (selectedUnitTypes.length === 1 && ["NavyUnit", "GroundUnit"].includes(selectedUnitTypes[0]) && getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) !== undefined)
options["group"] = { text: "Create group", tooltip: "Create a group from the selected units." };
@@ -787,14 +793,7 @@ export class Unit extends CustomMarker {
else if (action === "group")
getUnitsManager().selectedUnitsCreateGroup();
else if (action === "follow")
this.#showFollowOptions(e);
else if (action === "bomb")
getMap().setState(BOMBING);
else if (action === "carpet-bomb")
getMap().setState(CARPET_BOMBING);
else if (action === "fire-at-area")
getMap().setState(FIRE_AT_AREA);
this.#showFollowOptions(e);
}
#showFollowOptions(e: any) {
@@ -815,16 +814,38 @@ export class Unit extends CustomMarker {
getMap().hideUnitContextMenu();
this.#applyFollowOptions(option);
});
getMap().showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng);
}
#applyFollowOptions(action: string) {
if (action === "custom") {
document.getElementById("custom-formation-dialog")?.classList.remove("hide");
getMap().getUnitContextMenu().setCustomFormationCallback((offset: { x: number, y: number, z: number }) => {
getUnitsManager().selectedUnitsFollowUnit(this.ID, offset);
})
document.addEventListener("applyCustomFormation", () => {
var dialog = document.getElementById("custom-formation-dialog");
if (dialog) {
dialog.classList.add("hide");
var clock = 1;
while (clock < 8) {
if ((<HTMLInputElement>dialog.querySelector(`#formation-${clock}`)).checked)
break
clock++;
}
var angleDeg = 360 - (clock - 1) * 45;
var angleRad = deg2rad(angleDeg);
var distance = ftToM(parseInt((<HTMLInputElement>dialog.querySelector(`#distance`)?.querySelector("input")).value));
var upDown = ftToM(parseInt((<HTMLInputElement>dialog.querySelector(`#up-down`)?.querySelector("input")).value));
// X: front-rear, positive front
// Y: top-bottom, positive top
// Z: left-right, positive right
var x = distance * Math.cos(angleRad);
var y = upDown;
var z = distance * Math.sin(angleRad);
getUnitsManager().selectedUnitsFollowUnit(this.ID, { "x": x, "y": y, "z": z });
}
});
}
else {
getUnitsManager().selectedUnitsFollowUnit(this.ID, undefined, action);
@@ -1074,16 +1095,17 @@ export class Unit extends CustomMarker {
export class AirUnit extends Unit {
getIconOptions() {
var belongsToCommandedCoalition = this.belongsToCommandedCoalition();
return {
showState: this.belongsToCommandedCoalition(),
showVvi: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
showHotgroup: this.belongsToCommandedCoalition(),
showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
showShortLabel: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value))),
showFuel: this.belongsToCommandedCoalition(),
showAmmo: this.belongsToCommandedCoalition(),
showSummary: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
showCallsign: this.belongsToCommandedCoalition(),
showState: belongsToCommandedCoalition,
showVvi: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
showHotgroup: belongsToCommandedCoalition,
showUnitIcon: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
showShortLabel: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value))),
showFuel: belongsToCommandedCoalition,
showAmmo: belongsToCommandedCoalition,
showSummary: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
showCallsign: belongsToCommandedCoalition,
rotateToHeading: false
};
}
@@ -1115,16 +1137,17 @@ export class GroundUnit extends Unit {
}
getIconOptions() {
var belongsToCommandedCoalition = this.belongsToCommandedCoalition();
return {
showState: this.belongsToCommandedCoalition(),
showState: belongsToCommandedCoalition,
showVvi: false,
showHotgroup: this.belongsToCommandedCoalition(),
showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
showHotgroup: belongsToCommandedCoalition,
showUnitIcon: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
showShortLabel: false,
showFuel: false,
showAmmo: false,
showSummary: false,
showCallsign: this.belongsToCommandedCoalition(),
showCallsign: belongsToCommandedCoalition,
rotateToHeading: false
};
}
@@ -1135,7 +1158,7 @@ export class GroundUnit extends Unit {
getType() {
var blueprint = groundUnitDatabase.getByName(this.getName());
return blueprint?.type? blueprint.type: "";
return blueprint?.type ? blueprint.type : "";
}
}
@@ -1145,16 +1168,17 @@ export class NavyUnit extends Unit {
}
getIconOptions() {
var belongsToCommandedCoalition = this.belongsToCommandedCoalition();
return {
showState: this.belongsToCommandedCoalition(),
showState: belongsToCommandedCoalition,
showVvi: false,
showHotgroup: true,
showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
showUnitIcon: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
showShortLabel: false,
showFuel: false,
showAmmo: false,
showSummary: false,
showCallsign: this.belongsToCommandedCoalition(),
showCallsign: belongsToCommandedCoalition,
rotateToHeading: false
};
}
@@ -1169,6 +1193,6 @@ export class NavyUnit extends Unit {
getType() {
var blueprint = navyUnitDatabase.getByName(this.getName());
return blueprint?.type? blueprint.type: "";
return blueprint?.type ? blueprint.type : "";
}
}

View File

@@ -1,23 +1,27 @@
import { LatLng, LatLngBounds } from "leaflet";
import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler, getUnitsManager, getWeaponsManager } from "..";
import { Unit } from "./unit";
import { cloneUnit, deleteUnit, refreshAll, spawnAircrafts, spawnGroundUnits, spawnHelicopters, spawnNavyUnits } from "../server/server";
import { bearingAndDistanceToLatLng, deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polyContains, polygonArea, randomPointInPoly, randomUnitBlueprint } from "../other/utils";
import { CoalitionArea } from "../map/coalitionarea";
import { groundUnitDatabase } from "./groundunitdatabase";
import { DataIndexes, GAME_MASTER, IADSDensities, IDLE, MOVE_UNIT, NONE } from "../constants/constants";
import { cloneUnits, deleteUnit, spawnAircrafts, spawnGroundUnits, spawnHelicopters, spawnNavyUnits } from "../server/server";
import { bearingAndDistanceToLatLng, deg2rad, getUnitDatabaseByCategory, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polyContains, polygonArea, randomPointInPoly, randomUnitBlueprint } from "../other/utils";
import { CoalitionArea } from "../map/coalitionarea/coalitionarea";
import { groundUnitDatabase } from "./databases/groundunitdatabase";
import { DataIndexes, GAME_MASTER, IADSDensities, IDLE, MOVE_UNIT } from "../constants/constants";
import { DataExtractor } from "../server/dataextractor";
import { Contact } from "../@types/unit";
import { Contact, UnitData } from "../@types/unit";
import { citiesDatabase } from "./citiesDatabase";
import { aircraftDatabase } from "./aircraftdatabase";
import { helicopterDatabase } from "./helicopterdatabase";
import { navyUnitDatabase } from "./navyunitdatabase";
import { aircraftDatabase } from "./databases/aircraftdatabase";
import { helicopterDatabase } from "./databases/helicopterdatabase";
import { navyUnitDatabase } from "./databases/navyunitdatabase";
import { TemporaryUnitMarker } from "../map/markers/temporaryunitmarker";
/** The UnitsManager handles the creation, update, and control of units. Data is strictly updated by the server ONLY. This means that any interaction from the user will always and only
* result in a command to the server, executed by means of a REST PUT request. Any subsequent change in data will be reflected only when the new data is sent back by the server. This strategy allows
* to avoid client/server and client/client inconsistencies.
*/
export class UnitsManager {
#units: { [ID: number]: Unit };
#copiedUnits: any[];
#copiedUnits: UnitData[];
#selectionEventDisabled: boolean = false;
#pasteDisabled: boolean = false;
#hiddenTypes: string[] = [];
#requestDetectionUpdate: boolean = false;
@@ -34,26 +38,25 @@ export class UnitsManager {
document.addEventListener('keyup', (event) => this.#onKeyUp(event));
document.addEventListener('exportToFile', () => this.exportToFile());
document.addEventListener('importFromFile', () => this.importFromFile());
document.addEventListener('contactsUpdated', (e: CustomEvent) => {this.#requestDetectionUpdate = true});
document.addEventListener('commandModeOptionsChanged', () => {Object.values(this.#units).forEach((unit: Unit) => unit.updateVisibility())});
document.addEventListener('selectedUnitsChangeSpeed', (e: any) => {this.selectedUnitsChangeSpeed(e.detail.type)});
document.addEventListener('selectedUnitsChangeAltitude', (e: any) => {this.selectedUnitsChangeAltitude(e.detail.type)});
}
getSelectableAircraft() {
const units = this.getUnits();
return Object.keys(units).reduce((acc: { [key: number]: Unit }, unitId: any) => {
if (units[unitId].getCategory() === "Aircraft" && units[unitId].getAlive() === true) {
acc[unitId] = units[unitId];
}
return acc;
}, {});
document.addEventListener('contactsUpdated', (e: CustomEvent) => { this.#requestDetectionUpdate = true });
document.addEventListener('commandModeOptionsChanged', () => { Object.values(this.#units).forEach((unit: Unit) => unit.updateVisibility()) });
document.addEventListener('selectedUnitsChangeSpeed', (e: any) => { this.selectedUnitsChangeSpeed(e.detail.type) });
document.addEventListener('selectedUnitsChangeAltitude', (e: any) => { this.selectedUnitsChangeAltitude(e.detail.type) });
}
/**
*
* @returns All the existing units, both alive and dead
*/
getUnits() {
return this.#units;
}
/** Get a specific unit by ID
*
* @param ID ID of the unit. The ID shall be the same as the unit ID in DCS.
* @returns Unit object, or null if no unit with said ID exists.
*/
getUnitByID(ID: number) {
if (ID in this.#units)
return this.#units[ID];
@@ -61,13 +64,23 @@ export class UnitsManager {
return null;
}
/** Returns all the units that belong to a hotgroup
*
* @param hotgroup Hotgroup number
* @returns Array of units that belong to hotgroup
*/
getUnitsByHotgroup(hotgroup: number) {
return Object.values(this.#units).filter((unit: Unit) => { return unit.getAlive() && unit.getHotgroup() == hotgroup });
}
/** Add a new unit to the manager
*
* @param ID ID of the new unit
* @param category Either "Aircraft", "Helicopter", "GroundUnit", or "NavyUnit". Determines what class will be used to create the new unit accordingly.
*/
addUnit(ID: number, category: string) {
if (category){
/* The name of the unit category is exactly the same as the constructor name */
if (category) {
/* Get the constructor from the unit category */
var constructor = Unit.getConstructor(category);
if (constructor != undefined) {
this.#units[ID] = new constructor(ID);
@@ -75,11 +88,25 @@ export class UnitsManager {
}
}
/** Update the data of all the units. The data is directly decoded from the binary buffer received from the REST Server. This is necessary for performance and bandwidth reasons.
*
* @param buffer The arraybuffer, encoded according to the ICD defined in: TODO Add reference to ICD
* @returns The decoded updateTime of the data update.
*/
update(buffer: ArrayBuffer) {
/* Extract the data from the arraybuffer. Since data is encoded dynamically (not all data is always present, but rather only the data that was actually updated since the last request).
No a prori casting can be performed. On the contrary, the array is decoded incrementally, depending on the DataIndexes of the data. The actual data decoding is performed by the Unit class directly.
Every time a piece of data is decoded the decoder seeker is incremented. */
var dataExtractor = new DataExtractor(buffer);
var updateTime = Number(dataExtractor.extractUInt64());
/* Run until all data is extracted or an error occurs */
while (dataExtractor.getSeekPosition() < buffer.byteLength) {
/* Extract the unit ID */
const ID = dataExtractor.extractUInt32();
/* If the ID of the unit does not yet exist, create the unit, if the category is known. If it isn't, some data must have been lost and we need to wait for another update */
if (!(ID in this.#units)) {
const datumIndex = dataExtractor.extractUInt8();
if (datumIndex == DataIndexes.category) {
@@ -91,21 +118,25 @@ export class UnitsManager {
return updateTime;
}
}
this.#units[ID]?.setData(dataExtractor);
/* Update the data of the unit */
this.#units[ID]?.setData(dataExtractor);
}
/* If we are not in Game Master mode, visibility of units by the user is determined by the detections of the units themselves. This is performed here.
This operation is computationally expensive, therefore it is only performed when #requestDetectionUpdate is true. This happens whenever a change in the detectionUpdates is detected
*/
if (this.#requestDetectionUpdate && getMissionHandler().getCommandModeOptions().commandMode != GAME_MASTER) {
/* Create a dictionary of empty detection methods arrays */
var detectionMethods: {[key: string]: number[]} = {};
for (let ID in this.#units)
var detectionMethods: { [key: string]: number[] } = {};
for (let ID in this.#units)
detectionMethods[ID] = [];
for (let ID in getWeaponsManager().getWeapons())
for (let ID in getWeaponsManager().getWeapons())
detectionMethods[ID] = [];
/* Fill the array with the detection methods */
for (let ID in this.#units) {
const unit = this.#units[ID];
if (unit.getAlive() && unit.belongsToCommandedCoalition()){
if (unit.getAlive() && unit.belongsToCommandedCoalition()) {
const contacts = unit.getContacts();
contacts.forEach((contact: Contact) => {
const contactID = contact.ID;
@@ -120,6 +151,8 @@ export class UnitsManager {
const unit = this.#units[ID];
unit?.setDetectionMethods(detectionMethods[ID]);
}
/* Set the detection methods for every weapon (weapons must be detected too) */
for (let ID in getWeaponsManager().getWeapons()) {
const weapon = getWeaponsManager().getWeaponByID(parseInt(ID));
weapon?.setDetectionMethods(detectionMethods[ID]);
@@ -128,6 +161,7 @@ export class UnitsManager {
this.#requestDetectionUpdate = false;
}
/* Update the detection lines of all the units. This code is handled by the UnitsManager since it must be run both when the detected OR the detecting unit is updated */
for (let ID in this.#units) {
if (this.#units[ID].getSelected())
this.#units[ID].drawLines();
@@ -196,11 +230,11 @@ export class UnitsManager {
}
}
deselectUnit( ID:number ) {
if ( this.#units.hasOwnProperty( ID ) ) {
deselectUnit(ID: number) {
if (this.#units.hasOwnProperty(ID)) {
this.#units[ID].setSelected(false);
} else {
console.error( `deselectUnit(): no unit found with ID "${ID}".` );
console.error(`deselectUnit(): no unit found with ID "${ID}".`);
}
}
@@ -209,38 +243,33 @@ export class UnitsManager {
this.getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setSelected(true))
}
getSelectedUnitsTypes() {
const selectedUnits = this.getSelectedUnits();
if (selectedUnits.length == 0)
getUnitsTypes(units: Unit[]) {
if (units.length == 0)
return [];
return selectedUnits.map((unit: Unit) => {
return units.map((unit: Unit) => {
return unit.getCategory();
})?.filter((value: any, index: any, array: string[]) => {
return array.indexOf(value) === index;
});
};
}
/* Gets the value of a variable from the selected units. If all the units have the same value, returns the value, else returns undefined */
getSelectedUnitsVariable(variableGetter: CallableFunction) {
const selectedUnits = this.getSelectedUnits();
if (selectedUnits.length == 0)
/* Gets the value of a variable from the units. If all the units have the same value, returns the value, else returns undefined */
getUnitsVariable(variableGetter: CallableFunction, units: Unit[]) {
if (units.length == 0)
return undefined;
return selectedUnits.map((unit: Unit) => {
return units.map((unit: Unit) => {
return variableGetter(unit);
})?.reduce((a: any, b: any) => {
return a === b ? a : undefined
});
};
getSelectedUnitsCoalition() {
const selectedUnits = this.getSelectedUnits();
if (selectedUnits.length == 0)
return undefined;
return selectedUnits.map((unit: Unit) => {
return unit.getCoalition()
})?.reduce((a: any, b: any) => {
return a == b ? a : undefined
});
getSelectedUnitsTypes() {
return this.getUnitsTypes(this.getSelectedUnits());
};
getSelectedUnitsVariable(variableGetter: CallableFunction) {
return this.getUnitsVariable(variableGetter, this.getSelectedUnits());
};
getByType(type: string) {
@@ -252,10 +281,9 @@ export class UnitsManager {
getUnitDetectedMethods(unit: Unit) {
var detectionMethods: number[] = [];
for (let idx in this.#units) {
if (this.#units[idx].getAlive() && this.#units[idx].getIsLeader() && this.#units[idx].getCoalition() !== "neutral" && this.#units[idx].getCoalition() != unit.getCoalition())
{
if (this.#units[idx].getAlive() && this.#units[idx].getIsLeader() && this.#units[idx].getCoalition() !== "neutral" && this.#units[idx].getCoalition() != unit.getCoalition()) {
this.#units[idx].getContacts().forEach((contact: Contact) => {
if (contact.ID == unit.ID && !detectionMethods.includes(contact.detectionMethod))
if (contact.ID == unit.ID && !detectionMethods.includes(contact.detectionMethod))
detectionMethods.push(contact.detectionMethod);
});
}
@@ -414,18 +442,18 @@ export class UnitsManager {
selectedUnitsDelete(explosion: boolean = false) {
var selectedUnits = this.getSelectedUnits(); /* Can be applied to humans too */
const selectionContainsAHuman = selectedUnits.some( ( unit:Unit ) => {
const selectionContainsAHuman = selectedUnits.some((unit: Unit) => {
return unit.getHuman() === true;
});
if (selectionContainsAHuman && !confirm( "Your selection includes a human player. Deleting humans causes their vehicle to crash.\n\nAre you sure you want to do this?" ) ) {
if (selectionContainsAHuman && !confirm("Your selection includes a human player. Deleting humans causes their vehicle to crash.\n\nAre you sure you want to do this?")) {
return;
}
var immediate = false;
if (selectedUnits.length > 20)
immediate = confirm(`You are trying to delete ${selectedUnits.length} units, do you want to delete them immediately? This may cause lag for players.`)
for (let idx in selectedUnits) {
selectedUnits[idx].delete(explosion, immediate);
}
@@ -560,29 +588,45 @@ export class UnitsManager {
this.#showActionMessage(selectedUnits, `unit bombing point`);
}
// TODO add undo group
selectedUnitsCreateGroup() {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: false });
var units = [];
var coalition = "neutral";
var units: { ID: number, location: LatLng }[] = [];
for (let idx in selectedUnits) {
var unit = selectedUnits[idx];
coalition = unit.getCoalition();
deleteUnit(unit.ID, false, true);
units.push({unitType: unit.getName(), location: unit.getPosition(), liveryID: unit.getLiveryID()});
units.push({ ID: unit.ID, location: unit.getPosition() });
}
const category = this.getSelectedUnitsTypes()[0];
this.spawnUnits(category, units, coalition, true);
cloneUnits(units, true, 0 /* No spawn points, we delete the original units */);
}
/***********************************************/
copyUnits() {
this.#copiedUnits = JSON.parse(JSON.stringify(this.getSelectedUnits().map((unit: Unit) => {return unit.getData()}))); /* Can be applied to humans too */
this.#copiedUnits = JSON.parse(JSON.stringify(this.getSelectedUnits().map((unit: Unit) => { return unit.getData() }))); /* Can be applied to humans too */
getInfoPopup().setText(`${this.#copiedUnits.length} units copied`);
}
pasteUnits() {
if (!this.#pasteDisabled && getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER) {
let spawnPoints = 0;
/* If spawns are restricted, check that the user has the necessary spawn points */
if (getMissionHandler().getCommandModeOptions().commandMode != GAME_MASTER) {
if (getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0) {
getInfoPopup().setText(`Units can be pasted only during SETUP phase`);
return false;
}
this.#copiedUnits.forEach((unit: UnitData) => {
let unitSpawnPoints = getUnitDatabaseByCategory(unit.category)?.getSpawnPointsByName(unit.name);
if (unitSpawnPoints !== undefined)
spawnPoints += unitSpawnPoints;
})
if (spawnPoints > getMissionHandler().getAvailableSpawnPoints()) {
getInfoPopup().setText("Not enough spawn points available!");
return false;
}
}
if (this.#copiedUnits.length > 0) {
/* Compute the position of the center of the copied units */
var nUnits = this.#copiedUnits.length;
var avgLat = 0;
@@ -594,45 +638,44 @@ export class UnitsManager {
}
/* Organize the copied units in groups */
var groups: {[key: string]: any} = {};
this.#copiedUnits.forEach((unit: any) => {
var groups: { [key: string]: UnitData[] } = {};
this.#copiedUnits.forEach((unit: UnitData) => {
if (!(unit.groupName in groups))
groups[unit.groupName] = [];
groups[unit.groupName].push(unit);
});
/* Clone the units in groups */
for (let groupName in groups) {
/* Paste the units as groups. Only for ground and navy units because of loadouts, TODO: find a better solution so it works for them too*/
if (!["Aircraft", "Helicopter"].includes(groups[groupName][0].category)) {
var units = groups[groupName].map((unit: any) => {
var position = new LatLng(getMap().getMouseCoordinates().lat + unit.position.lat - avgLat, getMap().getMouseCoordinates().lng + unit.position.lng - avgLng);
getMap().addTemporaryMarker(position, unit.name, unit.coalition);
const liveryID = unit.getDatabase()?.getByName(unit.getName())?.liveryID;
return {unitType: unit.name, location: position, liveryID: liveryID? liveryID: ""};
});
this.spawnUnits(groups[groupName][0].category, units, groups[groupName][0].coalition, true);
}
else {
groups[groupName].forEach((unit: any) => {
var position = new LatLng(getMap().getMouseCoordinates().lat + unit.position.lat - avgLat, getMap().getMouseCoordinates().lng + unit.position.lng - avgLng);
getMap().addTemporaryMarker(position, unit.name, unit.coalition);
cloneUnit(unit.ID, position);
});
}
var units: { ID: number, location: LatLng }[] = [];
let markers: TemporaryUnitMarker[] = [];
groups[groupName].forEach((unit: UnitData) => {
var position = new LatLng(getMap().getMouseCoordinates().lat + unit.position.lat - avgLat, getMap().getMouseCoordinates().lng + unit.position.lng - avgLng);
markers.push(getMap().addTemporaryMarker(position, unit.name, unit.coalition));
units.push({ ID: unit.ID, location: position });
});
cloneUnits(units, false, spawnPoints, (res: any) => {
if (res.commandHash !== undefined) {
markers.forEach((marker: TemporaryUnitMarker) => {
marker.setCommandHash(res.commandHash);
})
}
});
}
getInfoPopup().setText(`${this.#copiedUnits.length - 1} units pasted`);
getInfoPopup().setText(`${this.#copiedUnits.length} units pasted`);
}
else {
getInfoPopup().setText(`Unit cloning is disabled in ${getMissionHandler().getCommandModeOptions().commandMode} mode`);
getInfoPopup().setText("No units copied!");
}
}
createIADS(coalitionArea: CoalitionArea, types: {[key: string]: boolean}, eras: {[key: string]: boolean}, ranges: {[key: string]: boolean}, density: number, distribution: number) {
const activeTypes = Object.keys(types).filter((key: string) => { return types[key]; });
const activeEras = Object.keys(eras).filter((key: string) => { return eras[key]; });
const activeRanges = Object.keys(ranges).filter((key: string) => { return ranges[key]; });
createIADS(coalitionArea: CoalitionArea, types: { [key: string]: boolean }, eras: { [key: string]: boolean }, ranges: { [key: string]: boolean }, density: number, distribution: number) {
const activeTypes = Object.keys(types).filter((key: string) => { return types[key]; });
const activeEras = Object.keys(eras).filter((key: string) => { return eras[key]; });
const activeRanges = Object.keys(ranges).filter((key: string) => { return ranges[key]; });
citiesDatabase.forEach((city: {lat: number, lng: number, pop: number}) => {
citiesDatabase.forEach((city: { lat: number, lng: number, pop: number }) => {
if (polyContains(new LatLng(city.lat, city.lng), coalitionArea)) {
var pointsNumber = 2 + Math.pow(city.pop, 0.2) * density / 100;
for (let i = 0; i < pointsNumber; i++) {
@@ -642,11 +685,9 @@ export class UnitsManager {
if (polyContains(latlng, coalitionArea)) {
const type = activeTypes[Math.floor(Math.random() * activeTypes.length)];
if (Math.random() < IADSDensities[type]) {
const unitBlueprint = randomUnitBlueprint(groundUnitDatabase, {type: type, eras: activeEras, ranges: activeRanges});
const unitBlueprint = randomUnitBlueprint(groundUnitDatabase, { type: type, eras: activeEras, ranges: activeRanges });
if (unitBlueprint) {
const liveryID = unitBlueprint.liveryID;
this.spawnUnits("GroundUnit", [{unitType: unitBlueprint.name, location: latlng, liveryID: liveryID? liveryID: ""}], coalitionArea.getCoalition(), true);
getMap().addTemporaryMarker(latlng, unitBlueprint.name, coalitionArea.getCoalition());
this.spawnUnits("GroundUnit", [{ unitType: unitBlueprint.name, location: latlng, liveryID: "" }], coalitionArea.getCoalition(), true);
}
}
}
@@ -656,19 +697,19 @@ export class UnitsManager {
}
exportToFile() {
var unitsToExport: {[key: string]: any} = {};
var unitsToExport: { [key: string]: any } = {};
for (let ID in this.#units) {
var unit = this.#units[ID];
if (!["Aircraft", "Helicopter"].includes(unit.getCategory())) {
var data: any = unit.getData();
if (unit.getGroupName() in unitsToExport)
unitsToExport[unit.getGroupName()].push(data);
else
else
unitsToExport[unit.getGroupName()] = [data];
}
}
var a = document.createElement("a");
var file = new Blob([JSON.stringify(unitsToExport)], {type: 'text/plain'});
var file = new Blob([JSON.stringify(unitsToExport)], { type: 'text/plain' });
a.href = URL.createObjectURL(file);
a.download = 'export.json';
a.click();
@@ -683,15 +724,14 @@ export class UnitsManager {
return;
}
var reader = new FileReader();
reader.onload = function(e: any) {
reader.onload = function (e: any) {
var contents = e.target.result;
var groups = JSON.parse(contents);
for (let groupName in groups) {
if (groupName !== "" && groups[groupName].length > 0 && (groups[groupName].every((unit: any) => {return unit.category == "GroundUnit";}) || groups[groupName].every((unit: any) => {return unit.category == "NavyUnit";}))) {
var aliveUnits = groups[groupName].filter((unit: any) => {return unit.alive});
if (groupName !== "" && groups[groupName].length > 0 && (groups[groupName].every((unit: any) => { return unit.category == "GroundUnit"; }) || groups[groupName].every((unit: any) => { return unit.category == "NavyUnit"; }))) {
var aliveUnits = groups[groupName].filter((unit: any) => { return unit.alive });
var units = aliveUnits.map((unit: any) => {
const liveryID = unit.getDatabase()?.getByName(unit.getName())?.liveryID;
return { unitType: unit.name, location: unit.position, liveryID: liveryID? liveryID: "" }
return { unitType: unit.name, location: unit.position, liveryID: "" }
});
getUnitsManager().spawnUnits(groups[groupName][0].category, units, groups[groupName][0].coalition, true);
}
@@ -702,40 +742,46 @@ export class UnitsManager {
input.click();
}
spawnUnits(category: string, units: any, coalition: string = "blue", immediate: boolean = true, airbase: string = "") {
spawnUnits(category: string, units: any, coalition: string = "blue", immediate: boolean = true, airbase: string = "", country: string = "", callback: CallableFunction = () => {}) {
var spawnPoints = 0;
var spawnFunction = () => {};
var spawnsRestricted = getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER;
if (category === "Aircraft") {
if (airbase == "" && getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) {
if (airbase == "" && spawnsRestricted) {
getInfoPopup().setText("Aircrafts can be air spawned during the SETUP phase only");
return false;
}
spawnPoints = units.reduce((points: number, unit: any) => {return points + aircraftDatabase.getSpawnPointsByName(unit.unitType)}, 0);
spawnAircrafts(units, coalition, airbase, immediate, spawnPoints);
spawnFunction = () => spawnAircrafts(units, coalition, airbase, country, immediate, spawnPoints, callback);
} else if (category === "Helicopter") {
if (airbase == "" && getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) {
if (airbase == "" && spawnsRestricted) {
getInfoPopup().setText("Helicopters can be air spawned during the SETUP phase only");
return false;
}
spawnPoints = units.reduce((points: number, unit: any) => {return points + helicopterDatabase.getSpawnPointsByName(unit.unitType)}, 0);
spawnHelicopters(units, coalition, airbase, immediate, spawnPoints);
spawnFunction = () => spawnHelicopters(units, coalition, airbase, country, immediate, spawnPoints, callback);
} else if (category === "GroundUnit") {
if (getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) {
if (spawnsRestricted) {
getInfoPopup().setText("Ground units can be spawned during the SETUP phase only");
return false;
}
spawnPoints = units.reduce((points: number, unit: any) => {return points + groundUnitDatabase.getSpawnPointsByName(unit.unitType)}, 0);
spawnGroundUnits(units, coalition, immediate, spawnPoints);
spawnFunction = () => spawnGroundUnits(units, coalition, country, immediate, spawnPoints, callback);
} else if (category === "NavyUnit") {
if (getMissionHandler().getCommandModeOptions().restrictSpawns && getMissionHandler().getRemainingSetupTime() < 0 && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) {
if (spawnsRestricted) {
getInfoPopup().setText("Navy units can be spawned during the SETUP phase only");
return false;
}
spawnPoints = units.reduce((points: number, unit: any) => {return points + navyUnitDatabase.getSpawnPointsByName(unit.unitType)}, 0);
spawnNavyUnits(units, coalition, immediate, spawnPoints);
spawnFunction = () => spawnNavyUnits(units, coalition, country, immediate, spawnPoints, callback);
}
if (spawnPoints <= getMissionHandler().getAvailableSpawnPoints()) {
getMissionHandler().setSpentSpawnPoints(spawnPoints);
spawnFunction();
return true;
} else {
getInfoPopup().setText("Not enough spawn points available!");
@@ -749,7 +795,7 @@ export class UnitsManager {
if (event.key === "Delete")
this.selectedUnitsDelete();
else if (event.key === "a" && event.ctrlKey)
Object.values(this.getUnits()).filter((unit: Unit) => {return !unit.getHidden()}).forEach((unit: Unit) => unit.setSelected(true));
Object.values(this.getUnits()).filter((unit: Unit) => { return !unit.getHidden() }).forEach((unit: Unit) => unit.setSelected(true));
}
}

View File

@@ -1,7 +1,7 @@
import { LatLng, DivIcon, Map } from 'leaflet';
import { getMap, getMissionHandler, getUnitsManager } from '..';
import { enumToCoalition, mToFt, msToKnots, rad2deg } from '../other/utils';
import { CustomMarker } from '../map/custommarker';
import { CustomMarker } from '../map/markers/custommarker';
import { SVGInjector } from '@tanem/svg-injector';
import { DLINK, DataIndexes, GAME_MASTER, IRST, OPTIC, RADAR, VISUAL } from '../constants/constants';
import { ObjectIconOptions } from '../@types/unit';
@@ -214,8 +214,6 @@ export class Weapon extends CustomMarker {
/***********************************************/
onAdd(map: Map): this {
super.onAdd(map);
/* If this is the first time adding this unit to the map, remove the temporary marker */
getMap().removeTemporaryMarker(new LatLng(this.#position.lat, this.#position.lng));
return this;
}

View File

@@ -0,0 +1,22 @@
<div id="airbase-contextmenu" class="ol-panel" oncontextmenu="return false;">
<h3 id="airbase-name"></h3>
<dl id="airbase-chart-data" class="ol-data-grid">
<dt>ICAO</dt>
<dd data-point="ICAO"></dd>
<dt>Coalition</dt>
<dd data-point="coalition"></dd>
<dt>Elevation</dt>
<dd><span data-point="elevation"></span>ft</dd>
<dt>TACAN</dt>
<dd data-point="TACAN"></dd>
</dl>
<h4>Runways</h4>
<div id="airbase-runways">
</div>
<hr/>
<button id="spawn-airbase-aircraft-button" data-coalition="blue" title="Spawn aircraft" data-on-click="contextMenuSpawnAirbase" class="deploy-unit-button">Spawn</button>
<button id="land-here-button" title="Land here" data-on-click="contextMenuLandAirbase" class="hide">Land here</button>
</div>

Some files were not shown because too many files have changed in this diff Show More