153 Commits

Author SHA1 Message Date
Pax1601
8d03c6a767 Merge branch 'main' of https://github.com/Pax1601/DCSOlympus 2023-12-05 15:59:25 +01:00
Pax1601
e29fdfd8c7 Fixed typo in map attribution 2023-12-05 15:59:19 +01:00
Pax1601
af619c3687 Merge pull request #666 from Pax1601/rc1-fixes
Rc1 fixes
2023-12-05 15:44:29 +01:00
Pax1601
6afb6682ea Fixed IADS creation, no longer clogs the server 2023-12-05 15:43:48 +01:00
Pax1601
9c2c7f45c6 Demo mode disabled 2023-12-05 11:35:10 +01:00
Pax1601
7fbc19e90a Merge pull request #665 from Pax1601/rc1-fixes
RC1 fixes
2023-12-05 11:31:14 +01:00
Pax1601
8f2f73dc0e RC1 fixes 2023-12-05 11:26:18 +01:00
Pax1601
24a1681337 Merge pull request #663 from Pax1601/task-optimization
Task optimization
2023-12-05 10:07:09 +01:00
Pax1601
304e8ba3a0 Fixed compilation warnings 2023-12-05 10:06:47 +01:00
Pax1601
45b840fa72 Changed default multiplier value 2023-12-05 09:24:13 +01:00
Pax1601
82704f042b Added check on negative speed 2023-12-05 09:22:22 +01:00
Pax1601
87d71da55e Small tweak to multiplier 2023-12-05 09:14:06 +01:00
Pax1601
71cf7c67f7 Fixed change altitude and speed for helicopters 2023-12-05 09:11:36 +01:00
Pax1601
62142ed976 Version 2 of tasking optimization 2023-12-04 21:06:13 +01:00
Pax1601
f56bd514dc Fixed tab order in installer 2023-12-03 21:30:16 +01:00
Pax1601
e30f161d1d Removed unnecessary file 2023-12-03 21:11:14 +01:00
Pax1601
720bfb3118 Added git clean command 2023-12-03 21:06:05 +01:00
Pax1601
4100a3cc67 v0.4.9-alpha-rc1 2023-12-03 21:04:19 +01:00
Pax1601
942993ff6d Added ranges for SAM Sites 2023-12-03 21:00:32 +01:00
Pax1601
fbf435c799 Added missing images, changed mop to da aaa 2023-12-03 20:46:26 +01:00
Pax1601
8fdb0d82e8 Merge branch 'main' of https://github.com/Pax1601/DCSOlympus 2023-12-03 19:54:36 +01:00
Pax1601
dcbebab61b Updated databases 2023-12-03 19:54:33 +01:00
Pax1601
7b10afae43 Merge pull request #644 from Pax1601/639-create-boilerplate-plugin
Added boilerplate plugin
2023-12-03 19:51:32 +01:00
PeekabooSteam
81d8a88abd Removed old definition file 2023-12-03 18:48:51 +00:00
Pax1601
cb14158a0f Merge pull request #645 from WoodyXP/main
Example script to set airbase to a certain coalition
2023-12-03 19:32:13 +01:00
PeekabooSteam
38fc60352a Fixed typo 2023-12-03 18:12:18 +00:00
PeekabooSteam
3748f0d6e3 Added extensive documentation. 2023-12-03 14:23:08 +00:00
Stefan Arsic
edd626d5e5 Example script to set airbase to a certain coalition 2023-12-03 14:38:58 +01:00
PeekabooSteam
f785e75686 Corrected package name 2023-12-03 12:28:19 +00:00
PeekabooSteam
9a46ed70b7 Added boilerplate plugin 2023-12-03 12:24:52 +00:00
Pax1601
532c09a815 Fixed merge error 2023-12-03 11:49:39 +01:00
Pax1601
e430d38c63 Merge pull request #643 from Pax1601/525-ships-spawn-with-0-health-indicated
Health correctly computed for ships too
2023-12-03 11:35:55 +01:00
Pax1601
289b36de6d Health correctly computed for ships too 2023-12-03 11:35:37 +01:00
Pax1601
7e01f40e5c Merge pull request #642 from Pax1601/537-cannot-copy-paste-robots
It's now possible to copy robots
2023-12-03 11:10:17 +01:00
Pax1601
cac4bb5f13 It's now possible to copy robots
Know limitation: it's not possible to copy payloads of units spawned by other scripts. They must be in the .miz file. Don't think we can do much about this.
2023-12-03 11:09:50 +01:00
Pax1601
c8bb041887 Merge pull request #635 from Pax1601/633-unable-to-hide-olympus-controlled-units
633 unable to hide olympus controlled units
2023-12-02 18:32:40 +01:00
PeekabooSteam
9302c5c6d1 Merge branch '633-unable-to-hide-olympus-controlled-units' of https://github.com/Pax1601/DCSOlympus into 633-unable-to-hide-olympus-controlled-units 2023-12-02 16:05:51 +00:00
PeekabooSteam
84a3313f2c TS declarations 2023-12-02 16:04:12 +00:00
PeekabooSteam
1eb3beeb2e Merge branch 'main' into 633-unable-to-hide-olympus-controlled-units 2023-12-02 15:58:37 +00:00
PeekabooSteam
d0e6ef8c6c Changed order 2023-12-02 12:57:58 +00:00
PeekabooSteam
5da1df7e20 Added Olympus unit toggle 2023-12-02 12:47:00 +00:00
Pax1601
16fa6d9805 Updated databases 2023-12-02 13:25:46 +01:00
Pax1601
0ca7766689 Merge pull request #634 from Pax1601/minor-refactoring
Minor refactoring
2023-12-02 12:37:55 +01:00
Pax1601
4342575418 Merge pull request #632 from Pax1601/631-alignment-of-second-bar-in-visibility-options-is-too-tall
Fixed bar height
2023-12-02 12:37:41 +01:00
Pax1601
4cdb73a60d Fixed error in address for configuration 2023-12-02 12:36:42 +01:00
PeekabooSteam
a23c53bcb8 Fixed bar height 2023-12-02 11:04:37 +00:00
Pax1601
19ac4f92e0 Merge branch 'main' into minor-refactoring 2023-12-02 11:20:38 +01:00
Pax1601
0b8f48b4fd Merge pull request #615 from Pax1601/602-attack-mode-not-working-on-ground-and-navy-units
Added ATTACK state for ground and navy units
2023-12-02 11:19:05 +01:00
Pax1601
d2fa94a6cb Minor graphical tweak 2023-12-02 11:17:30 +01:00
Pax1601
da1c674911 Merge branch 'main' into 602-attack-mode-not-working-on-ground-and-navy-units 2023-12-02 10:53:24 +01:00
Pax1601
7184bc1eb2 Merge branch 'main' into 602-attack-mode-not-working-on-ground-and-navy-units 2023-12-02 10:49:28 +01:00
Pax1601
37f30a0f1a Merge pull request #629 from Pax1601/Shredmetal-patch-1
Update LEGAL
2023-12-02 10:48:43 +01:00
Pax1601
04f4b3e78a Merge pull request #626 from Pax1601/609-update-top-menu-ui
Styled top bar, fixed IADS toggle.
2023-12-02 10:48:12 +01:00
Pax1601
dbdc162fae Merge pull request #630 from Pax1601/read-config-location-with-lfs.writedir()
Read config location with lfs.writedir()
2023-12-02 10:47:33 +01:00
Pax1601
563f673fb3 Minor tweak 2023-12-02 10:46:59 +01:00
Pax1601
423e799a82 Completed reading from current instance 2023-12-02 10:40:03 +01:00
Shredmetal
4734b89414 Update LEGAL
Amended unilateral modification term to state explicitly that parties agree to be bound by such terms as modified from time to time
2023-12-02 14:33:27 +08:00
Shredmetal
6f708e7733 update LEGAL
added unilateral modification term so we can change the agreement at will without informing users beyond updating the repo
2023-12-02 14:21:38 +08:00
PeekabooSteam
f0c3f95189 Merge and fix conflict 2023-12-01 16:55:14 +00:00
Pax1601
93ca0e3f22 Added read from lfs 2023-12-01 17:44:03 +01:00
PeekabooSteam
d94432636f Styled top bar, fixed IADS toggle. 2023-12-01 15:51:02 +00:00
Pax1601
60fca35d80 Merge branch 'main' of https://github.com/Pax1601/DCSOlympus 2023-12-01 14:12:50 +01:00
Pax1601
db71462d1c Update sample.png 2023-12-01 14:12:10 +01:00
Pax1601
86f522176a Update README.md 2023-12-01 14:08:41 +01:00
Pax1601
94ee71c48f A couple of comments 2023-12-01 13:02:18 +01:00
Pax1601
fda0b21fb0 Minor refactoring 2023-12-01 13:00:02 +01:00
Pax1601
775148cec8 Merge pull request #621 from Pax1601/610-update-spawn-menu-ui
Unit spawn menu restyled
2023-11-30 17:29:30 +01:00
Pax1601
f2dc4a1a00 Unit spawn menu restyled 2023-11-30 17:29:11 +01:00
Pax1601
eda5723a3c Merge pull request #620 from Pax1601/617-check-iads-respects-requested-units-and-add-unit-preview-on-spawn
617 check iads respects requested units and add unit preview on spawn
2023-11-30 15:55:36 +01:00
Pax1601
b044d9a6c0 Minor comments 2023-11-30 15:46:48 +01:00
Pax1601
125d5396b9 Fixed IADS creation
It now also considers airbases when creating the IADS. Fixed styling and unit types. Currently SAM Sites ranges are incorrect but will be fixed when database is updated
2023-11-30 15:36:31 +01:00
Pax1601
17b8b35a43 Added ATTACK state for ground and navy units 2023-11-30 13:08:43 +01:00
Pax1601
4bd017e3c7 Merge pull request #612 from Pax1601/542-spawn-menu-dropdowns-overlap-menu
Alignment fixed, spelling corrected
2023-11-30 11:12:52 +01:00
Pax1601
37447a7fd3 Merge pull request #613 from Pax1601/592-detect-if-installing-olympus-again-and-dont-prompt-to-set-olympusjson-properties
592 detect if installing olympus again and dont prompt to set olympusjson properties
2023-11-30 11:09:34 +01:00
Pax1601
1ed1dec65e Added full stop for Peekaboo 2023-11-30 11:08:48 +01:00
Pax1601
5e2e465813 Installer updated to allow preservation of old config 2023-11-30 11:01:45 +01:00
PeekabooSteam
335655406e Alignment fixed, spelling corrected 2023-11-30 09:41:43 +00:00
Pax1601
bf12d6330c Merge pull request #606 from Pax1601/566-ground-and-naval-units-have-roe-only-designated-option
Added a warning message for ground and navy units ROE
2023-11-29 12:57:41 +01:00
Pax1601
3cca3187ad Added a warning message for ground and navy units ROE 2023-11-29 12:57:17 +01:00
Pax1601
f4388a2cff Merge pull request #605 from Pax1601/594-bounds-for-normandy-map-are-missing
Bounds added for Normandy theatre
2023-11-29 11:54:33 +01:00
Pax1601
7cf5d324eb Bounds added for Normandy theatre 2023-11-29 11:54:16 +01:00
Pax1601
ec9ef2b0fb Merge branch 'main' of https://github.com/Pax1601/DCSOlympus 2023-11-29 11:06:34 +01:00
Pax1601
6c852d93a9 Minor tweak on splash screen when screen is very small 2023-11-29 11:06:31 +01:00
Pax1601
290067932e Merge pull request #604 from Pax1601/596-add-a-way-for-users-to-know-there-is-a-new-version-available
Added latest version number
2023-11-29 10:49:04 +01:00
Pax1601
4aa3a43604 Added latest version number
It pulses if a newer version is available
2023-11-29 10:46:43 +01:00
Pax1601
3c33696452 Create version.json
Added version file
2023-11-29 09:58:16 +01:00
Pax1601
3ea1ab8f30 Added link to wiki 2023-11-28 17:36:55 +01:00
Pax1601
76801f773d Merge pull request #601 from Pax1601/412-improve-importexport
412 improve importexport
2023-11-28 17:31:34 +01:00
Pax1601
de7eeec94a Completed import/export page 2023-11-28 17:30:59 +01:00
Pax1601
0723a3ab95 Merge branch 'main' into 412-improve-importexport 2023-11-28 17:15:41 +01:00
Pax1601
8ef48ad977 Implemented filename selector 2023-11-28 16:59:23 +01:00
Pax1601
ed24d1af60 Merge pull request #600 from Pax1601/fix-aim-errors
Fixed error in lead calculation code
2023-11-28 16:42:45 +01:00
Pax1601
e3dffb8245 Fixed error in lead calculation code 2023-11-28 16:41:43 +01:00
Pax1601
74310a5ad3 Fixed installation prompt about passwords 2023-11-27 21:26:48 +01:00
Pax1601
a2223165e8 Merge branch 'main' into 412-improve-importexport 2023-11-26 21:13:24 +01:00
Pax1601
a879761dc2 Installation fixes 2023-11-24 21:38:55 +01:00
Pax1601
124b909aa4 v0.4.8 2023-11-24 19:43:24 +01:00
Pax1601
858e9eb066 Merge pull request #591 from Pax1601/584-add-state-icon-for-scenic-aaa-functions
Added state icons for scenic functions
2023-11-24 17:53:02 +01:00
Pax1601
7415e0cb97 Added state icons for scenic functions
And fixed dropdowns in case of duplicate labels
2023-11-24 17:52:48 +01:00
Pax1601
915020ddc3 Merge pull request #590 from Pax1601/532-stop-any-previous-connection-requests-before-connecting-again
Added code to clear any previous connection attempt
2023-11-24 11:50:32 +01:00
Pax1601
95915489b2 Added code to clear any previous connection attempt 2023-11-24 11:49:09 +01:00
Pax1601
3e39c3351a Merge pull request #589 from Pax1601/520-south-atlantic-initial-viewpoint
Added bounds for South Atlantic map
2023-11-24 11:43:41 +01:00
Pax1601
244d32e7d2 Added bounds for South Atlantic map 2023-11-24 11:43:26 +01:00
Pax1601
16a79827b7 Merge pull request #588 from Pax1601/586-spawn-toggle-background-missing
Fixed toggles
2023-11-24 10:05:13 +01:00
Pax1601
bd6a36e9ea Merge pull request #587 from Pax1601/487-improve-simulated-firefights
Added indirect fire mode
2023-11-23 23:45:20 +01:00
Pax1601
771695af59 Added indirect fire mode 2023-11-23 23:45:00 +01:00
PeekabooSteam
e811c7bd46 Fixed toggles 2023-11-23 22:44:24 +00:00
Pax1601
20f99c287f Minor change in unit label dropdown
It now expands to fill the space, but it still overflows with long unit names as per #542
2023-11-23 20:32:42 +01:00
Pax1601
f464c9ae76 Merge pull request #585 from Pax1601/511-jf-17-isnt-assigned-to-any-roles-other-than-cap
Added JF-17 loadouts
2023-11-23 20:12:27 +01:00
Pax1601
15ccb3dc04 Added JF-17 loadouts 2023-11-23 20:11:54 +01:00
Pax1601
defe40d48f Merge pull request #583 from Pax1601/miss-on-purpose-tweaks
Added effect of acquisition range to wake units up
2023-11-23 20:09:43 +01:00
Pax1601
0f2622e821 Merge pull request #560 from Pax1601/524-units-will-not-perform-scenic-aaa-until-the-operate-as-toggle-is-toggled
Fixed missing reset of expected value of operateAs toggle
2023-11-23 20:09:09 +01:00
Pax1601
b442d238cd Fixed error in getUnitsVariable function 2023-11-23 20:08:37 +01:00
Pax1601
00f2a3c19d Merge branch 'main' into 524-units-will-not-perform-scenic-aaa-until-the-operate-as-toggle-is-toggled 2023-11-23 19:57:09 +01:00
Pax1601
dcbfdf8666 Added effect of acquisition range to wake units up
Note: alertness time constant suppressed
2023-11-23 16:40:18 +01:00
Pax1601
6e84423032 Merge pull request #577 from Pax1601/576-toggles-gone-walkabout
Fixed UCP toggles, added copy-paste control for plugins
2023-11-23 10:29:23 +01:00
PeekabooSteam
912ab75a96 Moved Database Manager Link 2023-11-22 17:55:39 +00:00
PeekabooSteam
1022fc2f0c Fixed UCP toggles, added copy-paste control for plugins 2023-11-22 17:15:23 +00:00
Pax1601
65edce855b Merge pull request #568 from Pax1601/loading-screen
Loading screen
2023-11-22 15:13:11 +01:00
Pax1601
0ba05d5795 Merge pull request #572 from Pax1601/540-grouped-units-cant-be-selected
Grouped units can be deleted now
2023-11-22 15:13:02 +01:00
Pax1601
1b17e17290 Added some TODO comments
And very minor refactoring
2023-11-22 15:12:28 +01:00
Pax1601
87957df1fb Fixed incorrect behaviour when rotating unit destinations
And a minor map code refactoring
2023-11-22 15:04:09 +01:00
Pax1601
d3f8d4eff7 Grouped units can be deleted now
Also added some performance optimizations on drawing of selected units and removed destination preview icon for single unit selection
2023-11-22 13:12:35 +01:00
Pax1601
3ffeed3b39 Merge pull request #569 from Pax1601/533-allow-users-to-edit-the-config-json-during-the-installation-process
533 allow users to edit the config json during the installation process
2023-11-22 11:35:06 +01:00
Pax1601
4197e4402c Relative paths for nwjs and node folders 2023-11-22 11:33:13 +01:00
Pax1601
1cfe6f5583 Added simple loading screen 2023-11-22 11:32:13 +01:00
Pax1601
ad5cb83abf Completed server version 2023-11-21 17:35:09 +01:00
Pax1601
f4a4882dbc Merge pull request #564 from Pax1601/563-map-icon-disappears-after-changing-map-type
We no longer LOSE the icon
2023-11-21 14:38:34 +01:00
Pax1601
ecb2cf182a Merge pull request #561 from Pax1601/Shredmetal-patch-1
Update LEGAL
2023-11-21 14:38:20 +01:00
Pax1601
6aea839e04 Added configurator GUI 2023-11-21 14:34:51 +01:00
PeekabooSteam
e20d2fa95a We no longer LOSE the icon 2023-11-20 20:33:41 +00:00
Pax1601
806d0cc52f Added ability for users to edit configuration during installation 2023-11-20 18:19:41 +01:00
Shredmetal
2a2baaf324 Update LEGAL 2023-11-21 01:02:03 +08:00
Pax1601
74989ec015 Fixed missing reset of expected value of operateAs toggle 2023-11-20 12:30:44 +01:00
Pax1601
31af3a53ce Merge pull request #559 from Pax1601/528-unknown-units-have-no-icons
Added default marker for unkown units
2023-11-20 11:43:42 +01:00
Pax1601
361d51b55d Merge pull request #545 from Pax1601/531-november-ui-cleanup
531 november UI cleanup
2023-11-20 09:45:36 +01:00
Pax1601
a12c09eba5 Minor refactoring 2023-11-20 09:44:59 +01:00
Pax1601
bcfe0653bd Merge pull request #544 from Pax1601/543-duplicated-categories-due-to-misspelling
Fixed typos
2023-11-20 09:35:10 +01:00
PeekabooSteam
c43bc763f3 Fixed typos 2023-11-19 13:47:24 +00:00
PeekabooSteam
85325c17ac More styling, added pulse to scenic actions from (?) 2023-11-19 11:17:00 +00:00
PeekabooSteam
178706f8de Finalised styling for UCP 2023-11-18 22:02:59 +00:00
Pax1601
51defbb8b2 Added default marker for unkown units 2023-11-18 19:01:47 +01:00
PeekabooSteam
84cf9aa27a More changes from the list 2023-11-17 22:44:15 +00:00
PeekabooSteam
a66098e080 Merge branch 'main' into 531-november-ui-cleanup 2023-11-17 21:46:58 +00:00
PeekabooSteam
f00df5ca3f Some changes done 2023-11-17 17:07:56 +00:00
PeekabooSteam
210c1fbecf Added a selective interface for import and export 2023-11-16 21:44:40 +00:00
PeekabooSteam
b89c5142b6 Merge and fix 2023-11-16 11:28:32 +00:00
PeekabooSteam
0421f6b8fe Fixed regex undefined 2023-11-16 11:16:54 +00:00
PeekabooSteam
9ef6efa3e0 Mid-way commit 2023-11-12 16:18:31 +00:00
PeekabooSteam
b43afd4e9c Export customisation working - but ugly. 2023-11-08 14:09:35 +00:00
PeekabooSteam
7700aa2030 Merge branch 'main' into 412-improve-importexport 2023-11-08 12:52:27 +00:00
PeekabooSteam
e68683acb7 Export matrix reads from data 2023-11-07 22:04:09 +00:00
PeekabooSteam
8dc48c10c3 Added dialog 2023-11-05 22:35:00 +00:00
142 changed files with 50514 additions and 33759 deletions

4
.gitignore vendored
View File

@@ -1,5 +1,8 @@
bin
/scripts/old
/scripts/python/configurator/dist
/scripts/python/configurator/build
/scripts/python/configurator/venv
.vs
x64
core.user
@@ -15,3 +18,4 @@ node_modules
hgt
/client/public/databases/units/old
/client/plugins/databasemanager/index.js

40
LEGAL
View File

@@ -2,13 +2,15 @@ DCS Olympus
A real-time AI unit control mod for DCS World
Copyright (C) 2023 Veltro & Gang
Copyright (C) 2023 Veltro & Gang (the "DCS Olympus Team" or the
"Rightsholders")
DCS Olympus (the "MATERIAL" or "Software") is provided completely free
to users subject to the it under both the terms of version 3 of the GNU
General Public License as published by the Free Software Foundation, and
the additional terms set out below; except where such terms conflict with this
disclaimer, in which case, the terms of this disclaimer shall prevail.
General Public License ("GPLv3") as published by the Free Software Foundation,
and the additional terms set out below (the "Additional Terms"). In the event
that the terms of GPLv3 conflict with the Additional Terms, the
Additional Terms shall prevail.
The authors and/or copyright holders of the Software have not received any
financial benefit in connection with the Software. In any event, the
@@ -17,15 +19,14 @@ implied, including but not limited to the warranties of merchantability,
fitness for a particular purpose and non-infringement. In no event shall
the authors and/or copyright holders be liable for any claim, damages or
other liability, whether in an action of contract, tort or otherwise,
arising from, out of or in connection with the Software or the use or o
ther dealings in the Software.
Any party making use of the Software in any manner agrees to be
bound by the terms set out in this disclaimer, version 3 of the GNU
General Public Licence, and the Additional Terms below.
arising from, out of or in connection with the Software or the use or
other dealings in the Software.
THIS MATERIAL IS NOT MADE OR SUPPORTED BY EAGLE DYNAMICS SA.
Any party making use of the Software in any manner agrees to be bound by
the entirety of this document.
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
@@ -657,4 +658,21 @@ copy of the Program in return for a fee.
GNU General Public Licence Version 3 above shall be governed by and
interpreted in accordance with English Law and the parties submit to the
exclusive jurisdiction of the English Courts.
2. Entire Agreement
The text of this document contains the entire understanding between
you, the licensee, and the authors and/or copyright holders of the
Software with respect to the subject matter to which it pertains.
It supersedes all prior agreements and understandings (if applicable),
oral or written, with respect to such matters.
3. Unilateral Modification
The parties agree that the DCS Olympus Team shall have the right to
unilaterally modify these terms (i.e. the agreement between you and the
DCS Olympus Team for the use of the Software), and that parties shall
be bound by such terms as modified from time to time. The DCS Olympus Team
shall not have an obligation to inform you of such modification, save that
such changes will be published on the DCS Olympus Github Repository located at
https://github.com/Pax1601/DCSOlympus.

View File

@@ -1,4 +1,4 @@
# Important note: DCS Olympus is in alpha state. No official release has been produced yet. The first public version is planned for Q4 2023.
# Important note: DCS Olympus is in alpha state. No official release has been produced yet. The first public version is planned for mid december 2023.
# DCS Olympus
*A real-time web interface to spawn and control units in DCS World*
@@ -6,32 +6,19 @@
![alt text](https://github.com/Pax1601/DCSOlympus/blob/main/client/sample.png?raw=true)
### What is this?
DCS Olympus is a mod for DCS World. It allows users to spawn, control, task, group, and remove units from a DCS World server using a real-time map interface, similarly to Real Time Strategy games. The user interface also provides useful informations units, like loadouts, fuel, tasking, and so on. In the future, more features for DCS World GCI and JTAC will be available.
DCS: Olympus is a free and open-source mod for DCS that enables dynamic real-time control through a map interface. The user is able to spawn units/groups, deploy a variety of effects such as smoke, flares, or explosions, and waypoints/tasks can be given to AI units in real-time in a way similar to a classic RTS game.
### Features and how to use it
- Spawn air and ground units, with preset loadouts
- Double click on the map to spawn a blue and red units, both in the air and in the ground, with preset loadouts for air-to-air or air-to-ground tasks;
- Control units
- Select one ore more units to move them around. Hold down ctrl and click to create a route for the unit to follow;
- Attack other units
- After selecting one ore more units, double click on another unit and select "Attack" to attack it, depending on the available weapons.
Additionally Olympus is able to run several effects and unit behaviours beyond the core DCS offerings. This includes such things as napalm and white phosphosous explosions, or setting up AA units to fire at players and miss, and more.
It even includes Red and Blue modes which limit your view and powers to just seeing what your coalition sees, with a spawning budget you could play against your friends even with no-one in the game piloting, or have a Red commander working against a squadron of blue pilots, and/or a blue commander working with them.
Even better it requires no client mods be installed if used on a server
The full feature list is simply too long to enumerate in a short summary but needless to say Olympus offers up a lot of unique gameplay that has previously not existed, and enhances many other elements of DCS in exciting ways
### Installing DCS Olympus
A prebuilt installer will soon be released and available here
### Building DCS Olympus
DCS Olympus is comprised of two modules:
A "core" c++ .dll module, which is run by DCS and exposes all the necessary data, and provides endpoints for commands from a REST server. A Visual Studio 2017/2019/2022 solution is provided, and requires no additional configuration. The core dll solution has two dependencies, both can be installed using vcpkg (https://vcpkg.io/en/getting-started.html):
- cpprestsdk: `vcpkg install cpprestsdk:x64-windows`
- geographiclib: `vcpkg install geographiclib:x64-windows`
A "client" node.js typescript web app, which can be hosted on the server using express.js. A Visual Studio Code configuration is provided for debugging. The client requires node.js to be installed for building (https://nodejs.org/en/). After installing node.js, move in the client folder and run the following commands:
- `npm install`
- `npm -g install`
After installing all the necessary dependencies you can start a development server executing the *client/debug.bat* batch file, and visiting http:\\localhost:3000 with any modern browser (tested with updated Chrome, Firefox and Edge). However, it is highly suggested to simply run the `Launch Chrome against localhost` debug configuration in Visual Studio Code.

28
build.bat Normal file
View File

@@ -0,0 +1,28 @@
call git clean -fx
cd src
msbuild olympus.sln /t:Rebuild /p:Configuration=Release
cd ..
cd scripts/python/configurator
call build_configurator.bat
cd ../../..
cd client
rmdir /s /q "hgt"
call npm install
call npm run emit-declarations
call npm run build-release
cd "plugins\controltips"
call npm install
call npm run build-release
cd "..\.."
cd "plugins\databasemanager"
call npm install
call npm run build-release
cd "..\.."
call npm prune --production
cd ..

View File

@@ -1,17 +1,2 @@
cd src
msbuild olympus.sln /t:Rebuild /p:Configuration=Release
cd ..
cd client
call npm install
rmdir /s /q "hgt"
call npm run emit-declarations
call npm run build
cd "plugins\controltips"
call npm run build
cd "..\.."
cd "plugins\databasemanager"
call npm run build
cd "..\.."
call npm prune --production
cd ..
call build.bat
call "C:\Program Files (x86)\Inno Setup 6\iscc.exe" "installer\olympus.iss"

File diff suppressed because it is too large Load Diff

View File

@@ -43,7 +43,7 @@ if (config["server"] != undefined)
module.exports = app;
const DemoDataGenerator = require('./demo.js');
var demoDataGenerator = new DemoDataGenerator(app);
var demoDataGenerator = new DemoDataGenerator(app, config);

View File

@@ -1,5 +1,18 @@
#!/usr/bin/env node
console.log('\x1b[36m%s\x1b[0m', "*********************************************************************");
console.log('\x1b[36m%s\x1b[0m', "* _____ _____ _____ ____ _ *");
console.log('\x1b[36m%s\x1b[0m', "* | __ \\ / ____|/ ____| / __ \\| | *");
console.log('\x1b[36m%s\x1b[0m', "* | | | | | | (___ | | | | |_ _ _ __ ___ _ __ _ _ ___ *");
console.log('\x1b[36m%s\x1b[0m', "* | | | | | \\___ \\ | | | | | | | | '_ ` _ \\| '_ \\| | | / __| *");
console.log('\x1b[36m%s\x1b[0m', "* | |__| | |____ ____) | | |__| | | |_| | | | | | | |_) | |_| \\__ \\ *");
console.log('\x1b[36m%s\x1b[0m', "* |_____/ \\_____|_____/ \\____/|_|\\__, |_| |_| |_| .__/ \\__,_|___/ *");
console.log('\x1b[36m%s\x1b[0m', "* __/ | | | *");
console.log('\x1b[36m%s\x1b[0m', "* |___/ |_| *");
console.log('\x1b[36m%s\x1b[0m', "*********************************************************************");
console.log('\x1b[36m%s\x1b[0m', "");
console.log("Please wait while DCS Olympus Server starts up...");
var fs = require('fs');
let rawdata = fs.readFileSync('../olympus.json');
let config = JSON.parse(rawdata);
@@ -98,3 +111,6 @@ function onListening() {
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
console.log("DCS Olympus server v0.4.9-alpha-rc1 started correctly!")
console.log("Waiting for connections...")

View File

@@ -14,7 +14,7 @@ const DEMO_WEAPONS_DATA = {
}
class DemoDataGenerator {
constructor(app)
constructor(app, config)
{
app.get('/demo/units', (req, res) => this.units(req, res));
app.get('/demo/weapons', (req, res) => this.weapons(req, res));
@@ -27,15 +27,15 @@ class DemoDataGenerator {
app.use('/demo', basicAuth({
users: {
'admin': 'password',
'blue': 'bluepassword',
'red': 'redpassword'
'admin': config["authentication"]["gameMasterPassword"],
'blue': config["authentication"]["blueCommanderPassword"],
'red': config["authentication"]["redCommanderPassword"]
},
}))
let baseData = { alive: true, human: false, controlled: true, coalition: 2, country: 0, unitName: "Cool guy", groupName: "Cool group 1", state: 1, task: "Being cool!",
hasTask: true, position: { lat: 37, lng: -116, alt: 1000 }, speed: 200, horizontalVelocity: 200, verticalVelicity: 0, heading: 45, isActiveTanker: false, isActiveAWACS: false, onOff: true, followRoads: false, fuel: 50,
let baseData = { alive: true, human: false, controlled: true, coalition: 2, country: 0, unitName: "Cool guy", groupName: "Cool group 1", state: 13, task: "Being cool!",
hasTask: true, position: { lat: 37, lng: -116, alt: 1000 }, speed: 200, horizontalVelocity: 200, verticalVelicity: 0, heading: 45, track: 45, isActiveTanker: false, isActiveAWACS: false, onOff: true, followRoads: false, fuel: 50,
desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0,
formationOffset: { x: 0, y: 0, z: 0 },
targetID: 0,
@@ -52,10 +52,10 @@ class DemoDataGenerator {
isLeader: true
}
// UNCOMMENT TO TEST ALL UNITS ****************
/*
***************** UNCOMMENT TO TEST ALL UNITS ****************
var databases = Object.assign({}, aircraftDatabase, helicopterDatabase, groundUnitDatabase, navyUnitDatabase);
var t = Object.keys(databases).length;
var l = Math.floor(Math.sqrt(t));
@@ -70,6 +70,7 @@ class DemoDataGenerator {
DEMO_UNIT_DATA[idx].groupName = `Group-${idx}`;
DEMO_UNIT_DATA[idx].position.lat += latIdx / 5;
DEMO_UNIT_DATA[idx].position.lng += lngIdx / 5;
DEMO_UNIT_DATA[idx].coalition = Math.floor(Math.random() * 3)
latIdx += 1;
if (latIdx === l) {
@@ -89,8 +90,9 @@ class DemoDataGenerator {
idx += 1;
}
}
*/
*/
let idx = 1;
DEMO_UNIT_DATA[idx] = JSON.parse(JSON.stringify(baseData));
DEMO_UNIT_DATA[idx].name = "S_75M_Volhov";
@@ -114,6 +116,7 @@ class DemoDataGenerator {
DEMO_UNIT_DATA[idx].position.lat += idx / 100;
DEMO_UNIT_DATA[idx].category = "GroundUnit";
DEMO_UNIT_DATA[idx].isLeader = false;
idx += 1;
DEMO_UNIT_DATA[idx] = JSON.parse(JSON.stringify(baseData));
@@ -130,6 +133,8 @@ class DemoDataGenerator {
DEMO_UNIT_DATA[idx].position.lat += idx / 100;
DEMO_UNIT_DATA[idx].category = "GroundUnit";
DEMO_UNIT_DATA[idx].isLeader = true;
DEMO_UNIT_DATA[idx].coalition = 0;
DEMO_UNIT_DATA[idx].operateAs = 2;
idx += 1;
DEMO_UNIT_DATA[idx] = JSON.parse(JSON.stringify(baseData));
@@ -138,6 +143,8 @@ class DemoDataGenerator {
DEMO_UNIT_DATA[idx].position.lat += idx / 100;
DEMO_UNIT_DATA[idx].category = "GroundUnit";
DEMO_UNIT_DATA[idx].isLeader = true;
DEMO_UNIT_DATA[idx].coalition = 0;
DEMO_UNIT_DATA[idx].operateAs = 1;
idx += 1;
DEMO_UNIT_DATA[idx] = JSON.parse(JSON.stringify(baseData));
@@ -158,48 +165,50 @@ class DemoDataGenerator {
if (req.query["time"] == 0){
for (let idx in DEMO_UNIT_DATA) {
const unit = DEMO_UNIT_DATA[idx];
var dataIndex = 1;
array = this.concat(array, this.uint32ToByteArray(idx));
array = this.appendString(array, unit.category, 1);
array = this.appendUint8(array, unit.alive, 2);
array = this.appendUint8(array, unit.human, 3);
array = this.appendUint8(array, unit.controlled, 4);
array = this.appendUint16(array, unit.coalition, 5);
array = this.appendUint8(array, unit.country, 6);
array = this.appendString(array, unit.name, 7);
array = this.appendString(array, unit.unitName, 8);
array = this.appendString(array, unit.groupName, 9);
array = this.appendUint8(array, unit.state, 10);
array = this.appendString(array, unit.task, 11);
array = this.appendUint8(array, unit.hasTask, 12);
array = this.appendCoordinates(array, unit.position, 13);
array = this.appendDouble(array, unit.speed, 14);
array = this.appendDouble(array, unit.horizontalVelocity, 15);
array = this.appendDouble(array, unit.verticalVelicity, 16);
array = this.appendDouble(array, unit.heading, 17);
array = this.appendUint8(array, unit.isActiveTanker, 18);
array = this.appendUint8(array, unit.isActiveAWACS, 19);
array = this.appendUint8(array, unit.onOff, 20);
array = this.appendUint8(array, unit.followRoads, 21);
array = this.appendUint16(array, unit.fuel, 22);
array = this.appendDouble(array, unit.desiredSpeed, 23);
array = this.appendUint8(array, unit.desiredSpeedType, 24);
array = this.appendDouble(array, unit.desiredAltitude, 25);
array = this.appendUint8(array, unit.desiredAltitudeType, 26);
array = this.appendUint32(array, unit.leaderID, 27);
array = this.appendOffset(array, unit.formationOffset, 28);
array = this.appendUint32(array, unit.targetID, 29);
array = this.appendCoordinates(array, unit.targetPosition, 30);
array = this.appendUint8(array, unit.ROE, 31);
array = this.appendUint8(array, unit.reactionToThreat, 32);
array = this.appendUint8(array, unit.emissionsCountermeasures, 33);
array = this.appendTACAN(array, unit.TACAN, 34);
array = this.appendRadio(array, unit.radio, 35);
array = this.appendRadio(array, unit.generalSettings, 36);
array = this.appendAmmo(array, unit.ammo, 37);
array = this.appendContacts(array, unit.contacts, 38);
array = this.appendActivePath(array, unit.activePath, 39);
array = this.appendUint8(array, unit.isLeader, 40);
array = this.appendUint8(array, unit.operateAs, 41);
array = this.appendString(array, unit.category, dataIndex); dataIndex++;
array = this.appendUint8(array, unit.alive, dataIndex); dataIndex++;
array = this.appendUint8(array, unit.human, dataIndex); dataIndex++;
array = this.appendUint8(array, unit.controlled, dataIndex); dataIndex++;
array = this.appendUint16(array, unit.coalition, dataIndex); dataIndex++;
array = this.appendUint8(array, unit.country, dataIndex); dataIndex++;
array = this.appendString(array, unit.name, dataIndex); dataIndex++;
array = this.appendString(array, unit.unitName, dataIndex); dataIndex++;
array = this.appendString(array, unit.groupName, dataIndex); dataIndex++;
array = this.appendUint8(array, unit.state, dataIndex); dataIndex++;
array = this.appendString(array, unit.task, dataIndex); dataIndex++;
array = this.appendUint8(array, unit.hasTask, dataIndex); dataIndex++;
array = this.appendCoordinates(array, unit.position, dataIndex); dataIndex++;
array = this.appendDouble(array, unit.speed, dataIndex); dataIndex++;
array = this.appendDouble(array, unit.horizontalVelocity, dataIndex); dataIndex++;
array = this.appendDouble(array, unit.verticalVelicity, dataIndex); dataIndex++;
array = this.appendDouble(array, unit.heading, dataIndex); dataIndex++;
array = this.appendDouble(array, unit.track, dataIndex); dataIndex++;
array = this.appendUint8(array, unit.isActiveTanker, dataIndex); dataIndex++;
array = this.appendUint8(array, unit.isActiveAWACS, dataIndex); dataIndex++;
array = this.appendUint8(array, unit.onOff, dataIndex); dataIndex++;
array = this.appendUint8(array, unit.followRoads, dataIndex); dataIndex++;
array = this.appendUint16(array, unit.fuel, dataIndex); dataIndex++;
array = this.appendDouble(array, unit.desiredSpeed, dataIndex); dataIndex++;
array = this.appendUint8(array, unit.desiredSpeedType, dataIndex); dataIndex++;
array = this.appendDouble(array, unit.desiredAltitude, dataIndex); dataIndex++;
array = this.appendUint8(array, unit.desiredAltitudeType, dataIndex); dataIndex++;
array = this.appendUint32(array, unit.leaderID, dataIndex); dataIndex++;
array = this.appendOffset(array, unit.formationOffset, dataIndex); dataIndex++;
array = this.appendUint32(array, unit.targetID, dataIndex); dataIndex++;
array = this.appendCoordinates(array, unit.targetPosition, dataIndex); dataIndex++;
array = this.appendUint8(array, unit.ROE, dataIndex); dataIndex++;
array = this.appendUint8(array, unit.reactionToThreat, dataIndex); dataIndex++;
array = this.appendUint8(array, unit.emissionsCountermeasures, dataIndex); dataIndex++;
array = this.appendTACAN(array, unit.TACAN, dataIndex); dataIndex++;
array = this.appendRadio(array, unit.radio, dataIndex); dataIndex++;
array = this.appendRadio(array, unit.generalSettings, dataIndex); dataIndex++;
array = this.appendAmmo(array, unit.ammo, dataIndex); dataIndex++;
array = this.appendContacts(array, unit.contacts, dataIndex); dataIndex++;
array = this.appendActivePath(array, unit.activePath, dataIndex); dataIndex++;
array = this.appendUint8(array, unit.isLeader, dataIndex); dataIndex++;
array = this.appendUint8(array, unit.operateAs, dataIndex); dataIndex++;
array = this.concat(array, this.uint8ToByteArray(255));
}
}

1297
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,10 +2,11 @@
"name": "DCSOlympus",
"node-main": "./bin/www",
"main": "http://localhost:3000",
"version": "v0.4.7-alpha",
"version": "v0.4.9-alpha-rc1",
"private": true,
"scripts": {
"build": "browserify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ] && copy.bat",
"build": "browserify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ] && copy.bat",
"build-release": "browserify .\\src\\index.ts -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ] && copy.bat",
"emit-declarations": "tsc --project tsconfig.json --declaration --emitDeclarationOnly --outfile ./@types/olympus/index.d.ts",
"copy": "copy.bat",
"start": "node ./bin/www",
@@ -20,14 +21,16 @@
"ejs": "^3.1.8",
"express": "~4.16.1",
"express-basic-auth": "^1.2.1",
"js-sha256": "^0.10.1",
"leaflet-gesture-handling": "^1.2.2",
"morgan": "~1.9.1",
"save": "^2.9.0",
"srtm-elevation": "^2.1.2"
"srtm-elevation": "^2.1.2",
"uuid": "^9.0.1"
},
"devDependencies": {
"@babel/preset-env": "^7.21.4",
"@tanem/svg-injector": "^10.1.55",
"@tanem/svg-injector": "^10.1.68",
"@types/formatcoords": "^1.1.0",
"@types/geojson": "^7946.0.10",
"@types/leaflet": "^1.9.0",
@@ -48,10 +51,9 @@
"nodemon": "^2.0.20",
"requirejs": "^2.3.6",
"sortablejs": "^1.15.0",
"tinyify": "^4.0.0",
"tsify": "^5.0.4",
"tslib": "latest",
"typedoc": "^0.24.8",
"typedoc-umlclass": "^0.7.1",
"typescript": "^4.9.4",
"usng.js": "^0.4.5",
"watchify": "^4.0.0"

View File

@@ -0,0 +1,8 @@
mkdir .\\..\\DCSOlympus\\client\\public\\plugins\\boilerplateplugin
copy .\\index.js .\\..\\DCSOlympus\\client\\public\\plugins\\boilerplateplugin\\index.js
copy .\\plugin.json .\\..\\DCSOlympus\\client\\public\\plugins\\boilerplateplugin\\plugin.json
copy .\\style.css .\\..\\DCSOlympus\\client\\public\\plugins\\boilerplateplugin\\style.css
mkdir .\\..\\DCSOlympus\\client\\public\\plugins\\boilerplateplugin\\images
copy .\\images\\*.* .\\..\\DCSOlympus\\client\\public\\plugins\\boilerplateplugin\\images\\

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 B

View File

@@ -0,0 +1,14 @@
{
"name": "boilerplateplugin",
"version": "v0.0.1",
"private": true,
"scripts": {
"build": "browserify ./src/index.ts -p [ tsify --noImplicitAny] > index.js && copy.bat"
},
"devDependencies": {
"@types/sortablejs": "^1.15.4",
"browserify": "^17.0.0",
"sortablejs": "^1.15.0",
"tsify": "^5.0.4"
}
}

View File

@@ -0,0 +1,7 @@
{
"name": "Boilerplate",
"version": "0.0.1",
"description": "Base plugin starter",
"authorName": "",
"authorContact": ""
}

View File

@@ -0,0 +1,3 @@
# Boilerplate plugin
See: https://github.com/Pax1601/DCSOlympus/wiki/Developer-Guide

View File

@@ -0,0 +1,27 @@
import { OlympusPlugin } from "interfaces";
import { OlympusApp } from "olympusapp";
export class BoilerplatePlugin implements OlympusPlugin {
#app!: OlympusApp;
constructor() {
}
/**
* @param app <OlympusApp>
*
* @returns boolean on success/fail
*/
initialize(app: OlympusApp) : boolean {
this.#app = app;
return true; // Return true on success
}
getName() {
return "Boilerplate";
}
}

View File

@@ -0,0 +1,5 @@
import { BoilerplatePlugin } from "./boilerplate";
globalThis.getOlympusPlugin = () => {
return new BoilerplatePlugin();
}

View File

View File

@@ -0,0 +1,104 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "ES2017", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
"rootDirs": ["./src", "./@types"], /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
"typeRoots": [
"./node_modules/@types",
"@types"
], /* Specify multiple folders that act like './node_modules/@types'. */
"types": [
"olympus"
], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": [
"src/*.ts",
"../DCSOlympus/client/@types/olympus/index.d.ts"
]
}

View File

@@ -1,384 +0,0 @@
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _ControlTipsPlugin_instances, _ControlTipsPlugin_element, _ControlTipsPlugin_app, _ControlTipsPlugin_shortcutManager, _ControlTipsPlugin_cursorIsHoveringOverUnit, _ControlTipsPlugin_cursorIsHoveringOverAirbase, _ControlTipsPlugin_mouseoverElement, _ControlTipsPlugin_updateTips;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ControlTipsPlugin = void 0;
const SHOW_CONTROL_TIPS = "Show control tips";
class ControlTipsPlugin {
constructor() {
_ControlTipsPlugin_instances.add(this);
_ControlTipsPlugin_element.set(this, void 0);
_ControlTipsPlugin_app.set(this, void 0);
_ControlTipsPlugin_shortcutManager.set(this, void 0);
_ControlTipsPlugin_cursorIsHoveringOverUnit.set(this, false);
_ControlTipsPlugin_cursorIsHoveringOverAirbase.set(this, false);
_ControlTipsPlugin_mouseoverElement.set(this, void 0);
__classPrivateFieldSet(this, _ControlTipsPlugin_element, document.createElement("div"), "f");
__classPrivateFieldGet(this, _ControlTipsPlugin_element, "f").id = "control-tips-panel";
document.body.appendChild(__classPrivateFieldGet(this, _ControlTipsPlugin_element, "f"));
}
getName() {
return "Control Tips Plugin";
}
initialize(app) {
__classPrivateFieldSet(this, _ControlTipsPlugin_app, app, "f");
__classPrivateFieldSet(this, _ControlTipsPlugin_shortcutManager, __classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getShortcutManager(), "f");
__classPrivateFieldGet(this, _ControlTipsPlugin_shortcutManager, "f").onKeyDown(() => {
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
});
__classPrivateFieldGet(this, _ControlTipsPlugin_shortcutManager, "f").onKeyUp(() => {
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
});
document.addEventListener("airbaseMouseover", (ev) => {
__classPrivateFieldSet(this, _ControlTipsPlugin_cursorIsHoveringOverAirbase, true, "f");
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
});
document.addEventListener("airbaseMouseout", (ev) => {
__classPrivateFieldSet(this, _ControlTipsPlugin_cursorIsHoveringOverAirbase, false, "f");
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
});
document.addEventListener("unitDeselection", (ev) => {
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
});
document.addEventListener("unitMouseover", (ev) => {
__classPrivateFieldSet(this, _ControlTipsPlugin_cursorIsHoveringOverUnit, true, "f");
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
});
document.addEventListener("unitMouseout", (ev) => {
__classPrivateFieldSet(this, _ControlTipsPlugin_cursorIsHoveringOverUnit, false, "f");
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
});
document.addEventListener("unitsSelection", (ev) => {
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
});
document.addEventListener("mapVisibilityOptionsChanged", () => {
this.toggle(!__classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getMap().getVisibilityOptions()[SHOW_CONTROL_TIPS]);
});
document.addEventListener("mouseover", (ev) => {
if (ev.target instanceof HTMLElement) {
__classPrivateFieldSet(this, _ControlTipsPlugin_mouseoverElement, ev.target, "f");
}
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
});
document.addEventListener("mouseup", (ev) => {
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
});
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
__classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getMap().addVisibilityOption(SHOW_CONTROL_TIPS, true);
return true;
}
getElement() {
return __classPrivateFieldGet(this, _ControlTipsPlugin_element, "f");
}
toggle(bool) {
this.getElement().classList.toggle("hide", bool);
}
}
exports.ControlTipsPlugin = ControlTipsPlugin;
_ControlTipsPlugin_element = new WeakMap(), _ControlTipsPlugin_app = new WeakMap(), _ControlTipsPlugin_shortcutManager = new WeakMap(), _ControlTipsPlugin_cursorIsHoveringOverUnit = new WeakMap(), _ControlTipsPlugin_cursorIsHoveringOverAirbase = new WeakMap(), _ControlTipsPlugin_mouseoverElement = new WeakMap(), _ControlTipsPlugin_instances = new WeakSet(), _ControlTipsPlugin_updateTips = function _ControlTipsPlugin_updateTips() {
const combos = [
{
"keys": [],
"tips": [
{
"key": `SHIFT`,
"action": `Box select`,
"showIfHoveringOverAirbase": false,
"showIfHoveringOverUnit": false,
"showIfUnitSelected": false
},
{
"key": `Mouse1`,
"action": `Deselect`,
"showIfUnitSelected": true
},
{
"key": `Mouse1+drag`,
"action": `Move map`,
"showIfHoveringOverAirbase": false,
"showIfHoveringOverUnit": false,
"showIfUnitSelected": false
},
{
"key": `Mouse2`,
"action": `Spawn menu`,
"showIfUnitSelected": false,
"showIfHoveringOverAirbase": false,
"showIfHoveringOverUnit": false
},
{
"key": `Mouse2`,
"action": `Quick options`,
"showIfUnitSelected": false,
"showIfHoveringOverAirbase": false,
"showIfHoveringOverUnit": true
},
{
"key": `Mouse2`,
"action": `Airbase menu`,
"showIfUnitSelected": false,
"showIfHoveringOverAirbase": true,
"showIfHoveringOverUnit": false
},
{
"key": `Mouse2`,
"action": `Set first waypoint`,
"showIfHoveringOverAirbase": false,
"showIfUnitSelected": true,
"unitsMustBeControlled": true
},
{
"key": `Mouse2 (hold)`,
"action": `Interact (ground)`,
"showIfUnitSelected": true,
"showIfHoveringOverAirbase": false,
"showIfHoveringOverUnit": false,
"unitsMustBeControlled": true
},
{
"key": `Shift`,
"action": "<em> in formation...</em>",
"showIfUnitSelected": true,
"minSelectedUnits": 2
},
{
"key": "CTRL",
"action": "<em> ... more</em>",
"showIfUnitSelected": true,
"showIfHoveringOverAirbase": false,
"unitsMustBeControlled": true
},
{
"key": "CTRL",
"action": " Pin tool",
"showIfUnitSelected": false,
"showIfHoveringOverAirbase": false,
"showIfHoveringOverUnit": false,
"unitsMustBeControlled": true
},
{
"key": "CTRL+Mouse2",
"action": " Airbase menu",
"showIfUnitSelected": true,
"showIfHoveringOverAirbase": true,
"unitsMustBeControlled": true
},
{
"key": `Mouse1`,
"action": "Toggle Blue/Red",
"mouseoverSelector": "#coalition-switch .ol-switch-fill"
},
{
"key": `Mouse2`,
"action": "Set Neutral",
"mouseoverSelector": "#coalition-switch .ol-switch-fill"
},
{
"key": `Mouse1`,
"action": "Toggle time display",
"mouseoverSelector": "#connection-status-panel[data-is-connected] #connection-status-message abbr"
},
{
"key": `Mouse1 or Z`,
"action": "Change location system",
"mouseoverSelector": "#coordinates-tool, #coordinates-tool *"
},
{
"key": `Comma`,
"action": "Decrease precision",
"mouseoverSelector": `#coordinates-tool[data-location-system="MGRS"], #coordinates-tool[data-location-system="MGRS"] *`
},
{
"key": `Period`,
"action": "Increase precision",
"mouseoverSelector": `#coordinates-tool[data-location-system="MGRS"], #coordinates-tool[data-location-system="MGRS"] *`
}
]
},
{
"keys": ["ControlLeft"],
"tips": [
{
"key": `Mouse1`,
"action": "Toggle pin",
"showIfUnitSelected": false,
"showIfHoveringOverAirbase": false,
"showIfHoveringOverUnit": false
},
{
"key": `Mouse1`,
"action": "Toggle selection",
"showIfUnitSelected": true,
"showIfHoveringOverAirbase": false,
"showIfHoveringOverUnit": true
},
{
"key": `Mouse2`,
"action": `Add waypoint`,
"showIfHoveringOverAirbase": false,
"showIfHoveringOverUnit": false,
"showIfUnitSelected": true,
"unitsMustBeControlled": true
},
{
"key": `Mouse2`,
"action": `Interact (airbase)`,
"showIfHoveringOverAirbase": true,
"showIfUnitSelected": true,
"unitsMustBeControlled": true
},
{
"key": `Mouse2`,
"action": `Interact (unit)`,
"showIfHoveringOverAirbase": false,
"showIfHoveringOverUnit": true,
"showIfUnitSelected": true,
"unitsMustBeControlled": true
},
{
"key": `Shift`,
"action": "<em> in formation...</em>",
"showIfUnitSelected": true,
"minSelectedUnits": 2
},
{
"key": `[Num 1-9]`,
"action": "Set hotgroup",
"showIfUnitSelected": true
}
]
},
{
"keys": ["ShiftLeft"],
"tips": [
{
"key": `Mouse1+drag`,
"action": "Box select",
"showIfUnitSelected": false
},
{
"key": `Mouse2`,
"action": "Set first formation waypoint",
"showIfUnitSelected": true,
"minSelectedUnits": 2
},
{
"key": `[Num 1-9]`,
"action": "Add to hotgroup",
"showIfUnitSelected": true
},
{
"key": "CTRL",
"action": "<em> ... more</em>",
"minSelectedUnits": 2,
"showIfUnitSelected": true,
"showIfHoveringOverAirbase": false,
"unitsMustBeControlled": true
}
]
},
{
"keys": ["ControlLeft", "ShiftLeft"],
"tips": [
{
"key": `Mouse2`,
"action": "Add formation waypoint",
"showIfUnitSelected": true,
"minSelectedUnits": 2,
"unitsMustBeControlled": true
}, {
"key": `[Num 1-9]`,
"action": "Add hotgroup to selection",
"callback": (tip) => {
return (Object.values(__classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getUnitsManager().getUnits()).some((unit) => {
return unit.getHotgroup();
}));
},
"showIfUnitSelected": true,
"minSelectedUnits": 1
}
]
}
];
const currentCombo = combos.find((combo) => __classPrivateFieldGet(this, _ControlTipsPlugin_shortcutManager, "f").keyComboMatches(combo.keys)) || combos[0];
const element = this.getElement();
element.innerHTML = "";
let numSelectedUnits = 0;
let numSelectedControlledUnits = 0;
let unitSelectionContainsControlled = false;
if (__classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getUnitsManager()) {
let selectedUnits = Object.values(__classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getUnitsManager().getSelectedUnits());
numSelectedUnits = selectedUnits.length;
numSelectedControlledUnits = selectedUnits.filter((unit) => unit.getControlled()).length;
unitSelectionContainsControlled = numSelectedControlledUnits > 0;
}
const tipsIncludesActiveMouseover = (currentCombo.tips.some((tip) => {
if (!tip.mouseoverSelector) {
return false;
}
if (__classPrivateFieldGet(this, _ControlTipsPlugin_mouseoverElement, "f") instanceof HTMLElement === false) {
return false;
}
if (!__classPrivateFieldGet(this, _ControlTipsPlugin_mouseoverElement, "f").matches(tip.mouseoverSelector)) {
return false;
}
return true;
}));
currentCombo.tips.filter((tip) => {
if (numSelectedUnits > 0) {
if (tip.showIfUnitSelected === false) {
return false;
}
if (tip.unitsMustBeControlled === true && unitSelectionContainsControlled === false) {
return false;
}
if (typeof tip.minSelectedUnits === "number" && numSelectedControlledUnits < tip.minSelectedUnits) {
return false;
}
}
if (numSelectedUnits === 0 && tip.showIfUnitSelected === true) {
return false;
}
if (typeof tip.showIfHoveringOverAirbase === "boolean") {
if (tip.showIfHoveringOverAirbase !== __classPrivateFieldGet(this, _ControlTipsPlugin_cursorIsHoveringOverAirbase, "f")) {
return false;
}
}
if (typeof tip.showIfHoveringOverUnit === "boolean") {
if (tip.showIfHoveringOverUnit !== __classPrivateFieldGet(this, _ControlTipsPlugin_cursorIsHoveringOverUnit, "f")) {
return false;
}
}
if (tipsIncludesActiveMouseover && (typeof tip.mouseoverSelector !== "string" || !__classPrivateFieldGet(this, _ControlTipsPlugin_mouseoverElement, "f").matches(tip.mouseoverSelector))) {
return false;
}
if (!tipsIncludesActiveMouseover && typeof tip.mouseoverSelector === "string") {
return false;
}
if (typeof tip.callback === "function" && !tip.callback(tip)) {
return false;
}
element.innerHTML += `<div><span class="key">${tip.key}</span><span class="action">${tip.action}</span></div>`;
});
};
},{}],2:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const controltipsplugin_1 = require("./controltipsplugin");
globalThis.getOlympusPlugin = () => {
return new controltipsplugin_1.ControlTipsPlugin();
};
},{"./controltipsplugin":1}]},{},[2]);

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,27 @@
"version": "v0.0.1",
"private": true,
"scripts": {
"build": "browserify ./src/index.ts -p [ tsify --noImplicitAny] > index.js && copy.bat"
"build": "browserify ./src/index.ts -p [ tsify --noImplicitAny] > index.js && copy.bat",
"build-release": "browserify ./src/index.ts -p [ tsify --noImplicitAny] > index.js && copy.bat"
},
"dependencies": {},
"devDependencies": {}
"devDependencies": {
"@babel/preset-env": "^7.21.4",
"@types/node": "^18.16.1",
"@types/sortablejs": "^1.15.0",
"babelify": "^10.0.0",
"browserify": "^17.0.0",
"concurrently": "^7.6.0",
"cp": "^0.2.0",
"esmify": "^2.1.1",
"nodemon": "^2.0.20",
"requirejs": "^2.3.6",
"sortablejs": "^1.15.0",
"tinyify": "^4.0.0",
"tsify": "^5.0.4",
"tslib": "latest",
"typescript": "^4.9.4",
"usng.js": "^0.4.5",
"watchify": "^4.0.0"
}
}

View File

@@ -64,7 +64,7 @@ export class ControlTipsPlugin implements OlympusPlugin {
this.#updateTips();
});
document.addEventListener("mapVisibilityOptionsChanged", () => {
document.addEventListener("mapOptionsChanged", () => {
this.toggle( !this.#app.getMap().getVisibilityOptions()[SHOW_CONTROL_TIPS] );
});

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -4,10 +4,29 @@
"private": true,
"scripts": {
"build": "browserify ./src/index.ts -p [ tsify --noImplicitAny] > index.js && copy.bat",
"build-release": "browserify ./src/index.ts -p [ tsify --noImplicitAny] -p [ tinyify ] > index.js && copy.bat",
"start": "npm run copy & concurrently --kill-others \"npm run watch\"",
"copy": "copy.bat",
"watch": "watchify ./src/index.ts --debug -o ../../public/plugins/databasemanager/index.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]"
"watch": "watchify ./src/index.ts --debug -o ../../public/plugins/databasemanager/index.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js']"
},
"dependencies": {},
"devDependencies": {}
"devDependencies": {
"@babel/preset-env": "^7.21.4",
"@types/node": "^18.16.1",
"@types/sortablejs": "^1.15.0",
"babelify": "^10.0.0",
"browserify": "^17.0.0",
"concurrently": "^7.6.0",
"cp": "^0.2.0",
"esmify": "^2.1.1",
"nodemon": "^2.0.20",
"requirejs": "^2.3.6",
"sortablejs": "^1.15.0",
"tinyify": "^4.0.0",
"tsify": "^5.0.4",
"tslib": "latest",
"typescript": "^4.9.4",
"usng.js": "^0.4.5",
"watchify": "^4.0.0"
}
}

View File

@@ -16,7 +16,7 @@ import { NavyUnitEditor } from "./navyuniteditor";
*/
export class DatabaseManagerPlugin implements OlympusPlugin {
#app: OlympusApp | null = null;
#app!: OlympusApp;
#element: HTMLElement;
#mainContentContainer: HTMLElement;
@@ -157,6 +157,15 @@ export class DatabaseManagerPlugin implements OlympusPlugin {
*/
initialize(app: any) {
this.#app = app;
const contextManager = this.#app.getContextManager();
contextManager.add( "databaseManager", {
"allowUnitCopying": false,
"allowUnitPasting": false,
"useSpawnMenu": false,
"useUnitControlPanel": false,
"useUnitInfoPanel": false
});
/* Load the databases and initialize the editors */
this.#loadDatabases();
@@ -169,7 +178,7 @@ export class DatabaseManagerPlugin implements OlympusPlugin {
var toolbar: PrimaryToolbar = this.#app?.getToolbarsManager().get("primaryToolbar") as PrimaryToolbar;
var elements = toolbar.getMainDropdown().getOptionElements();
var arr = Array.prototype.slice.call(elements);
arr.splice(arr.length - 1, 0, mainButtonDiv);
arr.splice(arr.length - 3, 0, mainButtonDiv);
toolbar.getMainDropdown().setOptionsElements(arr);
mainButton.onclick = () => {
toolbar.getMainDropdown().close();
@@ -197,6 +206,9 @@ export class DatabaseManagerPlugin implements OlympusPlugin {
this.getElement().classList.toggle("hide", !bool);
else
this.getElement().classList.toggle("hide");
if ( this.#app )
this.#app.getContextManager().setContext( this.getElement().classList.contains("hide") ? "olympus" : "databaseManager" );
}
/** Hide all the editors

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

@@ -1,25 +1,8 @@
{
"BDK-775": {
"name": "BDK-775",
"coalition": "blue",
"type": "Landing Ship",
"era": "Mid Cold War",
"label": "LS Ropucha",
"shortLabel": "LS Ropucha",
"range": "",
"filename": "",
"enabled": true,
"acquisitionRange": 25000,
"engagementRange": 6000,
"description": "Landing ship Ropucha",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
},
"CVN_71": {
"name": "CVN_71",
"coalition": "blue",
"type": "Super Aircraft Carrier",
"type": "Aircraft Carrier",
"era": "Late Cold War",
"label": "CVN-71 Theodore Roosevelt",
"shortLabel": "CVN-71",
@@ -28,7 +11,8 @@
"enabled": true,
"acquisitionRange": 50000,
"engagementRange": 25000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
@@ -36,7 +20,7 @@
"CVN_72": {
"name": "CVN_72",
"coalition": "blue",
"type": "Super Aircraft Carrier",
"type": "Aircraft Carrier",
"era": "Late Cold War",
"label": "CVN-72 Abraham Lincoln",
"shortLabel": "CVN-72",
@@ -45,7 +29,8 @@
"enabled": true,
"acquisitionRange": 50000,
"engagementRange": 25000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
@@ -53,7 +38,7 @@
"CVN_73": {
"name": "CVN_73",
"coalition": "blue",
"type": "Super Aircraft Carrier",
"type": "Aircraft Carrier",
"era": "Late Cold War",
"label": "CVN-73 George Washington",
"shortLabel": "CVN-73",
@@ -62,7 +47,8 @@
"enabled": true,
"acquisitionRange": 50000,
"engagementRange": 25000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
@@ -101,7 +87,8 @@
},
"acquisitionRange": 50000,
"engagementRange": 25000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
@@ -111,13 +98,14 @@
"coalition": "red",
"type": "Aircraft Carrier",
"era": "Modern",
"label": "CV Admiral Kuznetsov(2017)",
"shortLabel": "Admiral Kuznetsov(2017)",
"label": "Admiral Kuznetsov (2017)",
"shortLabel": "Admiral Kuznetsov",
"range": "Medium",
"filename": "",
"enabled": true,
"acquisitionRange": 25000,
"engagementRange": 12000,
"tags": "",
"description": "Admiral Kuznetsov. Conventional STOBAR carrier",
"abilities": "",
"canTargetPoint": false,
@@ -126,8 +114,8 @@
"CastleClass_01": {
"name": "CastleClass_01",
"coalition": "blue",
"type": "Patrol",
"era": "Mid Cold War",
"type": "Combatants",
"era": "Late Cold War",
"label": "HMS Leeds Castle (P-258)",
"shortLabel": "HMS Leeds Castle (P-258)",
"range": "",
@@ -141,7 +129,8 @@
},
"acquisitionRange": 25000,
"engagementRange": 3000,
"description": "HMS Leeds Castle. Smaller. Patrol craft",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -149,7 +138,7 @@
"HandyWind": {
"name": "HandyWind",
"coalition": "blue",
"type": "Cargoship",
"type": "Cargo/Transport",
"era": "Late Cold War",
"label": "Bulker Handy Wind",
"shortLabel": "Bulker Handy Wind",
@@ -176,6 +165,7 @@
},
"acquisitionRange": 0,
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
@@ -184,7 +174,7 @@
"HarborTug": {
"name": "HarborTug",
"coalition": "",
"type": "Tug",
"type": "Cargo/Transport",
"era": "Mid Cold War",
"label": "Harbor Tug",
"shortLabel": "Harbor Tug",
@@ -211,6 +201,7 @@
},
"acquisitionRange": 0,
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
@@ -228,7 +219,8 @@
"enabled": true,
"acquisitionRange": 150000,
"engagementRange": 20000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -481,6 +473,7 @@
},
"acquisitionRange": 19000,
"engagementRange": 4000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
@@ -489,15 +482,16 @@
"Seawise_Giant": {
"name": "Seawise_Giant",
"coalition": "blue",
"type": "Tanker",
"type": "Cargo/Transport",
"era": "Late Cold War",
"label": "Tanker Seawise Giant",
"label": "Seawise Giant (Tanker)",
"shortLabel": "Seawise Giant",
"range": "",
"filename": "",
"enabled": true,
"acquisitionRange": 0,
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
@@ -506,7 +500,7 @@
"Ship_Tilde_Supply": {
"name": "Ship_Tilde_Supply",
"coalition": "blue",
"type": "Transport",
"type": "Cargo/Transport",
"era": "Late Cold War",
"label": "Supply Ship MV Tilde",
"shortLabel": "Supply Ship Tilde",
@@ -515,6 +509,7 @@
"enabled": true,
"acquisitionRange": 0,
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
@@ -532,6 +527,7 @@
"enabled": true,
"acquisitionRange": 50000,
"engagementRange": 25000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
@@ -540,9 +536,9 @@
"TICONDEROG": {
"name": "TICONDEROG",
"coalition": "blue",
"type": "Cruiser",
"type": "Combatants",
"era": "Late Cold War",
"label": "Ticonderoga",
"label": "Ticonderoga Class (Cruiser)",
"shortLabel": "Ticonderoga",
"range": "Medium",
"filename": "",
@@ -611,7 +607,8 @@
},
"acquisitionRange": 150000,
"engagementRange": 100000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -619,9 +616,9 @@
"Type_052B": {
"name": "Type_052B",
"coalition": "red",
"type": "Destroyer",
"type": "Combatants",
"era": "Modern",
"label": "052B DDG-168 Guangzhou",
"label": "Type 52B Guangzhou",
"shortLabel": "Type 52B",
"range": "Short",
"filename": "",
@@ -646,7 +643,8 @@
},
"acquisitionRange": 100000,
"engagementRange": 30000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -654,9 +652,9 @@
"Type_052C": {
"name": "Type_052C",
"coalition": "red",
"type": "Destroyer",
"type": "Combatants",
"era": "Modern",
"label": "052C DDG-171 Haikou",
"label": "Type 52C Haikou (Destroyer)",
"shortLabel": "Type 52C",
"range": "Short",
"filename": "",
@@ -705,17 +703,18 @@
},
"acquisitionRange": 260000,
"engagementRange": 100000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
},
"Type_054A": {
"name": "",
"name": "Type_054A",
"coalition": "red",
"type": "Frigate",
"type": "Combatants",
"era": "Modern",
"label": "054A FFG-538 Yantai",
"label": "Type 54A Yantai (Frigate)",
"shortLabel": "Type 54A",
"range": "Medium",
"filename": "",
@@ -872,15 +871,16 @@
},
"acquisitionRange": 160000,
"engagementRange": 45000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "35nm >50,000ft range"
},
"Type_071": {
"name": "Type_071",
"coalition": "red",
"type": "Transport",
"type": "Cargo/Transport",
"era": "Modern",
"label": "Type 071",
"label": "Type 071 (Transport dock)",
"shortLabel": "Type 071",
"range": "",
"filename": "",
@@ -925,7 +925,8 @@
},
"acquisitionRange": 300000,
"engagementRange": 150000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -935,7 +936,7 @@
"coalition": "red",
"type": "Submarine",
"era": "Modern",
"label": "Type 093",
"label": "Type 093 (Shang)",
"shortLabel": "Type 093",
"range": "",
"filename": "",
@@ -948,6 +949,7 @@
},
"acquisitionRange": 40000,
"engagementRange": 40000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
@@ -956,10 +958,10 @@
"USS_Arleigh_Burke_IIa": {
"name": "USS_Arleigh_Burke_IIa",
"coalition": "blue",
"type": "Destroyer",
"type": "Combatants",
"era": "Late Cold War",
"label": "DDG Arleigh Burke lla",
"shortLabel": "DDG Arleigh Burke",
"label": "Arleigh Burke Class (Destroyer)",
"shortLabel": "Arleigh Burke Class (Destroyer)",
"range": "Medium",
"filename": "",
"enabled": true,
@@ -1064,6 +1066,7 @@
"acquisitionRange": 150000,
"engagementRange": 100000,
"abilities": "",
"tags": "",
"description": "",
"canTargetPoint": true,
"canRearm": false
@@ -1080,7 +1083,8 @@
"enabled": true,
"acquisitionRange": 18000,
"engagementRange": 5000,
"description": "ARA Vienticinco de Mayo. Conventional CATOBAR carrier",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -1089,15 +1093,16 @@
"name": "hms_invincible",
"coalition": "blue",
"type": "Aircraft Carrier",
"era": "Mid Cold War",
"label": "HMS Invincible (R05)",
"era": "Late Cold War",
"label": "HMS Invincible",
"shortLabel": "HMS Invincible",
"range": "",
"filename": "",
"enabled": true,
"acquisitionRange": 100000,
"engagementRange": 74000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -1105,8 +1110,8 @@
"leander-gun-achilles": {
"name": "leander-gun-achilles",
"coalition": "blue",
"type": "Frigate",
"era": "Mid Cold War",
"type": "Combatants",
"era": "Late Cold War",
"label": "HMS Achilles (F12)",
"shortLabel": "HMS Achilles",
"range": "",
@@ -1114,7 +1119,8 @@
"enabled": true,
"acquisitionRange": 180000,
"engagementRange": 8000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -1122,8 +1128,8 @@
"leander-gun-andromeda": {
"name": "leander-gun-andromeda",
"coalition": "blue",
"type": "Frigate",
"era": "Mid Cold War",
"type": "Combatants",
"era": "Late Cold War",
"label": "HMS Andromeda (F57)",
"shortLabel": "HMS Andromeda",
"range": "",
@@ -1131,7 +1137,8 @@
"enabled": true,
"acquisitionRange": 180000,
"engagementRange": 140000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -1139,8 +1146,8 @@
"leander-gun-ariadne": {
"name": "leander-gun-ariadne",
"coalition": "blue",
"type": "Frigate",
"era": "Mid Cold War",
"type": "Combatants",
"era": "Late Cold War",
"label": "HMS Ariadne (F72)",
"shortLabel": "HMS Ariadne",
"range": "",
@@ -1148,7 +1155,8 @@
"enabled": true,
"acquisitionRange": 150000,
"engagementRange": 100000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -1156,8 +1164,8 @@
"leander-gun-condell": {
"name": "leander-gun-condell",
"coalition": "",
"type": "Frigate",
"era": "Mid Cold War",
"type": "Combatants",
"era": "Late Cold War",
"label": "Almirante Condell PFG-06",
"shortLabel": "Almirante Condell",
"range": "",
@@ -1165,7 +1173,8 @@
"enabled": true,
"acquisitionRange": 150000,
"engagementRange": 100000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -1173,8 +1182,8 @@
"leander-gun-lynch": {
"name": "leander-gun-lynch",
"coalition": "",
"type": "Frigate",
"era": "Mid Cold War",
"type": "Combatants",
"era": "Late Cold War",
"label": "CNS Almirante Lynch (PFG-07)",
"shortLabel": "CNS Almirante Lynch",
"range": "",
@@ -1182,7 +1191,8 @@
"enabled": true,
"acquisitionRange": 180000,
"engagementRange": 140000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -1191,7 +1201,7 @@
"name": "santafe",
"coalition": "",
"type": "Submarine",
"era": "Early Cold War",
"era": "WW2",
"label": "ARA Santa Fe S-21",
"shortLabel": "ARA Santa",
"range": "",
@@ -1199,6 +1209,7 @@
"enabled": true,
"acquisitionRange": 0,
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
@@ -1210,36 +1221,21 @@
"era": "",
"label": "Boat Armed Hi-speed",
"shortLabel": "Boat Armed Hi-speed",
"type": "Speedboat",
"type": "Fast Attack Craft",
"enabled": true,
"liveries": {},
"acquisitionRange": 5000,
"engagementRange": 1000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
},
"VINSON": {
"name": "VINSON",
"coalition": "",
"era": "",
"label": "CVN-70 Carl Vinson",
"shortLabel": "CVN-70 Carl Vinson",
"type": "Aircraft Carrier",
"enabled": true,
"liveries": {},
"acquisitionRange": 30000,
"engagementRange": 15000,
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"PERRY": {
"name": "perry",
"name": "PERRY",
"coalition": "blue",
"type": "Frigate",
"type": "Combatants",
"era": "Mid Cold War",
"label": "Oliver H. Perry",
"shortLabel": "Oliver H. Perry",
@@ -1289,13 +1285,18 @@
}
},
"acquisitionRange": 150000,
"engagementRange": 100000
"engagementRange": 100000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"ALBATROS": {
"name": "albatros",
"name": "ALBATROS",
"coalition": "red",
"type": "Frigade",
"era": "Early Cold War",
"type": "Combatants",
"era": "Modern",
"label": "Albatros (Grisha-5)",
"shortLabel": "Albatros",
"range": "",
@@ -1340,25 +1341,35 @@
}
},
"acquisitionRange": 30000,
"engagementRange": 16000
"engagementRange": 16000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"KUZNECOW": {
"name": "kuznecow",
"name": "KUZNECOW",
"coalition": "red",
"type": "Aircraft Carrier",
"era": "Late Cold War",
"label": "Admiral Kuznetsov",
"shortLabel": "Admiral Kuznetsov",
"shortLabel": "AK",
"range": "Medium",
"filename": "",
"enabled": true,
"acquisitionRange": 25000,
"engagementRange": 12000
"engagementRange": 12000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"MOLNIYA": {
"name": "molniya",
"name": "MOLNIYA",
"coalition": "",
"type": "Corvette",
"type": "Combatants",
"era": "Late Cold War",
"label": "Molniya (Tarantul-3)",
"shortLabel": "Molniya",
@@ -1380,12 +1391,17 @@
}
},
"acquisitionRange": 21000,
"engagementRange": 2000
"engagementRange": 2000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"MOSCOW": {
"name": "moscow",
"name": "MOSCOW",
"coalition": "red",
"type": "Cruiser",
"type": "Combatants",
"era": "Late Cold War",
"label": "Moscow",
"shortLabel": "Moscow",
@@ -1411,54 +1427,70 @@
}
},
"acquisitionRange": 160000,
"engagementRange": 75000
"engagementRange": 75000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"NEUSTRASH": {
"name": "neustrash",
"name": "NEUSTRASH",
"coalition": "red",
"type": "Frigate",
"type": "Combatants",
"era": "Late Cold War",
"label": "Neustrashimy",
"shortLabel": "Neustrashimy",
"range": "Short",
"range": "",
"filename": "",
"enabled": true,
"acquisitionRange": 27000,
"engagementRange": 12000
"engagementRange": 12000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"PIOTR": {
"name": "PIOTR",
"coalition": "",
"era": "",
"label": "Battlecruiser 1144.2 Pyotr Velikiy",
"shortLabel": "Battlecruiser 1144.2 Pyotr Velikiy",
"type": "Cruiser",
"coalition": "red",
"era": "Late Cold War",
"label": "Pyotr Velikiy (Battlecruiser)",
"shortLabel": "Pyotr Velikiy",
"type": "Combatants",
"enabled": true,
"liveries": {},
"acquisitionRange": 250000,
"engagementRange": 190000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
},
"REZKY": {
"name": "Rezky (Krivak-2)",
"name": "REZKY",
"coalition": "red",
"type": "Frigate",
"era": "Early Cold War",
"type": "Combatants",
"era": "WW2",
"label": "Rezky (Krivak-2)",
"shortLabel": "Rezky",
"range": "Short",
"range": "",
"filename": "",
"enabled": true,
"acquisitionRange": 30000,
"engagementRange": 16000
"engagementRange": 16000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"ELNYA": {
"name": "elnya",
"name": "ELNYA",
"coalition": "red",
"type": "Tanker",
"type": "Cargo/Transport",
"era": "Late Cold War",
"label": "Elnya tanker",
"shortLabel": "Elnya tanker",
@@ -1480,7 +1512,12 @@
}
},
"acquisitionRange": 0,
"engagementRange": 0
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"Dry-cargo ship-2": {
"name": "Dry-cargo ship-2",
@@ -1488,11 +1525,12 @@
"era": "",
"label": "Cargo Ivanov",
"shortLabel": "Cargo Ivanov",
"type": "Cargoship",
"type": "Cargo/Transport",
"enabled": true,
"liveries": {},
"acquisitionRange": 0,
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
@@ -1504,20 +1542,21 @@
"era": "",
"label": "Bulker Yakushev",
"shortLabel": "Bulker Yakushev",
"type": "Cargoship",
"type": "Cargo/Transport",
"enabled": true,
"liveries": {},
"acquisitionRange": 0,
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"ZWEZDNY": {
"name": "zwezdny",
"name": "ZWEZDNY",
"coalition": "",
"type": "Civilian Boat",
"type": "Cargo/Transport",
"era": "Modern",
"label": "Zwezdny",
"shortLabel": "Zwezdny",
@@ -1525,32 +1564,43 @@
"filename": "",
"enabled": true,
"acquisitionRange": 0,
"engagementRange": 0
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"KILO": {
"name": "kilo",
"name": "KILO",
"coalition": "red",
"type": "Submarine",
"era": "Late Cold War",
"era": "Mid Cold War",
"label": "Project 636 Varshavyanka Basic",
"shortLabel": "Varshavyanka Basic",
"range": "Medium",
"filename": "",
"enabled": true,
"acquisitionRange": 0,
"engagementRange": 0
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"IMPROVED_KILO": {
"name": "IMPROVED_KILO",
"coalition": "",
"coalition": "red",
"era": "",
"label": "SSK 636 Improved Kilo",
"shortLabel": "SSK 636 Improved Kilo",
"shortLabel": "Kilo",
"type": "Submarine",
"enabled": true,
"liveries": {},
"acquisitionRange": 0,
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
@@ -1561,12 +1611,13 @@
"coalition": "",
"era": "",
"label": "SSK 641B Tango",
"shortLabel": "SSK 641B Tango",
"shortLabel": "Tango",
"type": "Submarine",
"enabled": true,
"liveries": {},
"acquisitionRange": 0,
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
@@ -1574,8 +1625,8 @@
},
"Forrestal": {
"name": "Forrestal",
"coalition": "",
"era": "",
"coalition": "blue",
"era": "Early Cold War",
"label": "CV-59 Forrestal",
"shortLabel": "CV-59 Forrestal",
"type": "Aircraft Carrier",
@@ -1583,6 +1634,7 @@
"liveries": {},
"acquisitionRange": 50000,
"engagementRange": 25000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
@@ -1590,15 +1642,16 @@
},
"LST_Mk2": {
"name": "LST_Mk2",
"coalition": "",
"era": "",
"coalition": "blue",
"era": "WW2",
"label": "LST Mk.II",
"shortLabel": "LST Mk.II",
"type": "Landing Ship",
"type": "Cargo/Transport",
"enabled": true,
"liveries": {},
"acquisitionRange": 0,
"engagementRange": 4000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
@@ -1606,15 +1659,16 @@
},
"USS_Samuel_Chase": {
"name": "USS_Samuel_Chase",
"coalition": "",
"era": "",
"coalition": "blue",
"era": "WW2",
"label": "LS Samuel Chase",
"shortLabel": "LS Samuel Chase",
"type": "Landing SHip",
"type": "Cargo/Transport",
"enabled": true,
"liveries": {},
"acquisitionRange": 0,
"engagementRange": 7000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
@@ -1622,15 +1676,16 @@
},
"Higgins_boat": {
"name": "Higgins_boat",
"coalition": "",
"era": "",
"coalition": "blue",
"era": "WW2",
"label": "Boat LCVP Higgins",
"shortLabel": "Boat LCVP Higgins",
"type": "Landing Ship",
"type": "Cargo/Transport",
"enabled": true,
"liveries": {},
"acquisitionRange": 3000,
"engagementRange": 1000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
@@ -1638,15 +1693,16 @@
},
"Uboat_VIIC": {
"name": "Uboat_VIIC",
"coalition": "",
"era": "",
"label": "U-boat VIIC U-flak",
"shortLabel": "U-boat VIIC U-flak",
"coalition": "red",
"era": "WW2",
"label": "U-boat VIIC",
"shortLabel": "U-boat",
"type": "Submarine",
"enabled": true,
"liveries": {},
"acquisitionRange": 20000,
"engagementRange": 4000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
@@ -1654,15 +1710,16 @@
},
"Schnellboot_type_S130": {
"name": "Schnellboot_type_S130",
"coalition": "",
"era": "",
"coalition": "red",
"era": "WW2",
"label": "Boat Schnellboot type S130",
"shortLabel": "Boat Schnellboot type S130",
"type": "Torpedo Boat",
"type": "Fast Attack Craft",
"enabled": true,
"liveries": {},
"acquisitionRange": 10000,
"engagementRange": 4000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +1,8 @@
{
"BDK-775": {
"name": "BDK-775",
"coalition": "blue",
"type": "Landing Ship",
"era": "Mid Cold War",
"label": "LS Ropucha",
"shortLabel": "LS Ropucha",
"range": "",
"filename": "",
"enabled": true,
"acquisitionRange": 25000,
"engagementRange": 6000,
"description": "Landing ship Ropucha",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
},
"CVN_71": {
"name": "CVN_71",
"coalition": "blue",
"type": "Super Aircraft Carrier",
"type": "Aircraft Carrier",
"era": "Late Cold War",
"label": "CVN-71 Theodore Roosevelt",
"shortLabel": "CVN-71",
@@ -28,7 +11,8 @@
"enabled": true,
"acquisitionRange": 50000,
"engagementRange": 25000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
@@ -36,7 +20,7 @@
"CVN_72": {
"name": "CVN_72",
"coalition": "blue",
"type": "Super Aircraft Carrier",
"type": "Aircraft Carrier",
"era": "Late Cold War",
"label": "CVN-72 Abraham Lincoln",
"shortLabel": "CVN-72",
@@ -45,7 +29,8 @@
"enabled": true,
"acquisitionRange": 50000,
"engagementRange": 25000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
@@ -53,7 +38,7 @@
"CVN_73": {
"name": "CVN_73",
"coalition": "blue",
"type": "Super Aircraft Carrier",
"type": "Aircraft Carrier",
"era": "Late Cold War",
"label": "CVN-73 George Washington",
"shortLabel": "CVN-73",
@@ -62,7 +47,8 @@
"enabled": true,
"acquisitionRange": 50000,
"engagementRange": 25000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
@@ -101,7 +87,8 @@
},
"acquisitionRange": 50000,
"engagementRange": 25000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
@@ -111,13 +98,14 @@
"coalition": "red",
"type": "Aircraft Carrier",
"era": "Modern",
"label": "CV Admiral Kuznetsov(2017)",
"shortLabel": "Admiral Kuznetsov(2017)",
"label": "Admiral Kuznetsov (2017)",
"shortLabel": "Admiral Kuznetsov",
"range": "Medium",
"filename": "",
"enabled": true,
"acquisitionRange": 25000,
"engagementRange": 12000,
"tags": "",
"description": "Admiral Kuznetsov. Conventional STOBAR carrier",
"abilities": "",
"canTargetPoint": false,
@@ -126,8 +114,8 @@
"CastleClass_01": {
"name": "CastleClass_01",
"coalition": "blue",
"type": "Patrol",
"era": "Mid Cold War",
"type": "Combatants",
"era": "Late Cold War",
"label": "HMS Leeds Castle (P-258)",
"shortLabel": "HMS Leeds Castle (P-258)",
"range": "",
@@ -141,7 +129,8 @@
},
"acquisitionRange": 25000,
"engagementRange": 3000,
"description": "HMS Leeds Castle. Smaller. Patrol craft",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -149,7 +138,7 @@
"HandyWind": {
"name": "HandyWind",
"coalition": "blue",
"type": "Cargoship",
"type": "Cargo/Transport",
"era": "Late Cold War",
"label": "Bulker Handy Wind",
"shortLabel": "Bulker Handy Wind",
@@ -176,6 +165,7 @@
},
"acquisitionRange": 0,
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
@@ -184,7 +174,7 @@
"HarborTug": {
"name": "HarborTug",
"coalition": "",
"type": "Tug",
"type": "Cargo/Transport",
"era": "Mid Cold War",
"label": "Harbor Tug",
"shortLabel": "Harbor Tug",
@@ -211,6 +201,7 @@
},
"acquisitionRange": 0,
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
@@ -228,7 +219,8 @@
"enabled": true,
"acquisitionRange": 150000,
"engagementRange": 20000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -481,6 +473,7 @@
},
"acquisitionRange": 19000,
"engagementRange": 4000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
@@ -489,15 +482,16 @@
"Seawise_Giant": {
"name": "Seawise_Giant",
"coalition": "blue",
"type": "Tanker",
"type": "Cargo/Transport",
"era": "Late Cold War",
"label": "Tanker Seawise Giant",
"label": "Seawise Giant (Tanker)",
"shortLabel": "Seawise Giant",
"range": "",
"filename": "",
"enabled": true,
"acquisitionRange": 0,
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
@@ -506,7 +500,7 @@
"Ship_Tilde_Supply": {
"name": "Ship_Tilde_Supply",
"coalition": "blue",
"type": "Transport",
"type": "Cargo/Transport",
"era": "Late Cold War",
"label": "Supply Ship MV Tilde",
"shortLabel": "Supply Ship Tilde",
@@ -515,6 +509,7 @@
"enabled": true,
"acquisitionRange": 0,
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
@@ -532,6 +527,7 @@
"enabled": true,
"acquisitionRange": 50000,
"engagementRange": 25000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
@@ -540,9 +536,9 @@
"TICONDEROG": {
"name": "TICONDEROG",
"coalition": "blue",
"type": "Cruiser",
"type": "Combatants",
"era": "Late Cold War",
"label": "Ticonderoga",
"label": "Ticonderoga Class (Cruiser)",
"shortLabel": "Ticonderoga",
"range": "Medium",
"filename": "",
@@ -611,7 +607,8 @@
},
"acquisitionRange": 150000,
"engagementRange": 100000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -619,9 +616,9 @@
"Type_052B": {
"name": "Type_052B",
"coalition": "red",
"type": "Destroyer",
"type": "Combatants",
"era": "Modern",
"label": "052B DDG-168 Guangzhou",
"label": "Type 52B Guangzhou",
"shortLabel": "Type 52B",
"range": "Short",
"filename": "",
@@ -646,7 +643,8 @@
},
"acquisitionRange": 100000,
"engagementRange": 30000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -654,9 +652,9 @@
"Type_052C": {
"name": "Type_052C",
"coalition": "red",
"type": "Destroyer",
"type": "Combatants",
"era": "Modern",
"label": "052C DDG-171 Haikou",
"label": "Type 52C Haikou (Destroyer)",
"shortLabel": "Type 52C",
"range": "Short",
"filename": "",
@@ -705,17 +703,18 @@
},
"acquisitionRange": 260000,
"engagementRange": 100000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
},
"Type_054A": {
"name": "",
"name": "Type_054A",
"coalition": "red",
"type": "Frigate",
"type": "Combatants",
"era": "Modern",
"label": "054A FFG-538 Yantai",
"label": "Type 54A Yantai (Frigate)",
"shortLabel": "Type 54A",
"range": "Medium",
"filename": "",
@@ -872,15 +871,16 @@
},
"acquisitionRange": 160000,
"engagementRange": 45000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "35nm >50,000ft range"
},
"Type_071": {
"name": "Type_071",
"coalition": "red",
"type": "Transport",
"type": "Cargo/Transport",
"era": "Modern",
"label": "Type 071",
"label": "Type 071 (Transport dock)",
"shortLabel": "Type 071",
"range": "",
"filename": "",
@@ -925,7 +925,8 @@
},
"acquisitionRange": 300000,
"engagementRange": 150000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -935,7 +936,7 @@
"coalition": "red",
"type": "Submarine",
"era": "Modern",
"label": "Type 093",
"label": "Type 093 (Shang)",
"shortLabel": "Type 093",
"range": "",
"filename": "",
@@ -948,6 +949,7 @@
},
"acquisitionRange": 40000,
"engagementRange": 40000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
@@ -956,10 +958,10 @@
"USS_Arleigh_Burke_IIa": {
"name": "USS_Arleigh_Burke_IIa",
"coalition": "blue",
"type": "Destroyer",
"type": "Combatants",
"era": "Late Cold War",
"label": "DDG Arleigh Burke lla",
"shortLabel": "DDG Arleigh Burke",
"label": "Arleigh Burke Class (Destroyer)",
"shortLabel": "Arleigh Burke Class (Destroyer)",
"range": "Medium",
"filename": "",
"enabled": true,
@@ -1064,6 +1066,7 @@
"acquisitionRange": 150000,
"engagementRange": 100000,
"abilities": "",
"tags": "",
"description": "",
"canTargetPoint": true,
"canRearm": false
@@ -1080,7 +1083,8 @@
"enabled": true,
"acquisitionRange": 18000,
"engagementRange": 5000,
"description": "ARA Vienticinco de Mayo. Conventional CATOBAR carrier",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -1089,15 +1093,16 @@
"name": "hms_invincible",
"coalition": "blue",
"type": "Aircraft Carrier",
"era": "Mid Cold War",
"label": "HMS Invincible (R05)",
"era": "Late Cold War",
"label": "HMS Invincible",
"shortLabel": "HMS Invincible",
"range": "",
"filename": "",
"enabled": true,
"acquisitionRange": 100000,
"engagementRange": 74000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -1105,8 +1110,8 @@
"leander-gun-achilles": {
"name": "leander-gun-achilles",
"coalition": "blue",
"type": "Frigate",
"era": "Mid Cold War",
"type": "Combatants",
"era": "Late Cold War",
"label": "HMS Achilles (F12)",
"shortLabel": "HMS Achilles",
"range": "",
@@ -1114,7 +1119,8 @@
"enabled": true,
"acquisitionRange": 180000,
"engagementRange": 8000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -1122,8 +1128,8 @@
"leander-gun-andromeda": {
"name": "leander-gun-andromeda",
"coalition": "blue",
"type": "Frigate",
"era": "Mid Cold War",
"type": "Combatants",
"era": "Late Cold War",
"label": "HMS Andromeda (F57)",
"shortLabel": "HMS Andromeda",
"range": "",
@@ -1131,7 +1137,8 @@
"enabled": true,
"acquisitionRange": 180000,
"engagementRange": 140000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -1139,8 +1146,8 @@
"leander-gun-ariadne": {
"name": "leander-gun-ariadne",
"coalition": "blue",
"type": "Frigate",
"era": "Mid Cold War",
"type": "Combatants",
"era": "Late Cold War",
"label": "HMS Ariadne (F72)",
"shortLabel": "HMS Ariadne",
"range": "",
@@ -1148,7 +1155,8 @@
"enabled": true,
"acquisitionRange": 150000,
"engagementRange": 100000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -1156,8 +1164,8 @@
"leander-gun-condell": {
"name": "leander-gun-condell",
"coalition": "",
"type": "Frigate",
"era": "Mid Cold War",
"type": "Combatants",
"era": "Late Cold War",
"label": "Almirante Condell PFG-06",
"shortLabel": "Almirante Condell",
"range": "",
@@ -1165,7 +1173,8 @@
"enabled": true,
"acquisitionRange": 150000,
"engagementRange": 100000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -1173,8 +1182,8 @@
"leander-gun-lynch": {
"name": "leander-gun-lynch",
"coalition": "",
"type": "Frigate",
"era": "Mid Cold War",
"type": "Combatants",
"era": "Late Cold War",
"label": "CNS Almirante Lynch (PFG-07)",
"shortLabel": "CNS Almirante Lynch",
"range": "",
@@ -1182,7 +1191,8 @@
"enabled": true,
"acquisitionRange": 180000,
"engagementRange": 140000,
"description": "Ship",
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
@@ -1191,7 +1201,7 @@
"name": "santafe",
"coalition": "",
"type": "Submarine",
"era": "Early Cold War",
"era": "WW2",
"label": "ARA Santa Fe S-21",
"shortLabel": "ARA Santa",
"range": "",
@@ -1199,6 +1209,7 @@
"enabled": true,
"acquisitionRange": 0,
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
@@ -1210,36 +1221,21 @@
"era": "",
"label": "Boat Armed Hi-speed",
"shortLabel": "Boat Armed Hi-speed",
"type": "Speedboat",
"type": "Fast Attack Craft",
"enabled": true,
"liveries": {},
"acquisitionRange": 5000,
"engagementRange": 1000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
},
"VINSON": {
"name": "VINSON",
"coalition": "",
"era": "",
"label": "CVN-70 Carl Vinson",
"shortLabel": "CVN-70 Carl Vinson",
"type": "Aircraft Carrier",
"enabled": true,
"liveries": {},
"acquisitionRange": 30000,
"engagementRange": 15000,
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"PERRY": {
"name": "perry",
"name": "PERRY",
"coalition": "blue",
"type": "Frigate",
"type": "Combatants",
"era": "Mid Cold War",
"label": "Oliver H. Perry",
"shortLabel": "Oliver H. Perry",
@@ -1289,13 +1285,18 @@
}
},
"acquisitionRange": 150000,
"engagementRange": 100000
"engagementRange": 100000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"ALBATROS": {
"name": "albatros",
"name": "ALBATROS",
"coalition": "red",
"type": "Frigade",
"era": "Early Cold War",
"type": "Combatants",
"era": "Modern",
"label": "Albatros (Grisha-5)",
"shortLabel": "Albatros",
"range": "",
@@ -1340,25 +1341,35 @@
}
},
"acquisitionRange": 30000,
"engagementRange": 16000
"engagementRange": 16000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"KUZNECOW": {
"name": "kuznecow",
"name": "KUZNECOW",
"coalition": "red",
"type": "Aircraft Carrier",
"era": "Late Cold War",
"label": "Admiral Kuznetsov",
"shortLabel": "Admiral Kuznetsov",
"shortLabel": "AK",
"range": "Medium",
"filename": "",
"enabled": true,
"acquisitionRange": 25000,
"engagementRange": 12000
"engagementRange": 12000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"MOLNIYA": {
"name": "molniya",
"name": "MOLNIYA",
"coalition": "",
"type": "Corvette",
"type": "Combatants",
"era": "Late Cold War",
"label": "Molniya (Tarantul-3)",
"shortLabel": "Molniya",
@@ -1380,12 +1391,17 @@
}
},
"acquisitionRange": 21000,
"engagementRange": 2000
"engagementRange": 2000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"MOSCOW": {
"name": "moscow",
"name": "MOSCOW",
"coalition": "red",
"type": "Cruiser",
"type": "Combatants",
"era": "Late Cold War",
"label": "Moscow",
"shortLabel": "Moscow",
@@ -1411,54 +1427,70 @@
}
},
"acquisitionRange": 160000,
"engagementRange": 75000
"engagementRange": 75000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"NEUSTRASH": {
"name": "neustrash",
"name": "NEUSTRASH",
"coalition": "red",
"type": "Frigate",
"type": "Combatants",
"era": "Late Cold War",
"label": "Neustrashimy",
"shortLabel": "Neustrashimy",
"range": "Short",
"range": "",
"filename": "",
"enabled": true,
"acquisitionRange": 27000,
"engagementRange": 12000
"engagementRange": 12000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"PIOTR": {
"name": "PIOTR",
"coalition": "",
"era": "",
"label": "Battlecruiser 1144.2 Pyotr Velikiy",
"shortLabel": "Battlecruiser 1144.2 Pyotr Velikiy",
"type": "Cruiser",
"coalition": "red",
"era": "Late Cold War",
"label": "Pyotr Velikiy (Battlecruiser)",
"shortLabel": "Pyotr Velikiy",
"type": "Combatants",
"enabled": true,
"liveries": {},
"acquisitionRange": 250000,
"engagementRange": 190000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
"canRearm": false
},
"REZKY": {
"name": "Rezky (Krivak-2)",
"name": "REZKY",
"coalition": "red",
"type": "Frigate",
"era": "Early Cold War",
"type": "Combatants",
"era": "WW2",
"label": "Rezky (Krivak-2)",
"shortLabel": "Rezky",
"range": "Short",
"range": "",
"filename": "",
"enabled": true,
"acquisitionRange": 30000,
"engagementRange": 16000
"engagementRange": 16000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"ELNYA": {
"name": "elnya",
"name": "ELNYA",
"coalition": "red",
"type": "Tanker",
"type": "Cargo/Transport",
"era": "Late Cold War",
"label": "Elnya tanker",
"shortLabel": "Elnya tanker",
@@ -1480,7 +1512,12 @@
}
},
"acquisitionRange": 0,
"engagementRange": 0
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"Dry-cargo ship-2": {
"name": "Dry-cargo ship-2",
@@ -1488,11 +1525,12 @@
"era": "",
"label": "Cargo Ivanov",
"shortLabel": "Cargo Ivanov",
"type": "Cargoship",
"type": "Cargo/Transport",
"enabled": true,
"liveries": {},
"acquisitionRange": 0,
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
@@ -1504,20 +1542,21 @@
"era": "",
"label": "Bulker Yakushev",
"shortLabel": "Bulker Yakushev",
"type": "Cargoship",
"type": "Cargo/Transport",
"enabled": true,
"liveries": {},
"acquisitionRange": 0,
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"ZWEZDNY": {
"name": "zwezdny",
"name": "ZWEZDNY",
"coalition": "",
"type": "Civilian Boat",
"type": "Cargo/Transport",
"era": "Modern",
"label": "Zwezdny",
"shortLabel": "Zwezdny",
@@ -1525,32 +1564,43 @@
"filename": "",
"enabled": true,
"acquisitionRange": 0,
"engagementRange": 0
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"KILO": {
"name": "kilo",
"name": "KILO",
"coalition": "red",
"type": "Submarine",
"era": "Late Cold War",
"era": "Mid Cold War",
"label": "Project 636 Varshavyanka Basic",
"shortLabel": "Varshavyanka Basic",
"range": "Medium",
"filename": "",
"enabled": true,
"acquisitionRange": 0,
"engagementRange": 0
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
"canRearm": false
},
"IMPROVED_KILO": {
"name": "IMPROVED_KILO",
"coalition": "",
"coalition": "red",
"era": "",
"label": "SSK 636 Improved Kilo",
"shortLabel": "SSK 636 Improved Kilo",
"shortLabel": "Kilo",
"type": "Submarine",
"enabled": true,
"liveries": {},
"acquisitionRange": 0,
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
@@ -1561,12 +1611,13 @@
"coalition": "",
"era": "",
"label": "SSK 641B Tango",
"shortLabel": "SSK 641B Tango",
"shortLabel": "Tango",
"type": "Submarine",
"enabled": true,
"liveries": {},
"acquisitionRange": 0,
"engagementRange": 0,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
@@ -1574,8 +1625,8 @@
},
"Forrestal": {
"name": "Forrestal",
"coalition": "",
"era": "",
"coalition": "blue",
"era": "Early Cold War",
"label": "CV-59 Forrestal",
"shortLabel": "CV-59 Forrestal",
"type": "Aircraft Carrier",
@@ -1583,6 +1634,7 @@
"liveries": {},
"acquisitionRange": 50000,
"engagementRange": 25000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": false,
@@ -1590,15 +1642,16 @@
},
"LST_Mk2": {
"name": "LST_Mk2",
"coalition": "",
"era": "",
"coalition": "blue",
"era": "WW2",
"label": "LST Mk.II",
"shortLabel": "LST Mk.II",
"type": "Landing Ship",
"type": "Cargo/Transport",
"enabled": true,
"liveries": {},
"acquisitionRange": 0,
"engagementRange": 4000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
@@ -1606,15 +1659,16 @@
},
"USS_Samuel_Chase": {
"name": "USS_Samuel_Chase",
"coalition": "",
"era": "",
"coalition": "blue",
"era": "WW2",
"label": "LS Samuel Chase",
"shortLabel": "LS Samuel Chase",
"type": "Landing SHip",
"type": "Cargo/Transport",
"enabled": true,
"liveries": {},
"acquisitionRange": 0,
"engagementRange": 7000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
@@ -1622,15 +1676,16 @@
},
"Higgins_boat": {
"name": "Higgins_boat",
"coalition": "",
"era": "",
"coalition": "blue",
"era": "WW2",
"label": "Boat LCVP Higgins",
"shortLabel": "Boat LCVP Higgins",
"type": "Landing Ship",
"type": "Cargo/Transport",
"enabled": true,
"liveries": {},
"acquisitionRange": 3000,
"engagementRange": 1000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
@@ -1638,15 +1693,16 @@
},
"Uboat_VIIC": {
"name": "Uboat_VIIC",
"coalition": "",
"era": "",
"label": "U-boat VIIC U-flak",
"shortLabel": "U-boat VIIC U-flak",
"coalition": "red",
"era": "WW2",
"label": "U-boat VIIC",
"shortLabel": "U-boat",
"type": "Submarine",
"enabled": true,
"liveries": {},
"acquisitionRange": 20000,
"engagementRange": 4000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,
@@ -1654,15 +1710,16 @@
},
"Schnellboot_type_S130": {
"name": "Schnellboot_type_S130",
"coalition": "",
"era": "",
"coalition": "red",
"era": "WW2",
"label": "Boat Schnellboot type S130",
"shortLabel": "Boat Schnellboot type S130",
"type": "Torpedo Boat",
"type": "Fast Attack Craft",
"enabled": true,
"liveries": {},
"acquisitionRange": 10000,
"engagementRange": 4000,
"tags": "",
"description": "",
"abilities": "",
"canTargetPoint": true,

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -62,6 +62,16 @@
height: 180px;
}
#hotgroup-panel {
bottom: 40px;
column-gap: 10px;
display: flex;
left: 50%;
position: absolute;
translate: -50%;
z-index: 9998;
}
#info-popup {
position: absolute;
width: fit-content;

View File

@@ -4,6 +4,8 @@
display: flex;
justify-content: center;
position: relative;
width: 100%;
height: 100%;
}
.airbase-icon[data-coalition="red"] svg * {

View File

@@ -4,6 +4,8 @@
display: flex;
justify-content: center;
position: relative;
width: 100%;
height: 100%;
}
.bullseye-icon[data-coalition="red"] svg * {

View File

@@ -50,6 +50,11 @@
width: var(--unit-width);
}
.unit-icon svg {
height: 100%;
width: 100%;
}
[data-is-selected] .unit-icon::before {
background-color: var(--unit-spotlight-fill);
border-radius: 50%;
@@ -200,12 +205,6 @@
display: flex;
}
@keyframes pulse {
50% {
opacity: 0;
}
}
[data-object|="unit"][data-has-low-fuel] .unit-fuel, [data-object|="unit"][data-has-low-health] .unit-health {
animation: pulse 1.5s linear infinite;
}
@@ -329,6 +328,19 @@
background-image: url("/resources/theme/images/states/awacs.svg");
}
[data-object|="unit"][data-state="miss-on-purpose"] .unit-state {
background-image: url("/resources/theme/images/states/miss-on-purpose.svg");
}
[data-object|="unit"][data-state="scenic-aaa"] .unit-state {
background-image: url("/resources/theme/images/states/scenic-aaa.svg");
}
[data-object|="unit"][data-state="simulate-fire-fight"] .unit-state {
background-image: url("/resources/theme/images/states/simulate-fire-fight.svg");
}
[data-object|="unit"] .unit-health::before {
background-image: url("/resources/theme/images/icons/health.svg");
background-repeat: no-repeat;

View File

@@ -45,6 +45,7 @@
justify-content: space-between;
row-gap: 5px;
width: 100%;
padding: 5px;
}
.contextmenu-advanced-options-toggle,
@@ -60,8 +61,18 @@
.contextmenu-advanced-options-toggle:after,
.contextmenu-metadata-toggle:after {
content: url(/resources/theme/images/icons/chevron-down.svg);
margin: auto;
content: "";
margin-left: auto;
margin-top: auto;
background-image: url(/resources/theme/images/icons/chevron-down.svg);
background-size: 100% 100%;
width: 15px;
height: 15px;
}
.contextmenu-advanced-options-toggle.is-open:after,
.contextmenu-metadata-toggle.is-open:after {
transform: rotate(180deg);
}
.contextmenu-advanced-options-toggle div:first-child,
@@ -241,12 +252,16 @@
}
.unit-label-count-container {
display: flex;
flex-direction: row;
display: grid;
grid-template-columns: 187px 1fr 1fr;
align-items: center;
column-gap: 5px;
}
.unit-label-count-container>*:first-child {
width: 100%;
}
.unit-label-count-container button {
display: flex !important;
flex-direction: row;
@@ -396,84 +411,124 @@
/* Buttons */
#center-map::before {
content: url("/resources/theme/images/icons/arrows-to-eye-solid.svg");
background-image: url("/resources/theme/images/icons/arrows-to-eye-solid.svg");
content: "";
background-size: 20px 20px;
}
#refuel::before {
content: url("/resources/theme/images/icons/fuel.svg");
background-image: url("/resources/theme/images/icons/fuel.svg");
content: "";
background-size: 20px 20px;
}
#attack::before {
content: url("/resources/theme/images/icons/sword.svg");
background-image: url("/resources/theme/images/icons/sword.svg");
content: "";
background-size: 20px 20px;
}
#bomb::before {
content: url("/resources/theme/images/icons/crosshairs-solid.svg");
background-image: url("/resources/theme/images/icons/crosshairs-solid.svg");
content: "";
background-size: 20px 20px;
}
#carpet-bomb::before {
content: url("/resources/theme/images/icons/explosion-solid.svg");
background-image: url("/resources/theme/images/icons/explosion-solid.svg");
content: "";
background-size: 20px 20px;
}
#fire-at-area::before {
content: url("/resources/theme/images/icons/crosshairs-solid.svg");
background-image: url("/resources/theme/images/icons/crosshairs-solid.svg");
content: "";
background-size: 20px 20px;
}
#simulate-fire-fight::before {
content: url("/resources/theme/images/icons/crosshairs-solid.svg");
background-image: url("/resources/theme/images/icons/crosshairs-solid.svg");
content: "";
background-size: 20px 20px;
}
#follow::before {
content: url("/resources/theme/images/icons/follow.svg");
background-image: url("/resources/theme/images/icons/follow.svg");
content: "";
background-size: 20px 20px;
}
#scenic-aaa::before {
content: url("/resources/theme/images/icons/scenic.svg");
background-image: url("/resources/theme/images/icons/scenic.svg");
content: "";
background-size: 20px 20px;
}
#miss-aaa::before {
content: url("/resources/theme/images/icons/miss.svg");
background-image: url("/resources/theme/images/icons/miss.svg");
content: "";
background-size: 20px 20px;
}
#group-ground::before {
content: url("/resources/theme/images/icons/group-ground.svg");
background-image: url("/resources/theme/images/icons/group-ground.svg");
content: "";
background-size: 20px 20px;
}
#group-navy::before {
content: url("/resources/theme/images/icons/group-navy.svg");
background-image: url("/resources/theme/images/icons/group-navy.svg");
content: "";
background-size: 20px 20px;
}
#land-at-point::before {
content: url("/resources/theme/images/icons/land-at-point.svg");
background-image: url("/resources/theme/images/icons/land-at-point.svg");
content: "";
background-size: 20px 20px;
}
#trail::before {
content: url("/resources/theme/images/icons/trail.svg");
background-image: url("/resources/theme/images/icons/trail.svg");
content: "";
background-size: 20px 20px;
}
#echelon-lh::before {
content: url("/resources/theme/images/icons/echelon-lh.svg");
background-image: url("/resources/theme/images/icons/echelon-lh.svg");
content: "";
background-size: 20px 20px;
}
#echelon-rh::before {
content: url("/resources/theme/images/icons/echelon-rh.svg");
background-image: url("/resources/theme/images/icons/echelon-rh.svg");
content: "";
background-size: 20px 20px;
}
#line-abreast-rh::before,
#line-abreast-lh::before {
content: url("/resources/theme/images/icons/line-abreast.svg");
background-image: url("/resources/theme/images/icons/line-abreast.svg");
content: "";
background-size: 20px 20px;
}
#front::before {
content: url("/resources/theme/images/icons/front.svg");
background-image: url("/resources/theme/images/icons/front.svg");
content: "";
background-size: 20px 20px;
}
#diamond::before {
content: url("/resources/theme/images/icons/diamond.svg");
background-image: url("/resources/theme/images/icons/diamond.svg");
content: "";
background-size: 20px 20px;
}
#custom::before {
content: url("/resources/theme/images/icons/custom.svg");
background-image: url("/resources/theme/images/icons/custom.svg");
content: "";
background-size: 20px 20px;
}
#custom-formation-dialog {
@@ -576,6 +631,7 @@
#iads-menu {
row-gap: 10px;
padding: 10px;
}
#coalition-area-contextmenu>div:nth-child(2) {
@@ -592,6 +648,7 @@
flex-direction: column;
justify-content: space-between;
row-gap: 5px;
padding: 20px;
}
.create-iads-button {

View File

@@ -14,6 +14,10 @@
width: fit-content;
}
#app-icon>.ol-select-value {
box-shadow: none;
}
#toolbar-summary {
background-image: url("/images/icon-round.png");
background-position: 20px 22px;
@@ -29,22 +33,69 @@
white-space: nowrap;
}
#toolbar-container>*:nth-child(2)>svg {
display: none;
width: 0px;
height: 0px;
.ol-panel-tab {
align-items: center;
display:flex;
flex-direction: row;
margin-right:6px;
}
.ol-panel-tab svg {
height:24;
width:24px;
}
.ol-panel-tab svg * {
fill:white;
}
.ol-panel-tab span {
font-size:13px;
font-weight:400;
padding:0 6px;
}
#view-label {
margin-left: 5px;
}
#view-label svg {
height: 20px;
width: 20px;
}
#toolbar-container>*:nth-child(3)>svg {
display: none;
}
#unit-visibility-control > div:nth-child(4) {
border-left: 2px solid white;
padding-left: 12px;
}
#unit-visibility-control > div:last-child {
border-right: 2px solid white;
padding-right: 12px;
}
@media (max-width: 1145px) {
#toolbar-container {
flex-direction: column;
align-items: start;
}
#toolbar-container .ol-panel .ol-panel-tab {
margin-right:0;
}
#toolbar-container .ol-panel:hover .ol-panel-tab {
display:none;
}
#toolbar-container .ol-panel-tab span {
display:none;
}
#toolbar-container>*:nth-child(1):not(:hover) {
width: fit-content;
height: fit-content;
@@ -62,10 +113,10 @@
}
#toolbar-container>*:not(:first-child):not(:hover)>svg {
display: block;
width: 24px;
height: 24px;
display: block;
filter: invert();
height: 24px;
width: 24px;
}
#toolbar-container>*:not(:first-child):not(:hover)>*:not(:first-child) {

View File

@@ -50,15 +50,6 @@
animation: pulse 1s infinite;
}
@keyframes pulse {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
#connection-status-panel[data-is-paused] #connection-status-light {
background: var(--accent-amber);
}

View File

@@ -27,7 +27,6 @@
display: block;
}
#log-panel-header-right {
align-items: center;
column-gap: 16px;
@@ -35,6 +34,11 @@
flex-flow: row nowrap;
}
#log-panel-header-right svg {
width: 15px;
height: 15px;
}
#server-status-panel abbr {
text-decoration: none;
}

View File

@@ -2,6 +2,75 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
display: block !important;
}
#roe-buttons-container button,
#reaction-to-threat-buttons-container button,
#emissions-countermeasures-buttons-container button,
#shots-scatter-buttons-container button #shots-intensity-buttons-container button {
align-items: center;
background-color: transparent;
border: 1px solid var(--accent-light-blue);
display: flex;
height: 30px;
justify-content: center;
width: 30px;
}
#reaction-to-threat-buttons-container button:not(:first-child) svg {
width: 150%;
margin: -5px;
}
#unit-control-panel .ol-option-button button {
width: 30px;
height: 30px;
}
#unit-control-panel .ol-option-button svg {
width: 100%;
height: 100%;
}
#unit-control-panel .ol-option-button button.selected {
background-color: white;
border-color: white;
}
#unit-control-panel .ol-option-button button.selected svg * {
fill: var(--background-steel);
stroke: var(--background-steel);
}
#rapid-controls {
display: flex;
flex-direction: column;
row-gap: 5px;
height: fit-content;
width: fit-content;
}
#rapid-controls button {
padding: 4px;
}
#rapid-controls button.pulse {
animation: pulse 1.5s linear infinite;
}
#rapid-controls svg {
height: 20px;
width: 20px;
fill: white;
stroke: white;
}
#rapid-controls button:before {
display: inline-block;
filter: invert(100%);
height: 20px;
width: 20px;
}
#unit-control-panel {
display: flex;
flex-direction: row;
@@ -49,16 +118,12 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
width: fit-content;
}
#unit-control-panel:not(:hover)>*:nth-child(2),
#unit-control-panel:not(:hover)>*:nth-child(2),
#unit-control-panel:not(:hover)>*:nth-child(3) {
display: none;
}
}
#unit-control-panel h3 {
margin-bottom: 8px;
}
#unit-control-panel #selected-units-container {
align-items: left;
border-radius: var(--border-radius-md);
@@ -72,9 +137,9 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
#unit-control-panel #selected-units-container button {
align-items: center;
border-radius: var(--border-radius-md);
border-radius: 20px;
display: flex;
font-size: 11px;
font-size: 13px;
height: 32px;
justify-content: space-between;
margin-right: 5px;
@@ -88,6 +153,7 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
content: attr(data-label);
font-size: 10px;
padding: 4px 6px;
padding-right: 7px;
white-space: nowrap;
width: fit-content;
}
@@ -103,7 +169,7 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
display: block;
overflow: hidden;
padding: 4px;
padding-left: 0;
padding-left: 7px;
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
@@ -139,7 +205,7 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
}
#advanced-settings-dialog>.ol-dialog-content>div input[type="number"] {
width: 60px;
width: 60px;
}
#advanced-settings-dialog hr {
@@ -204,27 +270,34 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
content: "GS";
}
#unit-control-panel .ol-slider-value {
.switch-control .ol-switch {
height: 23px;
width: 40px;
}
.ol-slider-value {
color: var(--accent-light-blue);
cursor: pointer;
font-size: 14px;
font-weight: bold;
}
#unit-control-panel .switch-control {
.switch-control {
align-items: center;
align-content: center;
display: flex;
width: 100%;
justify-content: space-between;
}
#unit-control-panel .switch-control h4 {
margin: 0px;
.switch-control h4 {
margin: 0px !important;
padding: 0px;
display: flex;
align-items: center;
}
#unit-control-panel .switch-control h4 img {
.switch-control h4 img {
height: 15px;
margin-left: 10px;
cursor: pointer;
@@ -232,56 +305,60 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
opacity: 80%;
}
#unit-control-panel .switch-control .ol-switch {
height: 25px;
width: 60px;
}
#unit-control-panel .switch-control .ol-switch-fill {
background-color: var(--accent-light-blue);
}
#unit-control-panel .switch-control .ol-switch-fill::after {
background-color: white;
}
#unit-control-panel .switch-control .ol-switch[data-value="true"]>.ol-switch-fill::before {
content: "YES";
}
#unit-control-panel .switch-control .ol-switch[data-value="false"]>.ol-switch-fill::before {
content: "NO";
}
#operate-as-switch[data-value="true"] .ol-switch-fill {
background-color: var(--accent-light-blue);
}
#operate-as-switch[data-value="false"] .ol-switch-fill {
background-color: var(--primary-red);
}
#operate-as-switch[data-value="true"]>.ol-switch-fill::before {
content: "BLUE" !important;
}
#operate-as-switch[data-value="false"]>.ol-switch-fill::before {
content: "RED" !important;
}
#advanced-settings-div {
position: relative;
column-gap: 5px;
align-items: center;
column-gap: 8px;
display: flex;
height: fit-content;
}
#advanced-settings-div>*:nth-child(2) {
margin-left: auto;
position: relative;
}
#advanced-settings-div>button {
background-color: var(--background-grey);
box-shadow: 0px 2px 5px #000A;
font-size: 13px;
height: 40px;
padding: 0 20px;
}
#delete-options {
font-size: 13px;
}
#delete-options.ol-select>.ol-select-value:after {
content: "";
}
#delete-options.ol-select>.ol-select-value svg {
background-color: transparent;
position: absolute;
right: 2px;
translate: 0 1px;
}
#delete-options.ol-select>.ol-select-value svg * {
fill: var(--primary-red);
}
#delete-options * {
background-color: var(--background-steel);
}
#delete-options.ol-select>.ol-select-value:hover,
#delete-options .ol-select-options>div:not(.hr):hover,
#delete-options .ol-select-options>div:not(.hr):hover button,
#delete-options .ol-select-options>div hr {
background-color: var(--background-grey);
}
#delete-options .ol-select-options>div:first-of-type {
margin-top: 12px;
padding-top: 0;
}
#delete-options .ol-select-options>div:last-of-type {
margin-bottom: 12px;
padding-bottom: 0;
}
#delete-options button {
@@ -291,11 +368,16 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
}
#delete-options button svg {
background-color: transparent;
margin-right: 10px;
width: 18px;
max-height: 18px;
}
#delete-options button svg * {
stroke: red;
}
/* Element visibility control */
#unit-control-panel:not([data-show-categories-tooltip]) #categories-tooltip,
#unit-control-panel:not([data-show-speed-slider]) #speed-slider,
@@ -317,4 +399,4 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
#advanced-settings-dialog:not([data-show-radio]) #radio-options,
#advanced-settings-dialog:not([data-show-air-unit-checkboxes]) .air-unit-checkbox {
display: none;
}
}

View File

@@ -146,12 +146,12 @@
}
#fuel-percentage::before {
content: url("/resources/theme/images/icons/fuel.svg");
content: "";
background-image: url("/resources/theme/images/icons/fuel.svg");
background-size: 16px 16px;
display: inline-block;
filter: invert(100%);
height: 16px;
margin-right: 6px;
width: 16px;
}
#fuel-percentage::after {

View File

@@ -23,7 +23,7 @@ body {
}
.hidden-cursor {
cursor: none !important;
/*cursor: none !important;*/
}
.hidden-cursor * {
@@ -72,6 +72,18 @@ form {
padding: 0;
}
button svg.fill-coalition * {
fill: var(--primary-neutral) !important;
}
button svg.fill-coalition[data-coalition="blue"] * {
fill: var(--primary-blue) !important;
}
button svg.fill-coalition[data-coalition="red"] * {
fill: var(--primary-red) !important;
}
.pill {
background-color: var(--background-steel);
border-radius: 999px;
@@ -194,9 +206,21 @@ form {
}
.ol-select:not(.ol-select-image)>.ol-select-value:after {
content: url("/resources/theme/images/icons/chevron-down.svg");
background-image: url("/resources/theme/images/icons/chevron-down.svg");
content: "";
position: absolute;
right: 10px;
width: 15px;
height: 15px;
background-size: 100% 100%;
}
.ol-select:not(.ol-select-image)>.ol-select-value.ol-select-warning:after {
background-image: url("/resources/theme/images/icons/chevron-down-warning.svg") !important;
}
.ol-select.is-open:not(.ol-select-image)>.ol-select-value:after {
transform: rotate(180deg);
}
.ol-select>.ol-select-options {
@@ -232,7 +256,6 @@ form {
.ol-select>.ol-select-options>div {
background-color: var(--background-grey);
box-shadow: 0 4px 4px rgba(0, 0, 0, 0.25);
display: flex;
justify-content: left;
padding: 2px 15px;
@@ -373,7 +396,7 @@ button.ol-button-warning>svg:first-child {
}
nav.ol-panel {
column-gap: 20px;
column-gap: 10px;
display: flex;
flex-direction: row;
height: 58px;
@@ -385,8 +408,7 @@ nav.ol-panel> :last-child {
.ol-panel .ol-group {
align-items: center;
border-radius: var(--border-radius-sm);
column-gap: 10px;
column-gap: 12px;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
@@ -427,6 +449,7 @@ nav.ol-panel> :last-child {
-webkit-filter: invert(100%);
height: 16px;
width: 16px;
background-size: 100% 100%;
}
.ol-panel .ol-group-button-toggle button.off::before {
@@ -653,13 +676,13 @@ nav.ol-panel> :last-child {
}
.ol-navbar-buttons-group button.off svg * {
fill: white !important;
stroke: white !important;
fill: white;
stroke: white;
}
.ol-navbar-buttons-group button svg * {
fill: var(--background-steel) !important;
stroke: var(--background-steel) !important;
fill: var(--background-steel);
stroke: var(--background-steel);
}
.ol-navbar-buttons-group .protectable button:first-of-type {
@@ -778,6 +801,7 @@ nav.ol-panel> :last-child {
width: 70%;
max-width: 1200px;
z-index: 999999;
min-width: 500px;
}
@media (min-width: 1700px) {
@@ -828,11 +852,11 @@ nav.ol-panel> :last-child {
justify-content: space-between;
min-height: 75px;
text-indent: 85px;
row-gap: 5px;
}
#splash-content #app-summary>* {
height: fit-content;
line-height: 25px;
padding: 2px;
white-space: nowrap;
width: fit-content;
@@ -840,10 +864,23 @@ nav.ol-panel> :last-child {
#splash-content .app-version {
font-size: 11px;
padding: 0px;
}
#splash-content .new-version {
animation: pulse 1.5s linear infinite;
}
#splash-content .app-version:first-of-type {
margin-top: auto;
}
#splash-content #legal-stuff {
width: 100%;
width: 120%;
text-wrap: wrap;
max-height: 250px;
overflow-x: hidden;
padding-right: 10px;
}
#splash-content #legal-stuff h5 {
@@ -853,11 +890,11 @@ nav.ol-panel> :last-child {
#splash-content #legal-stuff p {
color: #FFF7;
font-size: 10px;
width: 120%;
width: 100%;
}
@media (max-width: 1700px) {
#splash-content #legal-stuff p {
#splash-content #legal-stuff {
width: 100%;
}
}
@@ -876,6 +913,37 @@ nav.ol-panel> :last-child {
z-index: 99999;
}
#loading-screen {
display: flex;
background-image: linear-gradient(var(--background-steel), var(--background-grey));
height: 100%;
left: 0px;
position: fixed;
top: 0px;
width: 100%;
z-index: 999999;
justify-content: center;
align-items: center;
flex-direction: column;
row-gap: 20px;
}
#loading-screen img {
height: 300px;
width: 300px;
}
#loading-screen div {
color: white;
font-size: 18px;
animation: pulse 3s linear infinite;
}
.fade-out {
opacity: 0%;
transition: opacity 1s;
}
#authentication-form {
align-items: end;
column-gap: 10px;
@@ -926,16 +994,6 @@ nav.ol-panel> :last-child {
}
}
#hotgroup-panel {
bottom: 40px;
column-gap: 10px;
display: flex;
left: 50%;
position: absolute;
translate: -50%;
z-index: 9999;
}
#hotgroup-panel>div {
align-items: center;
background-color: var(--background-steel);
@@ -1254,11 +1312,22 @@ dl.ol-data-grid dd {
margin: 4px 0;
}
.ol-dialog-content table th {
background-color: var(--background-grey);
color:white;
font-size:14px;
font-weight: normal;
}
.ol-dialog-content table tbody th {
text-align: left;
}
.ol-dialog-footer {
align-content: center;
border-top: 1px solid var(--background-grey);
display: flex;
justify-content: center;
justify-content: flex-end;
padding-top: 15px;
row-gap: 10px;
}
@@ -1292,6 +1361,11 @@ dl.ol-data-grid dd {
height: 16px;
margin-right: 10px;
width: 16px;
background-size: 100% 100%;
}
.ol-checkbox input[type="checkbox"]:disabled:before {
opacity: 10%;
}
.ol-checkbox input[type="checkbox"]:checked::before {
@@ -1424,20 +1498,54 @@ input[type=number]::-webkit-outer-spin-button {
transform: translateX(calc((var(--width) - var(--height)) * 0.5));
}
.ol-contexmenu-panel {
padding: 20px;
.switch-control.yes-no .ol-switch[data-value="true"] .ol-switch-fill {
background-color: var(--accent-light-blue);
}
.ol-coalition-switch[data-value="false"]>.ol-switch-fill {
background-color: var(--primary-blue);
.switch-control.yes-no .ol-switch[data-value="false"] .ol-switch-fill {
background-color: var(--ol-switch-off);
}
.ol-coalition-switch[data-value="true"]>.ol-switch-fill {
background-color: var(--primary-red);
.switch-control.yes-no .ol-switch[data-value="undefined"] .ol-switch-fill {
background-color: var(--ol-switch-undefined);
}
.ol-coalition-switch[data-value="undefined"]>.ol-switch-fill {
background-color: var(--primary-neutral);
.switch-control.coalition .ol-switch>.ol-switch-fill::before,
.switch-control.yes-no .ol-switch>.ol-switch-fill::before {
translate:-100% 0;
transform: none;
}
.switch-control.yes-no .ol-switch[data-value="true"]>.ol-switch-fill::before {
content: "YES";
}
.switch-control.yes-no .ol-switch[data-value="false"]>.ol-switch-fill::before {
content: "NO";
}
.switch-control.coalition [data-value="true"] .ol-switch-fill {
background-color: var(--primary-blue);
}
.switch-control.coalition [data-value="false"] .ol-switch-fill {
background-color: var(--primary-red);
}
.switch-control.coalition [data-value="undefined"] .ol-switch-fill {
background-color: var(--primary-neutral);
}
.switch-control.coalition [data-value="true"] .ol-switch-fill::before {
content: "BLUE";
}
.switch-control.coalition [data-value="false"] .ol-switch-fill::before {
content: "RED";
}
.switch-control.no-label [data-value] .ol-switch-fill::before {
content:"";
}
.ol-context-menu>ul {
@@ -1461,7 +1569,7 @@ input[type=number]::-webkit-outer-spin-button {
width: 100%;
}
.ol-contexmenu-button {
.ol-context-menu-button {
border: none;
border-radius: 0px;
height: 48px;
@@ -1470,23 +1578,23 @@ input[type=number]::-webkit-outer-spin-button {
width: 48px;
}
.ol-contexmenu-button:last-of-type {
.ol-context-menu-button:last-of-type {
border-bottom-right-radius: var(--border-radius-sm);
border-top-right-radius: var(--border-radius-sm);
}
[data-coalition="blue"].ol-contexmenu-button:hover,
[data-coalition="blue"].ol-contexmenu-button.is-open {
[data-coalition="blue"].ol-context-menu-button:hover,
[data-coalition="blue"].ol-context-menu-button.is-open {
background-color: var(--primary-blue)
}
[data-coalition="red"].ol-contexmenu-button:hover,
[data-coalition="red"].ol-contexmenu-button.is-open {
[data-coalition="red"].ol-context-menu-button:hover,
[data-coalition="red"].ol-context-menu-button.is-open {
background-color: var(--primary-red)
}
[data-coalition="neutral"].ol-contexmenu-button:hover,
[data-coalition="neutral"].ol-contexmenu-button.is-open {
[data-coalition="neutral"].ol-context-menu-button:hover,
[data-coalition="neutral"].ol-context-menu-button.is-open {
background-color: var(--primary-neutral)
}
@@ -1497,6 +1605,24 @@ input[type=number]::-webkit-outer-spin-button {
fill: lightgray;
}
#map-visibility-options .ol-select-options .ol-checkbox {
font-size:13px;
font-weight:400;
padding:6px 15px;
}
#map-visibility-options .ol-select-options .ol-checkbox:first-of-type {
padding-top:12px;
}
#map-visibility-options .ol-select-options .ol-checkbox:last-of-type {
padding-bottom:12px;
}
#map-visibility-options .ol-select-options .ol-checkbox label:hover span {
text-decoration: underline;
}
.ol-log-entry:first-of-type {
border-top: 1px solid #FFFFFF44;
}
@@ -1504,3 +1630,93 @@ input[type=number]::-webkit-outer-spin-button {
.ol-log-entry {
border-bottom: 1px solid #FFFFFF44;
}
.file-import-export {
max-width: 500px;
}
.file-import-export .ol-dialog-content {
display:flex;
flex-direction: column;
justify-content: center;
}
.file-import-export p {
background-color: var(--background-grey);
border-left:6px solid var(--secondary-blue-text);
padding:12px;
}
.file-import-export th {
padding:4px 6px;
}
.file-import-export tr td:first-child {
text-align: left;
}
.file-import-export td {
color:white;
text-align: center;
}
.file-import-export .ol-checkbox {
display:flex;
justify-content: center;
}
.file-import-export .ol-checkbox input::before {
margin-right: 0;
}
.file-import-export .ol-checkbox span {
display:none;
}
.file-import-export button.start-transfer {
background-color: var(--secondary-blue-text);
border-color: var(--secondary-blue-text);
}
.file-import-export .export-filename-container {
display: flex;
column-gap: 15px;
width: 100%;
align-items: center;
padding: 10px 0px;
color: white;
font-size: 14px;
}
.file-import-export .export-filename-container input {
width: 100%;
background-color: var(--background-grey);
appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
border-style: solid;
border: 1px solid var(--background-steel);
color: white;
font-size: 14px;
padding: 4px;
}
.file-import-export .export-filename-container img {
height: 16px;
width: 16px;
filter: invert(100%);
margin-left: -31px;
transform: translateX(-15px);
pointer-events: none;
}
.file-import-export .ol-dialog-footer button:first-of-type{
margin-left: auto;
}
@keyframes pulse {
50% {
opacity: 0;
}
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-352a96 96 0 1 1 0 192 96 96 0 1 1 0-192z"/></svg>

After

Width:  |  Height:  |  Size: 346 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="14" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M64 32C64 14.3 49.7 0 32 0S0 14.3 0 32V64 368 480c0 17.7 14.3 32 32 32s32-14.3 32-32V352l64.3-16.1c41.1-10.3 84.6-5.5 122.5 13.4c44.2 22.1 95.5 24.8 141.7 7.4l34.7-13c12.5-4.7 20.8-16.6 20.8-30V66.1c0-23-24.2-38-44.8-27.7l-9.6 4.8c-46.3 23.2-100.8 23.2-147.1 0c-35.1-17.6-75.4-22-113.5-12.5L64 48V32z"/></svg>

After

Width:  |  Height:  |  Size: 554 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M0 224.2C0 100.6 100.2 0 224 0h24c95.2 0 181.2 69.3 197.3 160.2c2.3 13 6.8 25.7 15.1 36l42 52.6c6.2 7.8 9.6 17.4 9.6 27.4c0 24.2-19.6 43.8-43.8 43.8H448v64c0 35.3-28.7 64-64 64H320v32c0 17.7-14.3 32-32 32H96c-17.7 0-32-14.3-32-32V407.3c0-16.7-6.9-32.5-17.1-45.8C16.6 322.4 0 274.1 0 224.2zM224 64c-8.8 0-16 7.2-16 16c0 33-39.9 49.5-63.2 26.2c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6C145.5 152.1 129 192 96 192c-8.8 0-16 7.2-16 16s7.2 16 16 16c33 0 49.5 39.9 26.2 63.2c-6.2 6.2-6.2 16.4 0 22.6s16.4 6.2 22.6 0C168.1 286.5 208 303 208 336c0 8.8 7.2 16 16 16s16-7.2 16-16c0-33 39.9-49.5 63.2-26.2c6.2 6.2 16.4 6.2 22.6 0s6.2-16.4 0-22.6C302.5 263.9 319 224 352 224c8.8 0 16-7.2 16-16s-7.2-16-16-16c-33 0-49.5-39.9-26.2-63.2c6.2-6.2 6.2-16.4 0-22.6s-16.4-6.2-22.6 0C279.9 129.5 240 113 240 80c0-8.8-7.2-16-16-16zm-24 96a24 24 0 1 1 0 48 24 24 0 1 1 0-48zm40 80a16 16 0 1 1 32 0 16 16 0 1 1 -32 0z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M256 0c4.6 0 9.2 1 13.4 2.9L457.7 82.8c22 9.3 38.4 31 38.3 57.2c-.5 99.2-41.3 280.7-213.6 363.2c-16.7 8-36.1 8-52.8 0C57.3 420.7 16.5 239.2 16 140c-.1-26.2 16.3-47.9 38.3-57.2L242.7 2.9C246.8 1 251.4 0 256 0zm0 66.8V444.8C394 378 431.1 230.1 432 141.4L256 66.8l0 0z"/></svg>

After

Width:  |  Height:  |  Size: 519 B

View File

@@ -0,0 +1,3 @@
<svg width="14" height="8" viewBox="0 0 14 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.37109 7.37891C6.69922 7.73438 7.27344 7.73438 7.60156 7.37891L12.8516 2.12891C13.207 1.80078 13.207 1.22656 12.8516 0.898438C12.5234 0.542969 11.9492 0.542969 11.6211 0.898438L7 5.51953L2.35156 0.898438C2.02344 0.542969 1.44922 0.542969 1.12109 0.898438C0.765625 1.22656 0.765625 1.80078 1.12109 2.12891L6.37109 7.37891Z" fill="#ff5858"/>
</svg>

After

Width:  |  Height:  |  Size: 452 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM144 256a144 144 0 1 1 288 0 144 144 0 1 1 -288 0zm144-64c0 35.3-28.7 64-64 64c-7.1 0-13.9-1.2-20.3-3.3c-5.5-1.8-11.9 1.6-11.7 7.4c.3 6.9 1.3 13.8 3.2 20.7c13.7 51.2 66.4 81.6 117.6 67.9s81.6-66.4 67.9-117.6c-11.1-41.5-47.8-69.4-88.6-71.1c-5.8-.2-9.2 6.1-7.4 11.7c2.1 6.4 3.3 13.2 3.3 20.3z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="18" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM144 256a144 144 0 1 1 288 0 144 144 0 1 1 -288 0zm144-64c0 35.3-28.7 64-64 64c-7.1 0-13.9-1.2-20.3-3.3c-5.5-1.8-11.9 1.6-11.7 7.4c.3 6.9 1.3 13.8 3.2 20.7c13.7 51.2 66.4 81.6 117.6 67.9s81.6-66.4 67.9-117.6c-11.1-41.5-47.8-69.4-88.6-71.1c-5.8-.2-9.2 6.1-7.4 11.7c2.1 6.4 3.3 13.2 3.3 20.3z"/></svg>

Before

Width:  |  Height:  |  Size: 820 B

After

Width:  |  Height:  |  Size: 826 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Pro 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M64 64C28.7 64 0 92.7 0 128V384c0 35.3 28.7 64 64 64H512c35.3 0 64-28.7 64-64V128c0-35.3-28.7-64-64-64H64zm16 64h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16zM64 240c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V240zm16 80h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V336c0-8.8 7.2-16 16-16zm80-176c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H176c-8.8 0-16-7.2-16-16V144zm16 80h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H176c-8.8 0-16-7.2-16-16V240c0-8.8 7.2-16 16-16zM160 336c0-8.8 7.2-16 16-16H400c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H176c-8.8 0-16-7.2-16-16V336zM272 128h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H272c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16zM256 240c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H272c-8.8 0-16-7.2-16-16V240zM368 128h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H368c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16zM352 240c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H368c-8.8 0-16-7.2-16-16V240zM464 128h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H464c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16zM448 240c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H464c-8.8 0-16-7.2-16-16V240zm16 80h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H464c-8.8 0-16-7.2-16-16V336c0-8.8 7.2-16 16-16z"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="19"
height="15"
viewBox="0 0 19 15"
fill="none"
version="1.1"
id="svg6"
sodipodi:docname="miss-on-purpose.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xml:space="preserve"
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="defs10" /><sodipodi:namedview
id="namedview8"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="39.220856"
inkscape:cx="17.37596"
inkscape:cy="10.759582"
inkscape:window-width="2560"
inkscape:window-height="1377"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg6"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1" /><g
id="g139-7"
transform="matrix(0.02244338,0,0,0.02244338,3.6322523,1.8724426)"
style="fill:none;fill-opacity:1;stroke:#262626;stroke-width:180.346;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"><path
d="m 376.91839,231.41639 c -1.31238,-3.09721 -4.35709,-5.09202 -7.71677,-5.09202 -3.35968,0 -6.40439,1.99481 -7.71677,5.09202 l -23.1503,54.01737 c -1.78483,4.1996 -2.72974,8.66167 -2.72974,13.22874 v 40.42116 l -75.59281,44.0958 v -9.86906 c 0,-6.98183 -5.61697,-12.5988 -12.5988,-12.5988 -6.98184,0 -12.59881,5.61697 -12.59881,12.5988 v 29.39721 16.7984 12.5988 c 0,6.98184 5.61697,12.59881 12.59881,12.59881 6.98183,0 12.5988,-5.61697 12.5988,-12.59881 v -4.1996 h 75.59281 v 17.16587 l -30.70958,26.92994 c -1.83732,1.57485 -2.88722,3.88463 -2.88722,6.2994 v 8.3992 c 0,4.61956 3.77964,8.39921 8.3992,8.39921 h 50.39521 v -33.59681 c 0,-4.61956 3.77964,-8.3992 8.3992,-8.3992 4.61956,0 8.3992,3.77964 8.3992,8.3992 v 33.59681 h 50.39521 c 4.61956,0 8.3992,-3.77965 8.3992,-8.39921 v -8.3992 c 0,-2.41477 -1.0499,-4.72455 -2.88722,-6.2994 l -30.70958,-26.92994 v -17.16587 h 75.59281 v 4.1996 c 0,6.98184 5.61697,12.59881 12.5988,12.59881 6.98184,0 12.59881,-5.61697 12.59881,-12.59881 v -12.5988 -16.7984 -29.39721 c 0,-6.98183 -5.61697,-12.5988 -12.59881,-12.5988 -6.98183,0 -12.5988,5.61697 -12.5988,12.5988 v 9.86906 l -75.59281,-44.0958 V 298.6625 c 0,-4.56707 -0.94491,-9.02914 -2.72974,-13.22874 z"
id="path1154-3"
style="fill:none;fill-opacity:1;stroke:#262626;stroke-width:180.346;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" /><path
d="m 184.66044,8.5185895 c 12.17907,0 22.01865,9.8395825 22.01865,22.0186445 v 7.15606 C 271.15243,47.257643 322.07055,98.244571 331.6349,162.6491 h 7.15606 c 12.17906,0 22.01864,9.83958 22.01864,22.01864 0,12.17907 -9.83958,22.01865 -22.01864,22.01865 h -7.15606 c -9.56435,64.47334 -60.55128,115.39146 -124.95581,124.95581 v 7.15606 c 0,12.17906 -9.83958,22.01864 -22.01865,22.01864 -12.17906,0 -22.01864,-9.83958 -22.01864,-22.01864 V 331.6422 C 98.168455,322.07785 47.250339,271.15973 37.68599,206.68639 h -7.156059 c -12.179063,0 -22.0186444,-9.83958 -22.0186444,-22.01865 0,-12.17906 9.8395814,-22.01864 22.0186444,-22.01864 H 37.68599 C 47.250339,98.175761 98.168455,47.257643 162.6418,37.693294 v -7.15606 c 0,-12.179062 9.83958,-22.0186445 22.01864,-22.0186445 z M 82.411363,206.68639 c 8.601033,40.11522 40.184027,71.6294 80.230437,80.23043 v -14.1745 c 0,-12.17906 9.83958,-22.01864 22.01864,-22.01864 12.17907,0 22.01865,9.83958 22.01865,22.01864 v 14.1745 c 40.11522,-8.60103 71.6294,-40.18402 80.23044,-80.23043 h -14.17451 c -12.17906,0 -22.01864,-9.83958 -22.01864,-22.01865 0,-12.17906 9.83958,-22.01864 22.01864,-22.01864 h 14.17451 C 278.30849,122.53388 246.79431,91.019701 206.67909,82.418665 v 14.174506 c 0,12.179059 -9.83958,22.018639 -22.01865,22.018639 -12.17906,0 -22.01864,-9.83958 -22.01864,-22.018639 V 82.418665 C 122.52658,91.019701 91.012396,122.53388 82.411363,162.6491 h 14.174502 c 12.179065,0 22.018645,9.83958 22.018645,22.01864 0,12.17907 -9.83958,22.01865 -22.018645,22.01865 z M 184.66044,162.6491 a 22.018645,22.018645 0 1 1 0,44.03729 22.018645,22.018645 0 1 1 0,-44.03729 z"
id="path2-1"
style="fill:none;fill-opacity:1;stroke:#262626;stroke-width:180.346;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" /></g><g
id="g139"
transform="matrix(0.02244338,0,0,0.02244338,3.6322523,1.8724426)"
style="fill:#ffffff;fill-opacity:1"><path
d="m 376.91839,231.41639 c -1.31238,-3.09721 -4.35709,-5.09202 -7.71677,-5.09202 -3.35968,0 -6.40439,1.99481 -7.71677,5.09202 l -23.1503,54.01737 c -1.78483,4.1996 -2.72974,8.66167 -2.72974,13.22874 v 40.42116 l -75.59281,44.0958 v -9.86906 c 0,-6.98183 -5.61697,-12.5988 -12.5988,-12.5988 -6.98184,0 -12.59881,5.61697 -12.59881,12.5988 v 29.39721 16.7984 12.5988 c 0,6.98184 5.61697,12.59881 12.59881,12.59881 6.98183,0 12.5988,-5.61697 12.5988,-12.59881 v -4.1996 h 75.59281 v 17.16587 l -30.70958,26.92994 c -1.83732,1.57485 -2.88722,3.88463 -2.88722,6.2994 v 8.3992 c 0,4.61956 3.77964,8.39921 8.3992,8.39921 h 50.39521 v -33.59681 c 0,-4.61956 3.77964,-8.3992 8.3992,-8.3992 4.61956,0 8.3992,3.77964 8.3992,8.3992 v 33.59681 h 50.39521 c 4.61956,0 8.3992,-3.77965 8.3992,-8.39921 v -8.3992 c 0,-2.41477 -1.0499,-4.72455 -2.88722,-6.2994 l -30.70958,-26.92994 v -17.16587 h 75.59281 v 4.1996 c 0,6.98184 5.61697,12.59881 12.5988,12.59881 6.98184,0 12.59881,-5.61697 12.59881,-12.59881 v -12.5988 -16.7984 -29.39721 c 0,-6.98183 -5.61697,-12.5988 -12.59881,-12.5988 -6.98183,0 -12.5988,5.61697 -12.5988,12.5988 v 9.86906 l -75.59281,-44.0958 V 298.6625 c 0,-4.56707 -0.94491,-9.02914 -2.72974,-13.22874 z"
id="path1154"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.52495;stroke-opacity:1" /><path
d="m 184.66044,8.5185895 c 12.17907,0 22.01865,9.8395825 22.01865,22.0186445 v 7.15606 C 271.15243,47.257643 322.07055,98.244571 331.6349,162.6491 h 7.15606 c 12.17906,0 22.01864,9.83958 22.01864,22.01864 0,12.17907 -9.83958,22.01865 -22.01864,22.01865 h -7.15606 c -9.56435,64.47334 -60.55128,115.39146 -124.95581,124.95581 v 7.15606 c 0,12.17906 -9.83958,22.01864 -22.01865,22.01864 -12.17906,0 -22.01864,-9.83958 -22.01864,-22.01864 V 331.6422 C 98.168455,322.07785 47.250339,271.15973 37.68599,206.68639 h -7.156059 c -12.179063,0 -22.0186444,-9.83958 -22.0186444,-22.01865 0,-12.17906 9.8395814,-22.01864 22.0186444,-22.01864 H 37.68599 C 47.250339,98.175761 98.168455,47.257643 162.6418,37.693294 v -7.15606 c 0,-12.179062 9.83958,-22.0186445 22.01864,-22.0186445 z M 82.411363,206.68639 c 8.601033,40.11522 40.184027,71.6294 80.230437,80.23043 v -14.1745 c 0,-12.17906 9.83958,-22.01864 22.01864,-22.01864 12.17907,0 22.01865,9.83958 22.01865,22.01864 v 14.1745 c 40.11522,-8.60103 71.6294,-40.18402 80.23044,-80.23043 h -14.17451 c -12.17906,0 -22.01864,-9.83958 -22.01864,-22.01865 0,-12.17906 9.83958,-22.01864 22.01864,-22.01864 h 14.17451 C 278.30849,122.53388 246.79431,91.019701 206.67909,82.418665 v 14.174506 c 0,12.179059 -9.83958,22.018639 -22.01865,22.018639 -12.17906,0 -22.01864,-9.83958 -22.01864,-22.018639 V 82.418665 C 122.52658,91.019701 91.012396,122.53388 82.411363,162.6491 h 14.174502 c 12.179065,0 22.018645,9.83958 22.018645,22.01864 0,12.17907 -9.83958,22.01865 -22.018645,22.01865 z M 184.66044,162.6491 a 22.018645,22.018645 0 1 1 0,44.03729 22.018645,22.018645 0 1 1 0,-44.03729 z"
id="path2"
style="fill:#ffffff;fill-opacity:1;stroke-width:0.688083" /></g></svg>

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="19"
height="15"
viewBox="0 0 19 15"
fill="none"
version="1.1"
id="svg6"
sodipodi:docname="scenic-aaa.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xml:space="preserve"
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="defs10" /><sodipodi:namedview
id="namedview8"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="55.466666"
inkscape:cx="7.7253607"
inkscape:cy="7.3737982"
inkscape:window-width="2560"
inkscape:window-height="1377"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg6"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1" /><g
id="g6370-3"
transform="matrix(0.02459887,0,0,0.02459887,3.3277353,0.85717408)"
style="stroke:#262626;stroke-width:139.23403799;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"><path
d="m 376.91839,231.41639 c -1.31238,-3.09721 -4.35709,-5.09202 -7.71677,-5.09202 -3.35968,0 -6.40439,1.99481 -7.71677,5.09202 l -23.1503,54.01737 c -1.78483,4.1996 -2.72974,8.66167 -2.72974,13.22874 v 40.42116 l -75.59281,44.0958 v -9.86906 c 0,-6.98183 -5.61697,-12.5988 -12.5988,-12.5988 -6.98184,0 -12.59881,5.61697 -12.59881,12.5988 v 29.39721 16.7984 12.5988 c 0,6.98184 5.61697,12.59881 12.59881,12.59881 6.98183,0 12.5988,-5.61697 12.5988,-12.59881 v -4.1996 h 75.59281 v 17.16587 l -30.70958,26.92994 c -1.83732,1.57485 -2.88722,3.88463 -2.88722,6.2994 v 8.3992 c 0,4.61956 3.77964,8.39921 8.3992,8.39921 h 50.39521 v -33.59681 c 0,-4.61956 3.77964,-8.3992 8.3992,-8.3992 4.61956,0 8.3992,3.77964 8.3992,8.3992 v 33.59681 h 50.39521 c 4.61956,0 8.3992,-3.77965 8.3992,-8.39921 v -8.3992 c 0,-2.41477 -1.0499,-4.72455 -2.88722,-6.2994 l -30.70958,-26.92994 v -17.16587 h 75.59281 v 4.1996 c 0,6.98184 5.61697,12.59881 12.5988,12.59881 6.98184,0 12.59881,-5.61697 12.59881,-12.59881 v -12.5988 -16.7984 -29.39721 c 0,-6.98183 -5.61697,-12.5988 -12.59881,-12.5988 -6.98183,0 -12.5988,5.61697 -12.5988,12.5988 v 9.86906 l -75.59281,-44.0958 V 298.6625 c 0,-4.56707 -0.94491,-9.02914 -2.72974,-13.22874 z"
id="path1154-7-0"
style="fill:#000000;fill-opacity:1;stroke:#262626;stroke-width:139.23403799;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" /><path
style="fill:none;stroke:#262626;stroke-width:139.23403799;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 90.329008,471.87874 294.83388,138.74536"
id="path9928-4"
sodipodi:nodetypes="cc" /><path
style="fill:none;stroke:#262626;stroke-width:139.23403799;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 37.215553,461.40058 33.602384,51.668198"
id="path9928-3-3"
sodipodi:nodetypes="cc" /><path
style="fill:none;stroke:#262626;stroke-width:139.23403799;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 61.785043,466.45901 134.77088,205.58883"
id="path9928-31-1"
sodipodi:nodetypes="cc" /><path
d="m 97.776883,46.496287 c -2.69052,-1.821707 -6.27788,-1.485392 -8.576032,0.784735 -2.298159,2.270126 -2.634474,5.885513 -0.784735,8.548007 l 31.389404,45.766871 -27.914144,9.08052 c -2.774607,0.89684 -4.652369,3.47525 -4.652369,6.38998 0,2.91473 1.877762,5.49315 4.652369,6.38999 l 28.895064,9.36076 -14.82589,28.19441 c -1.37329,2.60645 -0.89684,5.80143 1.20513,7.87538 2.10197,2.07394 5.26893,2.57842 7.87538,1.20513 l 28.19442,-14.82589 9.36076,28.89507 c 0.89684,2.7746 3.47525,4.65236 6.38998,4.65236 2.91474,0 5.49316,-1.87776 6.39,-4.65236 l 9.36076,-28.89507 28.19441,14.82589 c 2.60645,1.37329 5.80144,0.89684 7.87539,-1.20513 2.07393,-2.10197 2.57841,-5.26893 1.20512,-7.87538 l -14.82589,-28.19441 28.89507,-9.36076 c 2.7746,-0.89684 4.65236,-3.47526 4.65236,-6.38999 0,-2.91473 -1.87776,-5.49314 -4.65236,-6.38998 l -29.84797,-9.66907 7.20276,-19.730481 c 0.89684,-2.466311 0.28026,-5.212883 -1.56947,-7.062617 -1.84974,-1.849732 -4.59631,-2.46631 -7.06262,-1.56947 l -19.73049,7.202748 -9.69707,-29.875988 c -0.89684,-2.7746 -3.47526,-4.652359 -6.39,-4.652359 -2.91473,0 -5.49314,1.877759 -6.38998,4.652359 l -9.05248,27.91415 z"
id="path10424-8"
style="stroke:#262626;stroke-width:139.23403799;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1" /></g><g
id="g6370"
transform="matrix(0.02459887,0,0,0.02459887,3.3277353,0.85717408)"
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-opacity:1"><path
d="m 376.91839,231.41639 c -1.31238,-3.09721 -4.35709,-5.09202 -7.71677,-5.09202 -3.35968,0 -6.40439,1.99481 -7.71677,5.09202 l -23.1503,54.01737 c -1.78483,4.1996 -2.72974,8.66167 -2.72974,13.22874 v 40.42116 l -75.59281,44.0958 v -9.86906 c 0,-6.98183 -5.61697,-12.5988 -12.5988,-12.5988 -6.98184,0 -12.59881,5.61697 -12.59881,12.5988 v 29.39721 16.7984 12.5988 c 0,6.98184 5.61697,12.59881 12.59881,12.59881 6.98183,0 12.5988,-5.61697 12.5988,-12.59881 v -4.1996 h 75.59281 v 17.16587 l -30.70958,26.92994 c -1.83732,1.57485 -2.88722,3.88463 -2.88722,6.2994 v 8.3992 c 0,4.61956 3.77964,8.39921 8.3992,8.39921 h 50.39521 v -33.59681 c 0,-4.61956 3.77964,-8.3992 8.3992,-8.3992 4.61956,0 8.3992,3.77964 8.3992,8.3992 v 33.59681 h 50.39521 c 4.61956,0 8.3992,-3.77965 8.3992,-8.39921 v -8.3992 c 0,-2.41477 -1.0499,-4.72455 -2.88722,-6.2994 l -30.70958,-26.92994 v -17.16587 h 75.59281 v 4.1996 c 0,6.98184 5.61697,12.59881 12.5988,12.59881 6.98184,0 12.59881,-5.61697 12.59881,-12.59881 v -12.5988 -16.7984 -29.39721 c 0,-6.98183 -5.61697,-12.5988 -12.59881,-12.5988 -6.98183,0 -12.5988,5.61697 -12.5988,12.5988 v 9.86906 l -75.59281,-44.0958 V 298.6625 c 0,-4.56707 -0.94491,-9.02914 -2.72974,-13.22874 z"
id="path1154-7"
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.52495;stroke-opacity:1" /><path
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 90.329008,471.87874 294.83388,138.74536"
id="path9928"
sodipodi:nodetypes="cc" /><path
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 37.215553,461.40058 33.602384,51.668198"
id="path9928-3"
sodipodi:nodetypes="cc" /><path
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:10;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="M 61.785043,466.45901 134.77088,205.58883"
id="path9928-31"
sodipodi:nodetypes="cc" /><path
d="m 97.776883,46.496287 c -2.69052,-1.821707 -6.27788,-1.485392 -8.576032,0.784735 -2.298159,2.270126 -2.634474,5.885513 -0.784735,8.548007 l 31.389404,45.766871 -27.914144,9.08052 c -2.774607,0.89684 -4.652369,3.47525 -4.652369,6.38998 0,2.91473 1.877762,5.49315 4.652369,6.38999 l 28.895064,9.36076 -14.82589,28.19441 c -1.37329,2.60645 -0.89684,5.80143 1.20513,7.87538 2.10197,2.07394 5.26893,2.57842 7.87538,1.20513 l 28.19442,-14.82589 9.36076,28.89507 c 0.89684,2.7746 3.47525,4.65236 6.38998,4.65236 2.91474,0 5.49316,-1.87776 6.39,-4.65236 l 9.36076,-28.89507 28.19441,14.82589 c 2.60645,1.37329 5.80144,0.89684 7.87539,-1.20513 2.07393,-2.10197 2.57841,-5.26893 1.20512,-7.87538 l -14.82589,-28.19441 28.89507,-9.36076 c 2.7746,-0.89684 4.65236,-3.47526 4.65236,-6.38999 0,-2.91473 -1.87776,-5.49314 -4.65236,-6.38998 l -29.84797,-9.66907 7.20276,-19.730481 c 0.89684,-2.466311 0.28026,-5.212883 -1.56947,-7.062617 -1.84974,-1.849732 -4.59631,-2.46631 -7.06262,-1.56947 l -19.73049,7.202748 -9.69707,-29.875988 c -0.89684,-2.7746 -3.47526,-4.652359 -6.39,-4.652359 -2.91473,0 -5.49314,1.877759 -6.38998,4.652359 l -9.05248,27.91415 z"
id="path10424"
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.280262;stroke-opacity:1" /></g></svg>

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="19"
height="15"
viewBox="0 0 19 15"
fill="none"
version="1.1"
id="svg6"
sodipodi:docname="simulate-fire-fight.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xml:space="preserve"
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="defs10" /><sodipodi:namedview
id="namedview8"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="39.220856"
inkscape:cx="4.2961837"
inkscape:cy="7.419522"
inkscape:window-width="2560"
inkscape:window-height="1377"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg6"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1" /><path
d="m 16.282245,9.6013771 c 0,0.1740419 -0.142397,0.3164399 -0.316438,0.3164399 h -2.300125 c -0.108773,0.189864 -0.314463,0.316441 -0.547837,0.316441 h -2.161682 l 0.104821,0.316439 h 1.10754 c 0.174043,0 0.316441,0.142398 0.316441,0.316441 v 0.316439 c 0,0.174043 -0.142398,0.316442 -0.316441,0.316442 h -1.671199 c -0.136465,0 -0.257107,-0.08702 -0.300618,-0.215576 L 9.8486235,10.234258 H 9.3205639 v 0.949319 c 0,0.174043 -0.142398,0.316442 -0.3164399,0.316442 H 8.6876841 c -0.1740424,0 -0.3164404,-0.142399 -0.3164404,-0.316442 v -0.87021 L 6.5497356,10.76825 C 6.349983,10.817694 6.1561633,10.667385 6.1561633,10.461699 V 9.2849368 c 0,-0.174042 0.1423981,-0.31644 0.3164399,-0.31644 H 8.3712437 V 8.6520569 c 0,-0.350062 0.2828182,-0.6328802 0.6328803,-0.6328802 h 2.847961 c 0.350061,0 0.63288,0.2828182 0.63288,0.6328802 h 0.63288 c 0.233374,0 0.43906,0.1265758 0.547837,0.3164399 h 1.983684 c 0,-0.1740418 0.142398,-0.3164399 0.316441,-0.3164399 0.174041,0 0.316438,0.1423981 0.316438,0.3164399 v 0.31644 z"
style="fill:#ffffff;fill-opacity:1;stroke:#262626;stroke-width:3.88754;stroke-dasharray:none;stroke-opacity:1"
id="path14144" /><path
d="m 7.2231639,6.192428 c 0.5023486,0 0.9849197,0.1404203 1.4002473,0.3935723 V 12.521231 H 4.8261305 V 9.0740104 L 3.7660562,10.865854 C 3.5445481,11.241626 3.0580216,11.366224 2.6822489,11.144715 2.3064765,10.923208 2.181878,10.436681 2.4033861,10.060908 L 3.9104319,7.5135653 C 4.394981,6.6947765 5.2750798,6.192428 6.2263778,6.192428 Z M 5.1425706,3.9773475 c -3.695e-4,-2.1099689 3.1640311,-2.1099689 3.1644005,0 3.697e-4,2.109969 -3.164031,2.109969 -3.1644005,0 z"
id="path10805"
style="fill:#ffffff;fill-opacity:1;stroke:#262626;stroke-width:3.88754;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
sodipodi:nodetypes="scccccsccsssss" /><path
d="m 16.290263,9.5997326 c 0,0.174042 -0.142396,0.31644 -0.316438,0.31644 h -2.300124 c -0.108774,0.1898644 -0.314463,0.3164404 -0.547838,0.3164404 h -2.161681 l 0.104821,0.316439 h 1.10754 c 0.174042,0 0.31644,0.142399 0.31644,0.316443 v 0.316439 c 0,0.174043 -0.142398,0.316442 -0.31644,0.316442 h -1.671198 c -0.136466,0 -0.257108,-0.08702 -0.300618,-0.215576 L 9.8566424,10.232613 H 9.3285828 v 0.949321 c 0,0.174043 -0.1423981,0.316442 -0.31644,0.316442 H 8.6957029 c -0.1740423,0 -0.3164404,-0.142399 -0.3164404,-0.316442 v -0.870211 l -1.821508,0.454883 C 6.3580019,10.816049 6.1641822,10.66574 6.1641822,10.460054 V 9.2832924 c 0,-0.174042 0.142398,-0.31644 0.3164398,-0.31644 H 8.3792625 V 8.6504125 c 0,-0.350062 0.2828182,-0.6328802 0.6328803,-0.6328802 h 2.8479602 c 0.350062,0 0.63288,0.2828182 0.63288,0.6328802 h 0.63288 c 0.233375,0 0.439061,0.1265757 0.547838,0.3164399 h 1.983684 c 0,-0.1740419 0.142398,-0.3164399 0.31644,-0.3164399 0.174042,0 0.316438,0.142398 0.316438,0.3164399 v 0.31644 z"
style="fill:#ffffff;fill-opacity:1;stroke:#262626;stroke-width:1.31528;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="path14144-2" /></svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -45,6 +45,8 @@
--nav-text: #ECECEC;
--ol-select-secondary: #545F6C;
--ol-switch-off:#686868;
--ol-switch-undefined:#383838;
/*** General border radii **/
--border-radius-xs: 2px;
@@ -88,3 +90,7 @@
--unit-fuel-y: 22px;
--unit-vvi-width: 4px;
}
* {
font-weight:600;
}

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 460 460" xml:space="preserve">
<g id="XMLID_2582_">
<path id="XMLID_4_" style="fill:#871A27;" d="M353.16,459.97L353.16,459.97c-32.138-3.383-61.944-18.336-83.849-42.066
L65.784,206.343c-6.885-7.157-3.266-19.083,6.439-21.217l26.729-5.878l258.429,272.378
C360.487,455.023,357.74,460.452,353.16,459.97z"/>
<path id="XMLID_2108_" style="fill:#A52531;" d="M403.222,459.97L403.222,459.97c-32.138-3.383-61.944-18.336-83.849-42.066
L100.124,190l48.891-10.752l258.429,272.378C410.549,455.023,407.802,460.452,403.222,459.97z"/>
<path id="XMLID_2018_" style="fill:#CC3248;" d="M454.455,459.97L454.455,459.97c-32.138-3.383-61.944-18.336-83.849-42.066
L151.357,190l48.891-10.752l258.429,272.378C461.782,455.023,459.035,460.452,454.455,459.97z"/>
<path id="XMLID_2391_" style="fill:#C1A991;" d="M280.347,380h-80.099l-9.48,37.873C188.984,425,182.572,430,175.217,430
s-13.767-5-15.551-12.127L150.186,380h-0.277c-40.008,0-73.881-29.485-79.348-69.069L44.112,119.414
C30.376,116.668,20.025,104.544,20.025,90H0c0-20.678,15.692-37.69,35.817-39.781C35.079,22.989,56.998,0,84.812,0l0,0
c21.102,0,39.837,13.487,46.51,33.481l0.617,1.848c8.632,25.864,26.455,47.418,49.597,61.877
c8.184,5.113,15.785,13.683,19.63,27.974L280.347,380z"/>
<path id="XMLID_2087_" style="fill:#473C3F;" d="M180.223,420v10v10v5c0,8.284-6.724,15-15.019,15h-50.062
c-2.765,0-5.006-2.239-5.006-5l0,0c0-8.284,6.724-15,15.019-15h35.043c5.53,0,10.012-4.477,10.012-10v-10H180.223z"/>
<path id="XMLID_1701_" style="fill:#9B1C2B;" d="M280.347,380h-80.099l-9.48,37.873C188.984,425,182.572,430,175.217,430
s-13.767-5-15.551-12.127L150.186,380h-0.277c-40.008,0-73.881-29.485-79.348-69.069L44.112,119.414L30.382,90H52.77
c4.803,0,9.541-1.117,13.837-3.262l16.413-8.197c10.482-5.234,17.103-15.935,17.103-27.639C100.124,33.835,86.272,20,69.184,20
H45.307C54.179,7.937,68.475,0,84.812,0l0,0c21.102,0,39.837,13.487,46.51,33.481l0.617,1.848
c8.632,25.864,26.455,47.418,49.597,61.877c8.184,5.113,15.785,13.683,19.63,27.974L280.347,380z"/>
<path id="XMLID_2389_" style="fill:#8C735D;" d="M40.05,90l4.062,29.414C30.376,116.668,20.025,104.544,20.025,90H0
c0-3.454,0.447-6.803,1.27-10h37.667L40.05,90z"/>
<path id="XMLID_1631_" style="fill:#AF8F6D;" d="M40.05,90H0c0-20.676,15.688-37.686,35.81-39.781
c0.037,1.375,0.121,2.759,0.276,4.154L40.05,90z"/>
<path id="XMLID_2088_" style="fill:#66313A;" d="M70.087,45c0,2.761-2.241,5-5.006,5s-5.006-2.239-5.006-5s2.241-5,5.006-5
C67.845,40,70.087,42.239,70.087,45z"/>
<path id="XMLID_1864_" style="fill:#720C1F;" d="M150.186,380h50.062l-5.006,20h-40.049L150.186,380z"/>
<path id="XMLID_1836_" style="fill:#720C1F;" d="M331.261,397.796L246.772,194.5c-19.25-46.319-62.419-73.992-106.431-68.225
l-33.801,4.429c-6.283,0.823-10.071,7.378-7.641,13.223l82.08,197.501c19.25,46.32,62.419,73.992,106.431,68.226l40.03-5.245
C330.582,403.996,332.476,400.719,331.261,397.796z"/>
<path id="XMLID_1924_" style="fill:#871A27;" d="M349.271,380.344l-39.554,8.084c-43.488,8.888-88.523-15.638-111.033-60.467
l-95.979-191.149c-2.841-5.657,0.469-12.465,6.677-13.734l33.398-6.826c43.488-8.888,88.523,15.638,111.033,60.468l98.796,196.758
C354.03,376.305,352.375,379.709,349.271,380.344z"/>
<path id="XMLID_1905_" style="fill:#A52531;" d="M351.863,378.865c-39.327,2.272-78.009-21.643-98.331-62.115l-73.749-146.618
c-1.957-3.897-0.994-8.339,1.823-11.141c37.969-0.535,74.788,23.122,94.439,62.258l76.565,152.227
C353.548,375.345,353.139,377.459,351.863,378.865z"/>
<path id="XMLID_1810_" style="fill:#82542E;" d="M349.27,380.344L310.841,385c-43.488,8.888-89.648-12.21-112.158-57.04
l-66.414-132.268c34.538,30.088,78.926,43.67,122.276,34.81l23.828-4.87l74.236,147.845
C354.029,376.305,352.374,379.709,349.27,380.344z"/>
<path id="XMLID_1705_" style="fill:#684627;" d="M351.864,378.865c-0.656,0.723-1.538,1.263-2.592,1.479l-39.307,6.781
c-43.488,8.888-88.77-14.335-111.28-59.165l-66.414-132.268c22.958,20,50.271,32.696,78.737,36.365l42.526,84.693
C273.856,357.223,312.537,381.137,351.864,378.865z"/>
<path id="XMLID_1636_" style="fill:#385056;" d="M349.27,380.344l-39.554,8.084c-43.488,8.888-88.523-15.638-111.033-60.467
l-21.17-42.161c28.416,19.045,62.146,26.827,95.205,20.07l41.667-8.516l38.223,76.123
C354.029,376.305,352.374,379.709,349.27,380.344z"/>
<path id="XMLID_1807_" style="fill:#446772;" d="M249.716,308.257c7.802-0.018,15.644-0.792,23.45-2.388l41.667-8.516
l38.223,76.123c0.594,1.184,0.644,2.466,0.281,3.606c-0.209,0.65-0.552,1.254-1.012,1.765c-0.005,0.006-0.01,0.012-0.016,0.018
c-39.326,2.271-78.007-21.643-98.329-62.115L249.716,308.257z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -0,0 +1,109 @@
:root {
/** Colours **/
/*** Coalition: neutral ***/
--primary-neutral: #949ba7;
--secondary-neutral-outline: #111111;
--secondary-neutral-text: #111111;
--unit-background-neutral: #CFD9E8;
/*** Coalition: blue ***/
--primary-blue: #247be2;
--secondary-blue-outline: #082e44;
--secondary-blue-text: #017DC1;
--unit-background-blue: #3BB9FF;
/*** Coalition: red ***/
--primary-red: #ff5858;
--secondary-red-outline: #262222;
--secondary-red-text: #D42121;
--unit-background-red: #FF5858;
/*** UI Colours **/
--accent-amber: #ffd828;
--accent-green: #8bff63;
--accent-light-blue: #5ca7ff;
--accent-dark-blue: #017DC1;
--transparent-accent-light-blue: rgba(92, 167, 255, .33);
--accent-light-red: #F5B6B6;
--background-grey: #3d4651;
--background-slate-blue: #363c43;
--background-offwhite: #f2f2f3;
--background-steel: #202831;
--secondary-dark-steel: #181e25;
--secondary-gunmetal-grey: #2f2f2f;
--secondary-lighter-grey: #949ba7;
--secondary-light-grey: #797e83;
--secondary-semitransparent-white: #FFFFFFAA;
--secondary-transparent-white: #FFFFFF30;
--secondary-yellow: #ffd46893;
--background-hover: #f2f2f333;
--nav-text: #ECECEC;
--ol-select-secondary: #545F6C;
--ol-switch-off:#686868;
--ol-switch-undefined:#383838;
/*** General border radii **/
--border-radius-xs: 2px;
--border-radius-sm: 5px;
--border-radius-md: 10px;
--border-radius-lg: 15px;
/*** Fonts **/
--font-weight-bolder: 600;
/*** Unit marker settings ***/
/*** All markers **/
--unit-border-radius: var(--border-radius-xs);
--unit-font-size: 14px;
--unit-font-weight: bolder;
--unit-label-border-width: 2px;
--unit-spotlight-fill: var(--secondary-yellow);
--unit-spotlight-radius: 26px;
--unit-stroke-width: 3px;
--unit-height: 50px;
--unit-width: 50px;
--unit-health-border-width: 2px;
--unit-health-height: 6px;
--unit-health-width: 36px;
--unit-health-x: 0px;
--unit-health-y: 26px;
/*** Air units ***/
--unit-ammo-gap: calc(2px + var(--unit-stroke-width));
--unit-ammo-border-radius: 50%;
--unit-ammo-border-width: 2px;
--unit-ammo-radius: 2px;
--unit-ammo-spacing: 2px;
--unit-ammo-x: 0px;
--unit-ammo-y: 30px;
--unit-fuel-border-width: 2px;
--unit-fuel-height: 6px;
--unit-fuel-width: 36px;
--unit-fuel-x: 0px;
--unit-fuel-y: 22px;
--unit-vvi-width: 4px;
}
* {
font-weight:600;
}
svg {
animation: spin linear infinite 1s;
}
@keyframes spin {
from {
transform:rotate(0deg);
}
to {
transform:rotate(360deg);
}
}

View File

@@ -1,11 +1,42 @@
const express = require('express');
const router = express.Router();
const { v4: uuidv4 } = require('uuid');
// TODO should be user selectable or at least configurable from configuration file
var theme = "olympus";
var themesMap = {};
router.get('/theme/*', function (req, res, next) {
res.redirect(req.url.replace("theme", "themes/" + theme));
/* If this is the first time this session makes a request, create a uuid and save it to the map. Default theme is the olympus theme */
if (!req.cookies.id) {
const id = uuidv4();
res.cookie('id', id, { httpOnly: true });
themesMap[id] = "olympus";
reqTheme = "olympus";
}
else {
/* If it is present, recover the session theme from the map */
if (!(req.cookies.id in themesMap))
themesMap[req.cookies.id] = "olympus";
reqTheme = themesMap[req.cookies.id];
}
/* Yes, this in an easter egg! :D Feel free to ignore it, or activate the parrot theme to check what it does. Why parrots? The story is a bit long, come to the Discord and ask :D */
if (reqTheme === "parrot" && !req.url.includes(".css"))
res.redirect('/themes/parrot/images/parrot.svg');
else
res.redirect(req.url.replace("theme", "themes/" + reqTheme));
});
router.put('/theme/:newTheme', function (req, res, next) {
/* Add the theme to the map, if this session already has an id */
const newTheme = req.params.newTheme;
if (req.cookies.id) {
themesMap[req.cookies.id] = newTheme;
console.log("Theme set to " + newTheme + " for session " + req.cookies.id);
} else {
console.log("Failed to set theme to " + newTheme + ", no session id");
}
res.end("Ok");
});
module.exports = router;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 7.7 MiB

View File

@@ -1,5 +1,5 @@
import { LatLng, LatLngBounds } from "leaflet";
import { MapMarkerControl } from "../map/map";
import { MapMarkerVisibilityControl } from "../map/map";
export const UNITS_URI = "units";
export const WEAPONS_URI = "weapons";
@@ -28,9 +28,9 @@ export const emissionsCountermeasures: string[] = ["silent", "attack", "defend",
export const ROEDescriptions: string[] = [
"Free (Attack anyone)",
"Designated (Attack the designated target only)",
"Designated (Attack the designated target only) \nWARNING: Ground and Navy units don't respect this ROE, it will be equivalent to weapons FREE.",
"",
"Return (Only fire if fired upon)",
"Return (Only fire if fired upon) \nWARNING: Ground and Navy units don't respect this ROE, it will be equivalent to weapons FREE.",
"Hold (Never fire)"
];
@@ -102,6 +102,27 @@ export const minimapBoundaries = [
new LatLng(10.7725, 149.3918333),
new LatLng(22.5127778, 149.5427778),
new LatLng(22.09, 135.0572222)
],
[ // South Atlantic
new LatLng(-49.097217, -79.418267),
new LatLng(-56.874517,-79.418267),
new LatLng(-56.874517, -43.316433),
new LatLng(-49.097217, -43.316433),
new LatLng(-49.097217, -79.418267)
],
[ // Normandy
new LatLng(50.44, -3.29),
new LatLng(48.12,-3.29),
new LatLng(48.12, 3.70),
new LatLng(50.44, 3.70),
new LatLng(50.44, -3.29)
],
[ // Sinai
new LatLng(34.312222, 28.523333),
new LatLng(25.946944, 28.523333),
new LatLng(25.946944, 36.897778),
new LatLng(34.312222, 36.897778),
new LatLng(34.312222, 28.523333)
]
];
@@ -111,7 +132,9 @@ export const mapBounds = {
"Nevada": { bounds: new LatLngBounds([34.4037128, -119.7806729], [39.7372411, -112.1130805]), zoom: 5 },
"PersianGulf": { bounds: new LatLngBounds([21.729393, 47.572675], [33.131584, 64.7313594]), zoom: 5 },
"Caucasus": { bounds: new LatLngBounds([39.6170191, 27.634935], [47.3907982, 49.3101946]), zoom: 4 },
// TODO "Falklands"
"Falklands": { bounds: new LatLngBounds([-49.097217, -79.418267], [-56.874517, -43.316433]), zoom: 3 },
"Normandy": { bounds: new LatLngBounds([50.44, -3.29], [48.12, 3.70]), zoom: 5 },
"SinaiMap": { bounds: new LatLngBounds([34.312222, 28.523333], [25.946944, 36.897778]), zoom: 4 },
}
export const mapLayers = {
@@ -119,7 +142,7 @@ export const mapLayers = {
urlTemplate: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
minZoom: 1,
maxZoom: 19,
attribution: "Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, GetApp().getMap()ping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community"
attribution: "Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Mapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community"
},
"USGS Topo": {
urlTemplate: 'https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}',
@@ -157,14 +180,22 @@ export const mapLayers = {
export const IDLE = "Idle";
export const MOVE_UNIT = "Move unit";
export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area";
export const visibilityControls: string[] = ["human", "dcs", "aircraft", "helicopter", "groundunit-sam", "groundunit-other", "navyunit", "airbase"];
export const visibilityControlsTypes: string[][] = [["human"], ["dcs"], ["aircraft"], ["helicopter"], ["groundunit-sam", "groundunit-sam-radar", "groundunit-sam-launcher"], ["groundunit-other", "groundunit-ewr"], ["navyunit"], ["airbase"]];
export const visibilityControls: string[] = ["human", "dcs", "aircraft", "helicopter", "groundunit-sam", "groundunit", "navyunit", "airbase"];
export const visibilityControlsTypes: string[][] = [["human"], ["dcs"], ["aircraft"], ["helicopter"], ["groundunit-sam"], ["groundunit"], ["navyunit"], ["airbase"]];
export const visibilityControlsTooltips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle helicopter visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"];
export const MAP_MARKER_CONTROLS: MapMarkerControl[] = [{
export const MAP_MARKER_CONTROLS: MapMarkerVisibilityControl[] = [{
"name": "Human",
"image": "visibility/human.svg",
"toggles": ["human"],
"tooltip": "Toggle human players' visibility"
}, {
"image": "visibility/head-side-virus-solid.svg",
"isProtected": false,
"name": "Olympus",
"protectable": false,
"toggles": ["olympus"],
"tooltip": "Toggle Olympus-controlled units' visibility"
}, {
"image": "visibility/dcs.svg",
"isProtected": true,
@@ -188,9 +219,9 @@ export const MAP_MARKER_CONTROLS: MapMarkerControl[] = [{
"toggles": ["groundunit-sam"],
"tooltip": "Toggle air defence units' visibility"
}, {
"image": "visibility/groundunit-other.svg",
"image": "visibility/groundunit.svg",
"name": "Ground units",
"toggles": ["groundunit-other"],
"toggles": ["groundunit"],
"tooltip": "Toggle ground units' visibility"
}, {
"image": "visibility/navyunit.svg",
@@ -204,9 +235,9 @@ export const MAP_MARKER_CONTROLS: MapMarkerControl[] = [{
"tooltip": "Toggle airbase' 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 IADSTypes = ["AAA", "SAM Site", "Radar (EWR)"];
export const IADSDensities: { [key: string]: number } = { "AAA": 0.8, "SAM Site": 0.1, "Radar (EWR)": 0.05 };
export const GROUND_UNIT_AIR_DEFENCE_REGEX:RegExp = /(\b(AAA|SAM|MANPADS?|[mM]anpads?)|[sS]tinger\b)/;
export const HIDE_GROUP_MEMBERS = "Hide group members when zoomed out";
export const SHOW_UNIT_LABELS = "Show unit labels (L)";
export const SHOW_UNITS_ENGAGEMENT_RINGS = "Show units threat range rings (Q)";
@@ -236,6 +267,7 @@ export enum DataIndexes {
horizontalVelocity,
verticalVelocity,
heading,
track,
isActiveTanker,
isActiveAWACS,
onOff,
@@ -275,4 +307,8 @@ export const MGRS_PRECISION_1M = 6;
export const DELETE_CYCLE_TIME = 0.05;
export const DELETE_SLOW_THRESHOLD = 50;
export const GROUPING_ZOOM_TRANSITION = 13;
export const GROUPING_ZOOM_TRANSITION = 13;
export const MAX_SHOTS_SCATTER = 3;
export const MAX_SHOTS_INTENSITY = 3;
export const SHOTS_SCATTER_DEGREES = 10;

View File

@@ -1,4 +1,6 @@
export interface ContextInterface {
allowUnitCopying?: boolean;
allowUnitPasting?: boolean;
useSpawnMenu?: boolean;
useUnitControlPanel?: boolean;
useUnitInfoPanel?: boolean;
@@ -6,16 +8,28 @@ export interface ContextInterface {
export class Context {
#allowUnitCopying:boolean;
#allowUnitPasting:boolean;
#useSpawnMenu:boolean;
#useUnitControlPanel:boolean;
#useUnitInfoPanel:boolean;
constructor( config:ContextInterface ) {
this.#allowUnitCopying = ( config.allowUnitCopying !== false );
this.#allowUnitPasting = ( config.allowUnitPasting !== false );
this.#useSpawnMenu = ( config.useSpawnMenu !== false );
this.#useUnitControlPanel = ( config.useUnitControlPanel !== false );
this.#useUnitInfoPanel = ( config.useUnitInfoPanel !== false );
}
getAllowUnitCopying() {
return this.#allowUnitCopying;
}
getAllowUnitPasting() {
return this.#allowUnitPasting;
}
getUseSpawnMenu() {
return this.#useSpawnMenu;
}

View File

@@ -27,8 +27,8 @@ export class CoalitionAreaContextMenu extends ContextMenu {
super(ID);
/* Create the coalition switch */
this.#coalitionSwitch = new Switch("coalition-area-switch", (value: boolean) => this.#onSwitchClick(value));
this.#coalitionSwitch.setValue(false);
this.#coalitionSwitch = new Switch("coalition-area-switch", (value: boolean) => this.#onSwitchClick(value), true);
this.#coalitionSwitch.setValue(true);
/* Create the controls of the IADS creation submenu */
this.#iadsTypesDropdown = new Dropdown("iads-units-type-options", () => { });
@@ -68,7 +68,8 @@ export class CoalitionAreaContextMenu extends ContextMenu {
const area = this.getCoalitionArea();
if (area)
getApp().getUnitsManager().createIADS(area, getCheckboxOptions(this.#iadsTypesDropdown), getCheckboxOptions(this.#iadsErasDropdown), getCheckboxOptions(this.#iadsRangesDropdown), this.#iadsDensitySlider.getValue(), this.#iadsDistributionSlider.getValue());
})
this.hide();
});
this.hide();
}
@@ -110,7 +111,7 @@ export class CoalitionAreaContextMenu extends ContextMenu {
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => {
element.setAttribute("data-coalition", this.getCoalitionArea()?.getCoalition())
});
this.#coalitionSwitch.setValue(this.getCoalitionArea()?.getCoalition() === "red");
this.#coalitionSwitch.setValue(this.getCoalitionArea()?.getCoalition() === "blue");
}
/** Get the CoalitionArea object the contextmenu is editing
@@ -146,11 +147,11 @@ export class CoalitionAreaContextMenu extends ContextMenu {
/** Callback event called when the coalition switch is clicked to change the coalition of the CoalitionArea
*
* @param value Switch position (false: blue, true: red)
* @param value Switch position (false: red, true: blue)
*/
#onSwitchClick(value: boolean) {
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER) {
this.getCoalitionArea()?.setCoalition(value ? "red" : "blue");
this.getCoalitionArea()?.setCoalition(value ? "blue" : "red");
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => {
element.setAttribute("data-coalition", this.getCoalitionArea()?.getCoalition())
});

View File

@@ -30,7 +30,7 @@ export class MapContextMenu extends ContextMenu {
/* Create the coalition switch */
this.#coalitionSwitch = new Switch("coalition-switch", (value: boolean) => this.#onSwitchClick(value));
this.#coalitionSwitch.setValue(false);
this.#coalitionSwitch.setValue(true);
this.#coalitionSwitch.getContainer()?.addEventListener("contextmenu", (e) => this.#onSwitchRightClick());
/* Create the spawn menus for the different unit types */
@@ -128,9 +128,9 @@ export class MapContextMenu extends ContextMenu {
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) });
if (getApp().getActiveCoalition() == "blue")
this.#coalitionSwitch.setValue(false);
else if (getApp().getActiveCoalition() == "red")
this.#coalitionSwitch.setValue(true);
else if (getApp().getActiveCoalition() == "red")
this.#coalitionSwitch.setValue(false);
else
this.#coalitionSwitch.setValue(undefined);
@@ -232,10 +232,10 @@ export class MapContextMenu extends ContextMenu {
/** Callback called when the user left clicks on the coalition switch
*
* @param value Switch position (false: "blue", true: "red")
* @param value Switch position (true: "blue", false: "red")
*/
#onSwitchClick(value: boolean) {
value ? getApp().setActiveCoalition("red") : getApp().setActiveCoalition("blue");
value ? getApp().setActiveCoalition("blue") : getApp().setActiveCoalition("red");
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) });
this.#aircraftSpawnMenu.setCountries();
this.#helicopterSpawnMenu.setCountries();

View File

@@ -5,8 +5,10 @@ export class Dropdown {
#callback: CallableFunction;
#defaultValue: string;
#optionsList: string[] = [];
#labelsList: string[] | undefined = [];
#index: number = 0;
#hidden: boolean = false;
#text!: HTMLElement;
constructor(ID: string | null, callback: CallableFunction, options: string[] | null = null, defaultText?: string) {
if (ID === null)
@@ -15,13 +17,16 @@ export class Dropdown {
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;
const text = this.#container.querySelector(".ol-select-value-text");
this.#value = (text instanceof HTMLElement) ? text : this.#container.querySelector(".ol-select-value") as HTMLElement;
this.#defaultValue = this.#value.innerText;
this.#callback = callback;
if (options != null) this.setOptions(options);
this.#value.addEventListener("click", (ev) => { this.#toggle(); });
(this.#container.querySelector(".ol-select-value") as HTMLElement)?.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.#container.contains(ev.target as Node))) {
@@ -36,48 +41,37 @@ export class Dropdown {
return this.#container;
}
setOptions(optionsList: string[], sort: "" | "string" | "number" | "string+number" = "string") {
if (sort === "number") {
this.#optionsList = optionsList.sort((optionA: string, optionB: string) => {
const a = parseInt(optionA);
const b = parseInt(optionB);
if (a > b)
return 1;
else
return (b > a) ? -1 : 0;
});
} else if (sort === "string+number") {
this.#optionsList = optionsList.sort((optionA: string, optionB: string) => {
var regex = /\d+/g;
var matchesA = optionA.match(regex);
var matchesB = optionB.match(regex);
if ((matchesA != null && matchesA?.length > 0) && (matchesB != null && matchesB?.length > 0) && optionA[0] == optionB[0]) {
const a = parseInt(matchesA[0] ?? 0);
const b = parseInt(matchesB[0] ?? 0);
if (a > b)
return 1;
else
return (b > a) ? -1 : 0;
} else {
if (optionA > optionB)
return 1;
else
return (optionB > optionA) ? -1 : 0;
}
});
} else if (sort === "string") {
this.#optionsList = optionsList.sort();
}
/** Set the dropdown options strings
*
* @param optionsList List of options. These are the keys that will always be returned on selection
* @param sort Sort method. "string" performs js default sort. "number" sorts purely by numeric value.
* "string+number" sorts by string, unless two elements are lexicographically identical up to a numeric value (e.g. "SA-2" and "SA-3"), in which case it sorts by number.
* @param labelsList (Optional) List of labels to be shown instead of the keys directly. If provided, the options will be sorted by label.
*/
setOptions(optionsList: string[], sort: "" | "string" | "number" | "string+number" = "string", labelsList: string[] | undefined = undefined) {
/* If labels are provided, sort by labels, else by options */
if (labelsList && labelsList.length == optionsList.length)
this.#sortByLabels(optionsList, sort, labelsList);
else
this.#sortByOptions(optionsList, sort);
/* If no options are provided, return */
if (this.#optionsList.length == 0) {
optionsList = ["No options available"]
this.#value.innerText = "No options available";
return;
}
this.#options.replaceChildren(...optionsList.map((option: string, idx: number) => {
/* Create the buttons containing the options or the labels */
this.#options.replaceChildren(...this.#optionsList.map((option: string, idx: number) => {
var div = document.createElement("div");
var button = document.createElement("button");
button.textContent = option;
/* If the labels are provided use them for the options */
if (this.#labelsList && this.#labelsList.length === optionsList.length)
button.textContent = this.#labelsList[idx];
else
button.textContent = option;
div.appendChild(button);
if (option === this.#defaultValue)
@@ -91,8 +85,21 @@ export class Dropdown {
}));
}
getOptionsList() {
return this.#optionsList;
}
getLabelsList() {
return this.#labelsList;
}
/** Manually set the HTMLElements of the dropdown values. Handling of the selection must be performed externally.
*
* @param optionsElements List of elements to be added to the dropdown
*/
setOptionsElements(optionsElements: HTMLElement[]) {
this.#optionsList = [];
this.#labelsList = [];
this.#options.replaceChildren(...optionsElements);
}
@@ -104,19 +111,22 @@ export class Dropdown {
this.#options.appendChild(optionElement);
}
selectText(text: string) {
const index = [].slice.call(this.#options.children).findIndex((opt: Element) => opt.querySelector("button")?.innerText === text);
if (index > -1) {
this.selectValue(index);
}
}
/** Select the active value of the dropdown
*
* @param idx The index of the element to select
* @returns True if the index is valid, false otherwise
*/
selectValue(idx: number) {
if (idx < this.#optionsList.length) {
var option = this.#optionsList[idx];
var el = document.createElement("div");
el.classList.add("ol-ellipsed");
el.innerText = option;
if (this.#labelsList && this.#labelsList.length == this.#optionsList.length)
el.innerText = this.#labelsList[idx];
else
el.innerText = option;
this.#value.replaceChildren();
this.#value.appendChild(el);
this.#index = idx;
@@ -133,16 +143,24 @@ export class Dropdown {
this.#value.innerText = this.#defaultValue;
}
getValue() {
return this.#value.innerText;
}
/** Manually set the selected value of the dropdown
*
* @param value The value to select. Must be one of the valid options
*/
setValue(value: string) {
var index = this.#optionsList.findIndex((option) => { return option === value });
if (index > -1)
this.selectValue(index);
}
getValue() {
return this.#value.innerText;
}
/** Force the selected value of the dropdown.
*
* @param value Any string. Will be shown as selected value even if not one of the options.
*/
forceValue(value: string) {
var el = document.createElement("div");
el.classList.add("ol-ellipsed");
@@ -205,4 +223,110 @@ export class Dropdown {
div.append(value, options);
return div;
}
/** Sort the elements by their option keys
*
* @param optionsList The unsorted list of options
* @param sort The sorting method
*/
#sortByOptions(optionsList: string[], sort: string) {
if (sort === "number") {
this.#optionsList = JSON.parse(JSON.stringify(this.#numberSort(optionsList)));
} else if (sort === "string+number") {
this.#optionsList = JSON.parse(JSON.stringify(this.#stringNumberSort(optionsList)));
} else if (sort === "string") {
this.#optionsList = JSON.parse(JSON.stringify(this.#stringSort(optionsList)));
}
}
/** Sort the elements by their labels
*
* @param optionsList The unsorted list of options
* @param sort The sorting method
* @param labelsList The unsorted list of labels. The elements will be sorted according to these values
*/
#sortByLabels(optionsList: string[], sort: string, labelsList: string[]) {
/* Create a temporary deepcopied list. This is necessary because unlike options, labels can be repeated.
Once matched, labels are removed from the temporary array to avoid repeating the same key multiple times */
var tempLabelsList: (string | undefined)[] = JSON.parse(JSON.stringify(labelsList));
if (sort === "number") {
this.#labelsList = JSON.parse(JSON.stringify(this.#numberSort(labelsList)));
} else if (sort === "string+number") {
this.#labelsList = JSON.parse(JSON.stringify(this.#stringNumberSort(labelsList)));
} else if (sort === "string") {
this.#labelsList = JSON.parse(JSON.stringify(this.#stringSort(labelsList)));
}
/* Remap the options list to match their labels */
this.#optionsList = optionsList?.map((option: string, idx: number) => {
let originalIdx = tempLabelsList.indexOf(this.#labelsList? this.#labelsList[idx]: "");
/* After a match has been completed, set the label to undefined so it won't be matched again. This allows to have repeated labels */
tempLabelsList[originalIdx] = undefined;
return optionsList[originalIdx];
})
}
/** Sort elements by number. All elements must be parsable as numbers.
*
* @param elements List of strings
* @returns Sorted list
*/
#numberSort(elements: string[]) {
return elements.sort((elementA: string, elementB: string) => {
const a = parseFloat(elementA);
const b = parseFloat(elementB);
if (a > b)
return 1;
else
return (b > a) ? -1 : 0;
});
}
/** Sort elements by string, unless two elements are lexicographically identical up to a numeric value (e.g. "SA-2" and "SA-3"), in which case sort by number
*
* @param elements List of strings
* @returns Sorted list
*/
#stringNumberSort(elements: string[]) {
return elements.sort((elementA: string, elementB: string) => {
/* Check if there is a number in both strings */
var regex = /\d+/g;
var matchesA = elementA.match(regex);
var matchesB = elementB.match(regex);
/* Get the position of the number in the string */
var indexA = -1;
var indexB = -1;
if (matchesA != null && matchesA?.length > 0)
indexA = elementA.search(matchesA[0]);
if (matchesB != null && matchesB?.length > 0)
indexB = elementB.search(matchesB[0]);
/* If the two strings are the same up to the number, sort them using the number value, else sort them according to the string */
if ((matchesA != null && matchesA?.length > 0) && (matchesB != null && matchesB?.length > 0) && elementA.substring(0, indexA) === elementB.substring(0, indexB)) {
const a = parseInt(matchesA[0] ?? 0);
const b = parseInt(matchesB[0] ?? 0);
if (a > b)
return 1;
else
return (b > a) ? -1 : 0;
} else {
if (elementA > elementB)
return 1;
else
return (elementB > elementA) ? -1 : 0;
}
});
}
/** Sort by string. Just a wrapper for consistency.
*
* @param elements List of strings
* @returns Sorted list
*/
#stringSort(elements: string[]) {
return elements.sort();
}
}

View File

@@ -3,17 +3,23 @@ import { Dropdown } from "./dropdown";
import { Slider } from "./slider";
import { UnitDatabase } from "../unit/databases/unitdatabase";
import { getApp } from "..";
import { GAME_MASTER } from "../constants/constants";
import { GAME_MASTER, GROUND_UNIT_AIR_DEFENCE_REGEX } from "../constants/constants";
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";
import { UnitSpawnOptions, UnitSpawnTable } from "../interfaces";
import { UnitBlueprint, UnitSpawnOptions, UnitSpawnTable } from "../interfaces";
export class UnitSpawnMenu {
/** This is the common code for all the unit spawn menus. It is shown both when right clicking on the map and when spawning from airbase.
*
*/
export abstract class UnitSpawnMenu {
protected showRangeCircles: boolean = false;
protected unitTypeFilter = (unit:any) => { return true; };
/* Default options */
protected spawnOptions: UnitSpawnOptions = {
roleType: "",
name: "",
@@ -31,8 +37,9 @@ export class UnitSpawnMenu {
#unitDatabase: UnitDatabase;
#countryCodes: any;
#orderByRole: boolean;
protected unitTypeFilter = (unit:any) => { return true; };
#showLoadout: boolean = true;
#showAltitudeSlider: boolean = true;
/* Controls */
#unitRoleTypeDropdown: Dropdown;
#unitLabelDropdown: Dropdown;
@@ -44,11 +51,18 @@ export class UnitSpawnMenu {
/* HTML Elements */
#deployUnitButtonEl: HTMLButtonElement;
#unitCountDivider: HTMLDivElement;
#unitLoadoutPreviewEl: HTMLDivElement;
#unitImageEl: HTMLImageElement;
#unitLoadoutListEl: HTMLDivElement;
#descriptionDiv: HTMLDivElement;
#abilitiesDiv: HTMLDivElement;
#advancedOptionsDiv: HTMLDivElement;
#unitInfoDiv: HTMLDivElement;
#advancedOptionsToggle: HTMLDivElement;
#advancedOptionsText: HTMLDivElement;
#unitInfoToggle: HTMLDivElement;
#unitInfoText: HTMLDivElement;
/* Range circle previews */
#engagementCircle: Circle;
@@ -60,20 +74,20 @@ export class UnitSpawnMenu {
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.#unitRoleTypeDropdown = new Dropdown(null, (roleType: string) => this.#setUnitRoleType(roleType), undefined, "Role");
this.#unitLabelDropdown = new Dropdown(null, (name: string) => this.#setUnitName(name), undefined, "Type");
this.#unitLoadoutDropdown = new Dropdown(null, (loadout: string) => this.#setUnitLoadout(loadout), undefined, "Loadout");
this.#unitCountDropdown = new Dropdown(null, (count: string) => this.#setUnitCount(count), undefined, "Count");
this.#unitCountryDropdown = new Dropdown(null, () => { /* Custom button implementation */ }, undefined, "Country");
this.#unitLiveryDropdown = new Dropdown(null, (livery: string) => this.#setUnitLivery(livery), undefined, "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());
this.#unitCountDivider = document.createElement("div");
this.#unitCountDivider.innerText = "x";
unitLabelCountContainerEl.append(this.#unitLabelDropdown.getContainer(), this.#unitCountDivider, this.#unitCountDropdown.getContainer());
/* Create the unit image and loadout elements */
this.#unitImageEl = document.createElement("img");
@@ -84,38 +98,37 @@ export class UnitSpawnMenu {
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");
/* Create the advanced options collapsible div */
this.#advancedOptionsDiv = document.createElement("div");
this.#advancedOptionsDiv.classList.add("contextmenu-advanced-options", "hide");
this.#advancedOptionsToggle = document.createElement("div");
this.#advancedOptionsToggle.classList.add("contextmenu-advanced-options-toggle");
this.#advancedOptionsText = document.createElement("div");
this.#advancedOptionsText.innerText = "Faction / Liveries";
this.#advancedOptionsToggle.append(this.#advancedOptionsText);
this.#advancedOptionsToggle.addEventListener("click", () => {
this.#advancedOptionsToggle.classList.toggle("is-open");
this.#advancedOptionsDiv.classList.toggle("hide");
this.#container.dispatchEvent(new Event("resize"));
});
advancedOptionsDiv.append(this.#unitCountryDropdown.getContainer(), this.#unitLiveryDropdown.getContainer(),
this.#unitSpawnAltitudeSlider.getContainer() as HTMLElement);
this.#advancedOptionsDiv.append(this.#unitCountryDropdown.getContainer(), this.#unitLiveryDropdown.getContainer());
/* Create the divider and the metadata collapsible div */
var metadataDiv = document.createElement("div");
metadataDiv.classList.add("contextmenu-metadata", "hide");
var metadataToggle = document.createElement("div");
metadataToggle.classList.add("contextmenu-metadata-toggle");
var metadataText = document.createElement("div");
metadataText.innerText = "Info";
var metadataHr = document.createElement("hr");
metadataToggle.append(metadataText, metadataHr);
metadataToggle.addEventListener("click", () => {
metadataDiv.classList.toggle("hide");
/* Create the unit info collapsible div */
this.#unitInfoDiv = document.createElement("div");
this.#unitInfoDiv.classList.add("contextmenu-metadata", "hide");
this.#unitInfoToggle = document.createElement("div");
this.#unitInfoToggle.classList.add("contextmenu-metadata-toggle");
this.#unitInfoText = document.createElement("div");
this.#unitInfoText.innerText = "Unit information";
this.#unitInfoToggle.append(this.#unitInfoText);
this.#unitInfoToggle.addEventListener("click", () => {
this.#unitInfoToggle.classList.toggle("is-open");
this.#unitInfoDiv.classList.toggle("hide");
this.#container.dispatchEvent(new Event("resize"));
});
this.#descriptionDiv = document.createElement("div");
this.#abilitiesDiv = document.createElement("div");
metadataDiv.append(this.#descriptionDiv, this.#abilitiesDiv);
this.#unitInfoDiv.append(this.#descriptionDiv, this.#abilitiesDiv);
/* Create the unit deploy button */
this.#deployUnitButtonEl = document.createElement("button");
@@ -128,8 +141,8 @@ export class UnitSpawnMenu {
});
/* Assemble all components */
this.#container.append(this.#unitRoleTypeDropdown.getContainer(), unitLabelCountContainerEl, this.#unitLoadoutDropdown.getContainer(),
this.#unitLoadoutPreviewEl, advancedOptionsToggle, advancedOptionsDiv, metadataToggle, metadataDiv, this.#deployUnitButtonEl);
this.#container.append(this.#unitRoleTypeDropdown.getContainer(), unitLabelCountContainerEl, this.#unitLoadoutDropdown.getContainer(), this.#unitSpawnAltitudeSlider.getContainer() as HTMLElement,
this.#unitLoadoutPreviewEl, this.#advancedOptionsToggle, this.#advancedOptionsDiv, this.#unitInfoToggle, this.#unitInfoDiv, this.#deployUnitButtonEl);
/* Load the country codes from the public folder */
var xhr = new XMLHttpRequest();
@@ -144,8 +157,29 @@ export class UnitSpawnMenu {
};
xhr.send();
/* Create the range circle previews */
this.#engagementCircle = new Circle(this.spawnOptions.latlng, { radius: 0, weight: 4, opacity: 0.8, fillOpacity: 0, dashArray: "4 8", interactive: false, bubblingMouseEvents: false });
this.#acquisitionCircle = new Circle(this.spawnOptions.latlng, { radius: 0, weight: 2, opacity: 0.8, fillOpacity: 0, dashArray: "8 12", interactive: false, bubblingMouseEvents: false });
/* Event listeners */
this.#container.addEventListener("unitRoleTypeChanged", () => {
/* Shown the unit label and the unit count dropdowns */
this.#unitLabelDropdown.show();
this.#unitCountDivider.classList.remove("hide");
this.#unitCountDropdown.show();
/* Hide all the other components */
this.#unitLoadoutDropdown.hide();
this.#unitSpawnAltitudeSlider.hide();
this.#unitLoadoutPreviewEl.classList.add("hide");
this.#advancedOptionsDiv.classList.add("hide");
this.#unitInfoDiv.classList.add("hide");
this.#advancedOptionsText.classList.add("hide");
this.#advancedOptionsToggle.classList.add("hide");
this.#unitInfoText.classList.add("hide");
this.#unitInfoToggle.classList.add("hide");
/* Disable the spawn button */
this.#deployUnitButtonEl.disabled = true;
this.#unitLabelDropdown.reset();
this.#unitLoadoutListEl.replaceChildren();
@@ -153,17 +187,30 @@ export class UnitSpawnMenu {
this.#unitImageEl.classList.toggle("hide", true);
this.#unitLiveryDropdown.reset();
/* Populate the labels dropdown from the database */
var blueprints: UnitBlueprint[] = [];
if (this.#orderByRole)
this.#unitLabelDropdown.setOptions(this.#unitDatabase.getByRole(this.spawnOptions.roleType).map((blueprint) => { return blueprint.label }), "string+number");
blueprints = this.#unitDatabase.getByRole(this.spawnOptions.roleType);
else
this.#unitLabelDropdown.setOptions(this.#unitDatabase.getByType(this.spawnOptions.roleType).map((blueprint) => { return blueprint.label }), "string+number");
blueprints = this.#unitDatabase.getByType(this.spawnOptions.roleType);
/* Presort the elements by name in case any have equal labels */
blueprints = blueprints.sort((blueprintA: UnitBlueprint, blueprintB: UnitBlueprint) => {
if (blueprintA.name > blueprintA.name)
return 1;
else
return (blueprintB.name > blueprintA.name) ? -1 : 0;
});
this.#unitLabelDropdown.setOptions(blueprints.map((blueprint) => { return blueprint.name }), "string+number", blueprints.map((blueprint) => { return blueprint.label }));
/* Add the tags to the options */
var elements: HTMLElement[] = [];
for (let idx = 0; idx < this.#unitLabelDropdown.getOptionElements().length; idx++) {
let name = this.#unitLabelDropdown.getOptionsList()[idx];
let element = this.#unitLabelDropdown.getOptionElements()[idx] as HTMLElement;
let entry = this.#unitDatabase.getByLabel(element.textContent ?? "");
if (entry) {
let entry = this.#unitDatabase.getByName(name);
if (entry && entry.tags?.trim() !== "") {
element.querySelectorAll("button")[0]?.append(...(entry.tags?.split(",").map((tag: string) => {
tag = tag.trim();
let el = document.createElement("div");
@@ -176,8 +223,10 @@ export class UnitSpawnMenu {
}
}
/* Request resizing */
this.#container.dispatchEvent(new Event("resize"));
/* Reset the spawn options */
this.spawnOptions.name = "";
this.spawnOptions.loadout = undefined;
this.spawnOptions.liveryID = undefined;
@@ -186,23 +235,43 @@ export class UnitSpawnMenu {
})
this.#container.addEventListener("unitLabelChanged", () => {
/* If enabled, show the altitude slideer and loadouts section */
if (this.#showAltitudeSlider)
this.#unitSpawnAltitudeSlider.show();
if (this.#showLoadout) {
this.#unitLoadoutDropdown.show();
this.#unitLoadoutPreviewEl.classList.remove("hide");
}
/* Show the advanced options and unit info sections */
this.#advancedOptionsText.classList.remove("hide");
this.#advancedOptionsToggle.classList.remove("hide");
this.#advancedOptionsToggle.classList.remove("is-open");
this.#unitInfoText.classList.remove("hide");
this.#unitInfoToggle.classList.remove("hide");
this.#unitInfoToggle.classList.remove("is-open");
/* Enable the spawn button */
this.#deployUnitButtonEl.disabled = false;
/* If enabled, populate the loadout dropdown */
if (!this.#unitLoadoutDropdown.isHidden()) {
this.#unitLoadoutDropdown.setOptions(this.#unitDatabase.getLoadoutNamesByRole(this.spawnOptions.name, this.spawnOptions.roleType));
this.#unitLoadoutDropdown.selectValue(0);
}
/* Get the unit data from the db */
var blueprint = this.#unitDatabase.getByName(this.spawnOptions.name);
/* Shown the unit silhouette */
this.#unitImageEl.src = `images/units/${blueprint?.filename}`;
this.#unitImageEl.classList.toggle("hide", !(blueprint?.filename !== undefined));
this.#unitImageEl.classList.toggle("hide", !(blueprint?.filename !== undefined && blueprint?.filename !== ''));
/* Set the livery options */
this.#setUnitLiveryOptions();
this.#container.dispatchEvent(new Event("resize"));
this.#computeSpawnPoints();
/* Populate the description and abilities sections */
this.#descriptionDiv.replaceChildren();
this.#abilitiesDiv.replaceChildren();
@@ -223,10 +292,16 @@ export class UnitSpawnMenu {
}
}
/* Show the range circles */
this.showCirclesPreviews();
/* Request resizing */
this.#container.dispatchEvent(new Event("resize"));
this.#computeSpawnPoints();
})
this.#container.addEventListener("unitLoadoutChanged", () => {
/* Update the loadout information */
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") : "";
@@ -243,10 +318,12 @@ export class UnitSpawnMenu {
})
this.#container.addEventListener("unitCountChanged", () => {
/* Recompute the spawn points */
this.#computeSpawnPoints();
})
this.#container.addEventListener("unitCountryChanged", () => {
/* Get the unit liveries by country */
this.#setUnitLiveryOptions();
})
@@ -255,13 +332,13 @@ export class UnitSpawnMenu {
})
document.addEventListener('activeCoalitionChanged', () => {
/* If the coalition changed, update the circle previews to set the colours */
this.showCirclesPreviews();
});
this.#engagementCircle = new Circle(this.spawnOptions.latlng, { radius: 0, weight: 4, opacity: 0.8, fillOpacity: 0, dashArray: "4 8", interactive: false, bubblingMouseEvents: false });
this.#acquisitionCircle = new Circle(this.spawnOptions.latlng, { radius: 0, weight: 2, opacity: 0.8, fillOpacity: 0, dashArray: "8 12", interactive: false, bubblingMouseEvents: false });
}
abstract deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number): void;
getContainer() {
return this.#container;
}
@@ -271,7 +348,10 @@ export class UnitSpawnMenu {
}
reset() {
/* Disable the spawn button */
this.#deployUnitButtonEl.disabled = true;
/* Reset all the dropdowns */
this.#unitRoleTypeDropdown.reset();
this.#unitLabelDropdown.reset();
this.#unitLiveryDropdown.reset();
@@ -280,19 +360,37 @@ export class UnitSpawnMenu {
else
this.#unitRoleTypeDropdown.setOptions(this.#unitDatabase.getTypes(this.unitTypeFilter));
/* Reset the contents of the div elements */
this.#unitLoadoutListEl.replaceChildren();
this.#unitLoadoutDropdown.reset();
this.#unitImageEl.classList.toggle("hide", true);
this.#descriptionDiv.replaceChildren();
this.#abilitiesDiv.replaceChildren();
this.setCountries();
this.#container.dispatchEvent(new Event("resize"));
/* Hide everything but the unit type dropdown */
this.#unitLabelDropdown.hide();
this.#unitCountDivider.classList.add("hide");
this.#unitCountDropdown.hide();
this.#unitLoadoutDropdown.hide();
this.#unitSpawnAltitudeSlider.hide();
this.#unitLoadoutPreviewEl.classList.add("hide");
this.#advancedOptionsDiv.classList.add("hide");
this.#unitInfoDiv.classList.add("hide");
this.#advancedOptionsText.classList.add("hide");
this.#advancedOptionsToggle.classList.add("hide");
this.#unitInfoText.classList.add("hide");
this.#unitInfoToggle.classList.add("hide");
/* Get the countries and clear the circle previews */
this.setCountries();
this.clearCirclesPreviews();
/* Request resizing */
this.#container.dispatchEvent(new Event("resize"));
}
setCountries() {
/* Create the countries dropdown elements (with the little flags) */
var coalitions = getApp().getMissionManager().getCoalitions();
var countries = Object.values(coalitions[getApp().getActiveCoalition() as keyof typeof coalitions]);
this.#unitCountryDropdown.setOptionsElements(this.#createCountryButtons(this.#unitCountryDropdown, countries, (country: string) => { this.#setUnitCountry(country) }));
@@ -303,13 +401,6 @@ export class UnitSpawnMenu {
}
}
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();
}
showCirclesPreviews() {
this.clearCirclesPreviews();
@@ -413,13 +504,20 @@ export class UnitSpawnMenu {
return this.#unitSpawnAltitudeSlider;
}
setShowLoadout(showLoadout: boolean) {
this.#showLoadout = showLoadout;
}
setShowAltitudeSlider(showAltitudeSlider: boolean) {
this.#showAltitudeSlider = showAltitudeSlider;
}
#setUnitRoleType(roleType: string) {
this.spawnOptions.roleType = roleType;
this.#container.dispatchEvent(new Event("unitRoleTypeChanged"));
}
#setUnitLabel(label: string) {
var name = this.#unitDatabase.getByLabel(label)?.name || null;
#setUnitName(name: string) {
if (name != null)
this.spawnOptions.name = name;
this.#container.dispatchEvent(new Event("unitLabelChanged"));
@@ -471,10 +569,6 @@ export class UnitSpawnMenu {
}
}
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");
@@ -603,9 +697,8 @@ export class HelicopterSpawnMenu extends UnitSpawnMenu {
}
export class GroundUnitSpawnMenu extends UnitSpawnMenu {
protected showRangeCircles: boolean = true;
protected unitTypeFilter = (unit:any) => {return !(/\bAAA|SAM\b/.test(unit.type) || /\bmanpad|stinger\b/i.test(unit.type))};
protected unitTypeFilter = (unit:any) => {return !(GROUND_UNIT_AIR_DEFENCE_REGEX.test(unit.type))};
/**
*
@@ -614,8 +707,8 @@ export class GroundUnitSpawnMenu extends UnitSpawnMenu {
constructor(ID: string){
super(ID, groundUnitDatabase, false);
this.setMaxUnitCount(20);
this.getAltitudeSlider().hide();
this.getLoadoutDropdown().hide();
this.setShowAltitudeSlider(false);
this.setShowLoadout(false);
this.getLoadoutPreview().classList.add("hide");
}
@@ -645,8 +738,7 @@ export class GroundUnitSpawnMenu extends UnitSpawnMenu {
}
export class AirDefenceUnitSpawnMenu extends GroundUnitSpawnMenu {
protected unitTypeFilter = (unit:any) => {return /\bAAA|SAM\b/.test(unit.type) || /\bmanpad|stinger\b/i.test(unit.type)};
protected unitTypeFilter = (unit:any) => {return GROUND_UNIT_AIR_DEFENCE_REGEX.test(unit.type)};
/**
*
@@ -666,9 +758,8 @@ export class NavyUnitSpawnMenu extends UnitSpawnMenu {
constructor(ID: string){
super(ID, navyUnitDatabase, false);
this.setMaxUnitCount(4);
this.getAltitudeSlider().hide();
this.getLoadoutDropdown().hide();
this.getLoadoutPreview().classList.add("hide");
this.setShowAltitudeSlider(false);
this.setShowLoadout(false);
}
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number) {

2
client/src/dom.d.ts vendored
View File

@@ -18,7 +18,7 @@ interface CustomEventMap {
"groupDeletion": CustomEvent<Unit[]>,
"mapStateChanged": CustomEvent<string>,
"mapContextMenu": CustomEvent<>,
"mapVisibilityOptionsChanged": CustomEvent<>,
"mapOptionsChanged": CustomEvent<>,
"commandModeOptionsChanged": CustomEvent<>,
"contactsUpdated": CustomEvent<Unit>,
"activeCoalitionChanged": CustomEvent<>

View File

@@ -139,6 +139,7 @@ export interface Offset {
export interface UnitData {
category: string,
categoryDisplayName: string,
ID: number;
alive: boolean;
human: boolean;
@@ -156,6 +157,7 @@ export interface UnitData {
horizontalVelocity: number;
verticalVelocity: number;
heading: number;
track: number;
isActiveTanker: boolean;
isActiveAWACS: boolean;
onOff: boolean;

View File

@@ -7,13 +7,12 @@ 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 { bearing, createCheckboxOption, polyContains } from "../other/utils";
import { DestinationPreviewMarker } from "./markers/destinationpreviewmarker";
import { TemporaryUnitMarker } from "./markers/temporaryunitmarker";
import { ClickableMiniMap } from "./clickableminimap";
import { SVGInjector } from '@tanem/svg-injector'
import { mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, MOVE_UNIT, SHOW_UNIT_CONTACTS, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, SHOW_UNIT_LABELS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, MAP_MARKER_CONTROLS } from "../constants/constants";
import { TargetMarker } from "./markers/targetmarker";
import { CoalitionArea } from "./coalitionarea/coalitionarea";
import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu";
import { DrawingCursor } from "./coalitionarea/drawingcursor";
@@ -27,9 +26,9 @@ var hasTouchScreen = false;
//if ("maxTouchPoints" in navigator)
// hasTouchScreen = navigator.maxTouchPoints > 0;
if (hasTouchScreen)
if (hasTouchScreen)
L.Map.addInitHook('addHandler', 'boxSelect', TouchBoxSelect);
else
else
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
L.Map.addInitHook("addHandler", "gestureHandling", GestureHandling);
@@ -38,10 +37,10 @@ L.Map.addInitHook("addHandler", "gestureHandling", GestureHandling);
require("../../public/javascripts/leaflet.nauticscale.js")
require("../../public/javascripts/L.Path.Drag.js")
export type MapMarkerControl = {
export type MapMarkerVisibilityControl = {
"image": string;
"isProtected"?: boolean,
"name":string,
"name": string,
"protectable"?: boolean,
"toggles": string[],
"tooltip": string
@@ -75,7 +74,6 @@ export class Map extends L.Map {
#destinationRotationCenter: L.LatLng | null = null;
#coalitionAreas: CoalitionArea[] = [];
#targetCursor: TargetMarker = new TargetMarker(new L.LatLng(0, 0), { interactive: false });
#destinationPreviewCursors: DestinationPreviewMarker[] = [];
#drawingCursor: DrawingCursor = new DrawingCursor();
#destinationPreviewHandle: DestinationPreviewHandle = new DestinationPreviewHandle(new L.LatLng(0, 0));
@@ -90,7 +88,7 @@ export class Map extends L.Map {
#coalitionAreaContextMenu: CoalitionAreaContextMenu = new CoalitionAreaContextMenu("coalition-area-contextmenu");
#mapSourceDropdown: Dropdown;
#mapMarkerControls:MapMarkerControl[] = MAP_MARKER_CONTROLS;
#mapMarkerVisibilityControls: MapMarkerVisibilityControl[] = MAP_MARKER_CONTROLS;
#mapVisibilityOptionsDropdown: Dropdown;
#optionButtons: { [key: string]: HTMLButtonElement[] } = {}
#visibilityOptions: { [key: string]: boolean } = {}
@@ -100,21 +98,21 @@ export class Map extends L.Map {
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
constructor(ID: string) {
/* Init the leaflet map */
super(ID, {
preferCanvas: true,
doubleClickZoom: false,
zoomControl: false,
boxZoom: false,
super(ID, {
preferCanvas: true,
doubleClickZoom: false,
zoomControl: false,
boxZoom: false,
//@ts-ignore Needed because the boxSelect option is non-standard
boxSelect: true,
zoomAnimation: true,
boxSelect: true,
zoomAnimation: true,
maxBoundsViscosity: 1.0,
minZoom: 7,
minZoom: 7,
keyboard: true,
keyboardPanDelta: 0,
gestureHandling: hasTouchScreen
gestureHandling: hasTouchScreen
});
this.setView([37.23, -115.8], 10);
@@ -198,15 +196,15 @@ export class Map extends L.Map {
this.#panToUnit(this.#centerUnit);
});
document.addEventListener("mapVisibilityOptionsChanged", () => {
document.addEventListener("mapOptionsChanged", () => {
this.getContainer().toggleAttribute("data-hide-labels", !this.getVisibilityOptions()[SHOW_UNIT_LABELS]);
});
/* Pan interval */
this.#panInterval = window.setInterval(() => {
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));
this.panBy(new L.Point(((this.#panLeft ? -1 : 0) + (this.#panRight ? 1 : 0)) * this.#deafultPanDelta * (this.#shiftKey ? 3 : 1),
((this.#panUp ? -1 : 0) + (this.#panDown ? 1 : 0)) * this.#deafultPanDelta * (this.#shiftKey ? 3 : 1)));
}, 20);
/* Option buttons */
@@ -257,7 +255,7 @@ export class Map extends L.Map {
/* Operations to perform if you are NOT in a state */
if (this.#state !== COALITIONAREA_DRAW_POLYGON) {
this.#deselectCoalitionAreas();
this.#deselectSelectedCoalitionArea();
}
/* Operations to perform if you ARE in a state */
@@ -291,7 +289,6 @@ export class Map extends L.Map {
else {
this.#hiddenTypes.push(key);
}
Object.values(getApp().getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
}
getHiddenTypes() {
@@ -377,11 +374,6 @@ export class Map extends L.Map {
this.#coalitionAreaContextMenu.hide();
}
isZooming() {
return this.#isZooming;
}
/* Mouse coordinates */
getMousePosition() {
return this.#lastMousePosition;
}
@@ -390,11 +382,6 @@ export class Map extends L.Map {
return this.containerPointToLatLng(this.#lastMousePosition);
}
/* Spawn from air base */
spawnFromAirbase(e: any) {
//this.#aircraftSpawnMenu(e);
}
centerOnUnit(ID: number | null) {
if (ID != null) {
this.options.scrollWheelZoom = 'center';
@@ -407,7 +394,7 @@ export class Map extends L.Map {
this.#updateCursor();
}
getCenterUnit() {
getCenteredOnUnit() {
return this.#centerUnit;
}
@@ -420,7 +407,6 @@ export class Map extends L.Map {
}
this.setView(bounds.getCenter(), 8);
//this.setMaxBounds(bounds);
if (this.#miniMap)
this.#miniMap.remove();
@@ -502,10 +488,35 @@ export class Map extends L.Map {
return this.#visibilityOptions;
}
isZooming() {
return this.#isZooming;
}
getPreviousZoom() {
return this.#previousZoom;
}
getIsUnitProtected(unit: Unit) {
const toggles = this.#mapMarkerVisibilityControls.reduce((list, control: MapMarkerVisibilityControl) => {
if (control.isProtected) {
list = list.concat(control.toggles);
}
return list;
}, [] as string[]);
if (toggles.length === 0)
return false;
return toggles.some((toggle: string) => {
// Specific coding for robots - extend later if needed
return (toggle === "dcs" && !unit.getControlled() && !unit.getHuman());
});
}
getMapMarkerVisibilityControls() {
return this.#mapMarkerVisibilityControls;
}
/* Event handlers */
#onClick(e: any) {
if (!this.#preventLeftClick) {
@@ -548,7 +559,7 @@ export class Map extends L.Map {
/* 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 (polyContains(e.latlng, coalitionArea)) {
if (coalitionArea.getSelected())
clickedCoalitionArea = coalitionArea;
else
@@ -560,19 +571,20 @@ export class Map extends L.Map {
}
}
else if (this.#state === MOVE_UNIT) {
if (!e.originalEvent.ctrlKey) {
getApp().getUnitsManager().clearDestinations();
if (!e.originalEvent.shiftKey) {
if (!e.originalEvent.ctrlKey) {
getApp().getUnitsManager().clearDestinations();
}
getApp().getUnitsManager().addDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, this.#shiftKey, this.#destinationGroupRotation)
this.#destinationGroupRotation = 0;
this.#destinationRotationCenter = null;
this.#computeDestinationRotation = false;
}
getApp().getUnitsManager().addDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, this.#shiftKey, this.#destinationGroupRotation)
this.#destinationGroupRotation = 0;
this.#destinationRotationCenter = null;
this.#computeDestinationRotation = false;
}
else {
this.setState(IDLE);
}
}
#onSelectionStart(e: any) {
@@ -616,7 +628,7 @@ export class Map extends L.Map {
units.forEach((unit: Unit) => {
unit.appendContextActions(contextActionSet, null, e.latlng);
})
if (Object.keys(contextActionSet.getContextActions()).length > 0) {
getApp().getMap().showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng);
getApp().getMap().getUnitContextMenu().setContextActions(contextActionSet);
@@ -626,6 +638,16 @@ export class Map extends L.Map {
}
#onMouseUp(e: any) {
if (this.#state === MOVE_UNIT && e.originalEvent.button == 2 && e.originalEvent.shiftKey) {
if (!e.originalEvent.ctrlKey) {
getApp().getUnitsManager().clearDestinations();
}
getApp().getUnitsManager().addDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, this.#shiftKey, this.#destinationGroupRotation)
this.#destinationGroupRotation = 0;
this.#destinationRotationCenter = null;
this.#computeDestinationRotation = false;
}
}
#onMouseMove(e: any) {
@@ -640,7 +662,7 @@ 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 (this.#state === COALITIONAREA_DRAW_POLYGON) {
else if (this.#state === COALITIONAREA_DRAW_POLYGON && e.latlng !== undefined) {
this.#drawingCursor.setLatLng(e.latlng);
/* Update the polygon being drawn with the current position of the mouse cursor */
this.getSelectedCoalitionArea()?.moveActiveVertex(e.latlng);
@@ -677,6 +699,7 @@ export class Map extends L.Map {
this.#isZooming = false;
}
/* */
#panToUnit(unit: Unit) {
var unitPosition = new L.LatLng(unit.getPosition().lat, unit.getPosition().lng);
this.setView(unitPosition, this.getZoom(), { animate: false });
@@ -691,10 +714,10 @@ export class Map extends L.Map {
#createUnitMarkerControlButtons() {
const unitVisibilityControls = <HTMLElement>document.getElementById("unit-visibility-control");
const makeTitle = (isProtected:boolean) => {
return ( isProtected ) ? "Unit type is protected and will ignore orders" : "Unit is NOT protected and will respond to orders";
const makeTitle = (isProtected: boolean) => {
return (isProtected) ? "Unit type is protected and will ignore orders" : "Unit is NOT protected and will respond to orders";
}
this.getMapMarkerControls().forEach( (control:MapMarkerControl) => {
this.getMapMarkerVisibilityControls().forEach((control: MapMarkerVisibilityControl) => {
const toggles = `["${control.toggles.join('","')}"]`;
const div = document.createElement("div");
div.className = control.protectable === true ? "protectable" : "";
@@ -707,7 +730,7 @@ export class Map extends L.Map {
`;
unitVisibilityControls.appendChild(div);
if ( control.protectable ) {
if (control.protectable) {
div.innerHTML += `
<button class="lock" ${control.isProtected ? "data-protected" : ""} title="${makeTitle(control.isProtected || false)}">
<img src="/resources/theme/images/buttons/other/lock-solid.svg" class="locked" />
@@ -715,10 +738,10 @@ export class Map extends L.Map {
</button>`;
const btn = <HTMLButtonElement>div.querySelector("button.lock");
btn.addEventListener("click", (ev:MouseEventInit) => {
btn.addEventListener("click", (ev: MouseEventInit) => {
control.isProtected = !control.isProtected;
btn.toggleAttribute("data-protected", control.isProtected);
btn.title = makeTitle( control.isProtected );
btn.title = makeTitle(control.isProtected);
document.dispatchEvent(new CustomEvent("toggleMarkerProtection", {
detail: {
"_element": btn,
@@ -732,28 +755,32 @@ export class Map extends L.Map {
unitVisibilityControls.querySelectorAll(`img[src$=".svg"]`).forEach(img => SVGInjector(img));
}
unitIsProtected(unit:Unit) {
const toggles = this.#mapMarkerControls.reduce((list, control:MapMarkerControl) => {
if (control.isProtected) {
list = list.concat(control.toggles);
}
return list;
}, [] as string[]);
if (toggles.length === 0)
return false;
return toggles.some((toggle:string) => {
// Specific coding for robots - extend later if needed
return (toggle === "dcs" && !unit.getControlled() && !unit.getHuman());
});
}
#deselectCoalitionAreas() {
#deselectSelectedCoalitionArea() {
this.getSelectedCoalitionArea()?.setSelected(false);
}
/* Cursors */
#updateCursor() {
/* If the ctrl key is being pressed or we are performing an area selection, show the default cursor */
if (this.#ctrlKey || this.#selecting) {
/* Hide all non default cursors */
this.#hideDestinationCursors();
this.#hideDrawingCursor();
this.#showDefaultCursor();
} else {
/* Hide all the unnecessary cursors depending on the active state */
if (this.#state !== IDLE) this.#hideDefaultCursor();
if (this.#state !== MOVE_UNIT) this.#hideDestinationCursors();
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 (this.#state === COALITIONAREA_DRAW_POLYGON) this.#showDrawingCursor();
}
}
#showDefaultCursor() {
document.getElementById(this.#ID)?.classList.remove("hidden-cursor");
}
@@ -766,48 +793,47 @@ export class Map extends L.Map {
const singleCursor = !this.#shiftKey;
const selectedUnitsCount = getApp().getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length;
if (singleCursor) {
if ( this.#destinationPreviewCursors.length != 1) {
this.#hideDestinationCursors();
var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false });
marker.addTo(this);
this.#destinationPreviewCursors = [marker];
}
this.#destinationPreviewHandleLine.removeFrom(this);
this.#destinationPreviewHandle.removeFrom(this);
this.#hideDestinationCursors();
}
else if (!singleCursor) {
while (this.#destinationPreviewCursors.length > selectedUnitsCount) {
this.removeLayer(this.#destinationPreviewCursors[0]);
this.#destinationPreviewCursors.splice(0, 1);
if (selectedUnitsCount > 1) {
while (this.#destinationPreviewCursors.length > selectedUnitsCount) {
this.removeLayer(this.#destinationPreviewCursors[0]);
this.#destinationPreviewCursors.splice(0, 1);
}
this.#destinationPreviewHandleLine.addTo(this);
this.#destinationPreviewHandle.addTo(this);
while (this.#destinationPreviewCursors.length < selectedUnitsCount) {
var cursor = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false });
cursor.addTo(this);
this.#destinationPreviewCursors.push(cursor);
}
this.#updateDestinationCursors();
}
this.#destinationPreviewHandleLine.addTo(this);
this.#destinationPreviewHandle.addTo(this);
while (this.#destinationPreviewCursors.length < selectedUnitsCount) {
var cursor = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false });
cursor.addTo(this);
this.#destinationPreviewCursors.push(cursor);
}
this.#updateDestinationCursors();
}
}
}
#updateDestinationCursors() {
const groupLatLng = this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : this.getMouseCoordinates();
if (this.#destinationPreviewCursors.length == 1)
this.#destinationPreviewCursors[0].setLatLng(this.getMouseCoordinates());
else {
Object.values(getApp().getUnitsManager().computeGroupDestination(groupLatLng, this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => {
if (idx < this.#destinationPreviewCursors.length)
this.#destinationPreviewCursors[idx].setLatLng(this.#shiftKey ? latlng : this.getMouseCoordinates());
})
};
const selectedUnitsCount = getApp().getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length;
if (selectedUnitsCount > 1) {
const groupLatLng = this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : this.getMouseCoordinates();
if (this.#destinationPreviewCursors.length == 1)
this.#destinationPreviewCursors[0].setLatLng(this.getMouseCoordinates());
else {
Object.values(getApp().getUnitsManager().computeGroupDestination(groupLatLng, this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => {
if (idx < this.#destinationPreviewCursors.length)
this.#destinationPreviewCursors[idx].setLatLng(this.#shiftKey ? latlng : this.getMouseCoordinates());
})
};
this.#destinationPreviewHandleLine.setLatLngs([groupLatLng, this.getMouseCoordinates()]);
this.#destinationPreviewHandle.setLatLng(this.getMouseCoordinates());
this.#destinationPreviewHandleLine.setLatLngs([groupLatLng, this.getMouseCoordinates()]);
this.#destinationPreviewHandle.setLatLng(this.getMouseCoordinates());
} else {
this.#hideDestinationCursors();
}
}
#hideDestinationCursors() {
@@ -838,34 +864,9 @@ export class Map extends L.Map {
this.#drawingCursor.removeFrom(this);
}
#updateCursor() {
/* If the ctrl key is being pressed or we are performing an area selection, show the default cursor */
if (this.#ctrlKey || this.#selecting) {
/* Hide all non default cursors */
this.#hideDestinationCursors();
this.#hideDrawingCursor();
this.#showDefaultCursor();
} else {
/* Hide all the unnecessary cursors depending on the active state */
if (this.#state !== IDLE) this.#hideDefaultCursor();
if (this.#state !== MOVE_UNIT) this.#hideDestinationCursors();
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 (this.#state === COALITIONAREA_DRAW_POLYGON) this.#showDrawingCursor();
}
}
#setVisibilityOption(option: string, ev: any) {
this.#visibilityOptions[option] = ev.currentTarget.checked;
document.dispatchEvent(new CustomEvent("mapVisibilityOptionsChanged"));
}
getMapMarkerControls() {
return this.#mapMarkerControls;
document.dispatchEvent(new CustomEvent("mapOptionsChanged"));
}
}

View File

@@ -35,6 +35,10 @@ export class MissionManager {
this.#commandModeErasDropdown = new Dropdown("command-mode-era-options", () => {});
}
/** Update location of bullseyes
*
* @param object <BulleyesData>
*/
updateBullseyes(data: BullseyesData) {
const commandMode = getApp().getMissionManager().getCommandModeOptions().commandMode;
for (let idx in data.bullseyes) {
@@ -56,6 +60,10 @@ export class MissionManager {
}
}
/** Update airbase information
*
* @param object <AirbasesData>
*/
updateAirbases(data: AirbasesData) {
for (let idx in data.airbases) {
var airbase = data.airbases[idx]
@@ -75,6 +83,10 @@ export class MissionManager {
}
}
/** Update mission information
*
* @param object <MissionData>
*/
updateMission(data: MissionData) {
if (data.mission) {
@@ -109,30 +121,63 @@ export class MissionManager {
}
}
/** Get the bullseyes set in this theatre
*
* @returns object
*/
getBullseyes() {
return this.#bullseyes;
}
/** Get the airbases in this theatre
*
* @returns object
*/
getAirbases() {
return this.#airbases;
}
/** Get the options/settings as set in the command mode
*
* @returns object
*/
getCommandModeOptions() {
return this.#commandModeOptions;
}
/** Get the current date and time of the mission (based on local time)
*
* @returns object
*/
getDateAndTime() {
return this.#dateAndTime;
}
/**
* Get the number of seconds left of setup time
* @returns number
*/
getRemainingSetupTime() {
return this.#remainingSetupTime;
}
/** Get an object with the coalitions in it
*
* @returns object
*/
getCoalitions() {
return this.#coalitions;
}
/** Get the current theatre (map) name
*
* @returns string
*/
getTheatre() {
return this.#theatre;
}
getAvailableSpawnPoints() {
if (this.getCommandModeOptions().commandMode === GAME_MASTER)
return Infinity;

View File

@@ -17,6 +17,7 @@ import { WeaponsManager } from "./weapon/weaponsmanager";
import { Manager } from "./other/manager";
import { SVGInjector } from "@tanem/svg-injector";
import { ServerManager } from "./server/servermanager";
import { sha256 } from 'js-sha256';
import { BLUE_COMMANDER, FILL_SELECTED_RING, GAME_MASTER, HIDE_UNITS_SHORT_RANGE_RINGS, RED_COMMANDER, SHOW_UNITS_ACQUISITION_RINGS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNIT_LABELS } from "./constants/constants";
import { aircraftDatabase } from "./unit/databases/aircraftdatabase";
@@ -28,9 +29,13 @@ import { UnitListPanel } from "./panels/unitlistpanel";
import { ContextManager } from "./context/contextmanager";
import { Context } from "./context/context";
var VERSION = "v0.4.9-alpha-rc1";
var DEBUG = false;
export class OlympusApp {
/* Global data */
#activeCoalition: string = "blue";
#latestVersion: string|undefined = undefined;
/* Main leaflet map, extended by custom methods */
#map: Map | null = null;
@@ -49,7 +54,6 @@ export class OlympusApp {
#weaponsManager: WeaponsManager | null = null;
constructor() {
}
// TODO add checks on null
@@ -177,7 +181,6 @@ export class OlympusApp {
start() {
/* Initialize base functionalitites */
this.#contextManager = new ContextManager();
this.#contextManager.add( "olympus", {} );
@@ -231,11 +234,39 @@ export class OlympusApp {
this.#setupEvents();
/* Set the splash background image to a random image */
var splashScreen = document.getElementById("splash-screen");
if (splashScreen) {
let i = Math.round(Math.random() * 7 + 1);
let splashScreen = document.getElementById("splash-screen") as HTMLElement;
let i = Math.round(Math.random() * 7 + 1);
new Promise((resolve, reject) => {
const image = new Image();
image.addEventListener('load', resolve);
image.addEventListener('error', resolve);
image.src = `/resources/theme/images/splash/${i}.jpg`;
}).then(() => {
splashScreen.style.backgroundImage = `url('/resources/theme/images/splash/${i}.jpg')`;
}
let loadingScreen = document.getElementById("loading-screen") as HTMLElement;
loadingScreen.classList.add("fade-out");
window.setInterval(() => { loadingScreen.classList.add("hide"); }, 1000);
})
/* Check if we are running the latest version */
const request = new Request("https://raw.githubusercontent.com/Pax1601/DCSOlympus/main/version.json");
fetch(request).then((response) => {
if (response.status === 200) {
return response.json();
} else {
throw new Error("Error connecting to Github to retrieve latest version");
}
}).then((res) => {
this.#latestVersion = res["version"];
const latestVersionSpan = document.getElementById("latest-version") as HTMLElement;
if (latestVersionSpan) {
latestVersionSpan.innerHTML = this.#latestVersion ?? "Unknown";
if (this.#latestVersion !== VERSION) {
latestVersionSpan.classList.add("new-version");
}
}
})
}
#setupEvents() {
@@ -268,7 +299,7 @@ export class OlympusApp {
shortcutManager.addKeyboardShortcut("toggleDemo", {
"altKey": false,
"callback": () => {
this.getServerManager().toggleDemoEnabled();
if (DEBUG === true) this.getServerManager().toggleDemoEnabled();
},
"code": "KeyT",
"context": "olympus",
@@ -400,6 +431,7 @@ export class OlympusApp {
// TODO: move from here in dedicated class
document.addEventListener("closeDialog", (ev: CustomEventInit) => {
ev.detail._element.closest(".ol-dialog").classList.add("hide");
document.getElementById("gray-out")?.classList.toggle("hide", true);
});
/* Try and connect with the Olympus REST server */
@@ -408,8 +440,9 @@ export class OlympusApp {
loginForm.addEventListener("submit", (ev:SubmitEvent) => {
ev.preventDefault();
ev.stopPropagation();
var hash = sha256.create();
const username = (loginForm.querySelector("#username") as HTMLInputElement).value;
const password = (loginForm.querySelector("#password") as HTMLInputElement).value;
const password = hash.update((loginForm.querySelector("#password") as HTMLInputElement).value).hex();
// Update the user credentials
this.getServerManager().setCredentials(username, password);
@@ -423,7 +456,6 @@ export class OlympusApp {
console.error("Unable to find login form.");
}
/* Reload the page, used to mimic a restart of the app */
document.addEventListener("reloadPage", () => {
location.reload();

View File

@@ -348,7 +348,7 @@ export function getMarkerCategoryByName(name: string) {
else if (navyUnitDatabase.getByName(name) != null)
return "navyunit";
else
return "groundunit-other"; // TODO add other unit types
return "aircraft"; // TODO add other unit types
}
export function getUnitDatabaseByCategory(category: string) {
@@ -434,14 +434,24 @@ export function convertDateAndTimeToDate(dateAndTime: DateAndTime) {
return new Date(year, month, date.Day, time.h, time.m, time.s);
}
export function createCheckboxOption(value: string, text: string, checked: boolean = true, callback: CallableFunction = (ev: any) => {}) {
export function createCheckboxOption(value: string, text: string, checked: boolean = true, callback: CallableFunction = (ev: any) => {}, options?:any) {
options = {
"disabled": false,
"name": "",
"readOnly": false,
...options
};
var div = document.createElement("div");
div.classList.add("ol-checkbox");
var label = document.createElement("label");
label.title = text;
var input = document.createElement("input");
input.type = "checkbox";
input.checked = checked;
input.checked = checked;
input.name = options.name;
input.disabled = options.disabled;
input.readOnly = options.readOnly;
input.value = value;
var span = document.createElement("span");
span.innerText = value;
label.appendChild(input);

View File

@@ -2,16 +2,13 @@ import { SVGInjector } from "@tanem/svg-injector";
import { getApp } from "..";
import { Dropdown } from "../controls/dropdown";
import { Slider } from "../controls/slider";
import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
import { Unit } from "../unit/unit";
import { Panel } from "./panel";
import { Switch } from "../controls/switch";
import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, emissionsCountermeasuresDescriptions, maxAltitudeValues, maxSpeedValues, minAltitudeValues, minSpeedValues, reactionsToThreat, reactionsToThreatDescriptions, shotsIntensityDescriptions, shotsScatterDescriptions, speedIncrements } from "../constants/constants";
import { ftToM, knotsToMs, mToFt, msToKnots } from "../other/utils";
import { GeneralSettings, Radio, TACAN } from "../interfaces";
import { PrimaryToolbar } from "../toolbars/primarytoolbar";
import { ContextActionSet } from "../unit/contextactionset";
import { ContextAction } from "../unit/contextaction";
export class UnitControlPanel extends Panel {
#altitudeSlider: Slider;
@@ -36,36 +33,36 @@ export class UnitControlPanel extends Panel {
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
constructor(ID: string) {
super(ID);
/* Unit control sliders */
this.#altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => { getApp().getUnitsManager().setAltitude(ftToM(value)); });
this.#altitudeTypeSwitch = new Switch("altitude-type-switch", (value: boolean) => { getApp().getUnitsManager().setAltitudeType(value? "ASL": "AGL"); });
this.#altitudeTypeSwitch = new Switch("altitude-type-switch", (value: boolean) => { getApp().getUnitsManager().setAltitudeType(value ? "ASL" : "AGL"); });
this.#speedSlider = new Slider("speed-slider", 0, 100, "kts", (value: number) => { getApp().getUnitsManager().setSpeed(knotsToMs(value)); });
this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getApp().getUnitsManager().setSpeedType(value? "CAS": "GS"); });
this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getApp().getUnitsManager().setSpeedType(value ? "CAS" : "GS"); });
/* Option buttons */
// Reversing the ROEs so that the least "aggressive" option is always on the left
this.#optionButtons["ROE"] = ROEs.slice(0).reverse().map((option: string, index: number) => {
return this.#createOptionButton(option, `roe/${option.toLowerCase()}.svg`, ROEDescriptions.slice(0).reverse()[index], () => { getApp().getUnitsManager().setROE(option); });
}).filter((button: HTMLButtonElement, index: number) => {return ROEs[index] !== "";});
}).filter((button: HTMLButtonElement, index: number) => { return ROEs[index] !== ""; });
this.#optionButtons["reactionToThreat"] = reactionsToThreat.map((option: string, index: number) => {
return this.#createOptionButton(option, `threat/${option.toLowerCase()}.svg`, reactionsToThreatDescriptions[index],() => { getApp().getUnitsManager().setReactionToThreat(option); });
return this.#createOptionButton(option, `threat/${option.toLowerCase()}.svg`, reactionsToThreatDescriptions[index], () => { getApp().getUnitsManager().setReactionToThreat(option); });
});
this.#optionButtons["emissionsCountermeasures"] = emissionsCountermeasures.map((option: string, index: number) => {
return this.#createOptionButton(option, `emissions/${option.toLowerCase()}.svg`, emissionsCountermeasuresDescriptions[index],() => { getApp().getUnitsManager().setEmissionsCountermeasures(option); });
return this.#createOptionButton(option, `emissions/${option.toLowerCase()}.svg`, emissionsCountermeasuresDescriptions[index], () => { getApp().getUnitsManager().setEmissionsCountermeasures(option); });
});
this.#optionButtons["shotsScatter"] = [1, 2, 3].map((option: number, index: number) => {
return this.#createOptionButton(option.toString(), `scatter/${option.toString().toLowerCase()}.svg`, shotsScatterDescriptions[index],() => { getApp().getUnitsManager().setShotsScatter(option); });
return this.#createOptionButton(option.toString(), `scatter/${option.toString().toLowerCase()}.svg`, shotsScatterDescriptions[index], () => { getApp().getUnitsManager().setShotsScatter(option); });
});
this.#optionButtons["shotsIntensity"] = [1, 2, 3].map((option: number, index: number) => {
return this.#createOptionButton(option.toString(), `intensity/${option.toString().toLowerCase()}.svg`, shotsIntensityDescriptions[index],() => { getApp().getUnitsManager().setShotsIntensity(option); });
return this.#createOptionButton(option.toString(), `intensity/${option.toString().toLowerCase()}.svg`, shotsIntensityDescriptions[index], () => { getApp().getUnitsManager().setShotsIntensity(option); });
});
this.getElement().querySelector("#roe-buttons-container")?.append(...this.#optionButtons["ROE"]);
@@ -107,38 +104,58 @@ export class UnitControlPanel extends Panel {
getApp().getUnitsManager().setOperateAs(value);
});
/* Mouseover of (?) highlights activation buttons */
const operateAsQuestionMark = <HTMLElement>this.getElement().querySelector("#operate-as h4 img");
operateAsQuestionMark.addEventListener("mouseover", () => {
document.querySelectorAll(`#rapid-controls button.scenic-action`).forEach((btn: Element) => {
btn.classList.add(`pulse`);
});
});
operateAsQuestionMark.addEventListener("mouseout", () => {
document.querySelectorAll(`#rapid-controls button.scenic-action.pulse`).forEach((btn: Element) => {
btn.classList.remove(`pulse`);
});
});
/* Advanced settings dialog */
this.#advancedSettingsDialog = <HTMLElement> document.querySelector("#advanced-settings-dialog");
this.#advancedSettingsDialog = <HTMLElement>document.querySelector("#advanced-settings-dialog");
/* Advanced settings dropdowns */
this.#TACANXYDropdown = new Dropdown("TACAN-XY", () => {});
this.#TACANXYDropdown = new Dropdown("TACAN-XY", () => { });
this.#TACANXYDropdown.setOptions(["X", "Y"]);
this.#radioDecimalsDropdown = new Dropdown("radio-decimals", () => {});
this.#radioDecimalsDropdown = new Dropdown("radio-decimals", () => { });
this.#radioDecimalsDropdown.setOptions([".000", ".250", ".500", ".750"]);
this.#radioCallsignDropdown = new Dropdown("radio-callsign", () => {});
this.#radioCallsignDropdown = new Dropdown("radio-callsign", () => { });
this.#deleteDropdown = new Dropdown("delete-options", () => { });
/* Events and timer */
window.setInterval(() => {this.update();}, 25);
window.setInterval(() => { this.update(); }, 25);
document.addEventListener("unitsSelection", (e: CustomEvent<Unit[]>) => {
this.show();
document.addEventListener("unitsSelection", (e: CustomEvent<Unit[]>) => {
this.show();
this.addButtons();
this.#updateRapidControls();
this.#updateRapidControls();
});
document.addEventListener("clearSelection", () => {
document.addEventListener("clearSelection", () => {
this.hide();
this.#updateRapidControls();
this.#updateRapidControls();
});
document.addEventListener("applyAdvancedSettings", () => {this.#applyAdvancedSettings();})
document.addEventListener("applyAdvancedSettings", () => { this.#applyAdvancedSettings(); })
document.addEventListener("showAdvancedSettings", () => {
this.#updateAdvancedSettingsDialog(getApp().getUnitsManager().getSelectedUnits());
this.#advancedSettingsDialog.classList.remove("hide");
});
/* This is for when a ctrl-click happens on the map for deselection and we need to remove the selected unit from the panel */
document.addEventListener( "unitsDeselection", ( ev:CustomEventInit ) => {
this.getElement().querySelector( `button[data-unit-id="${ev.detail.ID}"]` )?.remove();
this.#updateRapidControls();
document.addEventListener("unitsDeselection", (ev: CustomEventInit) => {
if (ev.detail.length > 0) {
this.show();
this.addButtons();
this.#updateRapidControls();
} else {
this.hide();
this.#updateRapidControls();
}
});
window.addEventListener("resize", (e: any) => this.#calculateMaxHeight());
@@ -146,16 +163,16 @@ export class UnitControlPanel extends Panel {
const element = document.getElementById("toolbar-container");
if (element)
new ResizeObserver(() => this.#calculateTop()).observe(element);
this.#calculateMaxHeight()
this.hide();
}
show() {
const context = getApp().getCurrentContext();
if ( !context.getUseUnitControlPanel() )
if (!context.getUseUnitControlPanel())
return;
super.show();
this.#speedTypeSwitch.resetExpectedValue();
this.#altitudeTypeSwitch.resetExpectedValue();
@@ -163,6 +180,7 @@ export class UnitControlPanel extends Panel {
this.#AWACSSwitch.resetExpectedValue();
this.#onOffSwitch.resetExpectedValue();
this.#followRoadsSwitch.resetExpectedValue();
this.#operateAsSwitch.resetExpectedValue();
this.#altitudeSlider.resetExpectedValue();
this.#speedSlider.resetExpectedValue();
this.#calculateMaxHeight();
@@ -171,26 +189,26 @@ export class UnitControlPanel extends Panel {
addButtons() {
this.#units = getApp().getUnitsManager().getSelectedUnits();
this.#selectedUnitsTypes = getApp().getUnitsManager().getSelectedUnitsCategories();
if (this.#units.length < 20) {
this.getElement().querySelector("#selected-units-container")?.replaceChildren(...this.#units.map((unit: Unit, index: number) => {
var button = document.createElement("button");
var callsign = unit.getUnitName() || "";
var label = unit.getDatabase()?.getByName(unit.getName())?.label || unit.getName();
button.setAttribute("data-unit-id", "" + unit.ID );
button.setAttribute("data-unit-id", "" + unit.ID);
button.setAttribute("data-label", label);
button.setAttribute("data-callsign", callsign);
button.setAttribute("data-coalition", unit.getCoalition());
button.classList.add("pill", "highlight-coalition")
button.addEventListener("click", ( ev:MouseEventInit ) => {
button.addEventListener("click", (ev: MouseEventInit) => {
// Ctrl-click deselection
if ( ev.ctrlKey === true && ev.shiftKey === false && ev.altKey === false ) {
getApp().getUnitsManager().deselectUnit( unit.ID );
if (ev.ctrlKey === true && ev.shiftKey === false && ev.altKey === false) {
getApp().getUnitsManager().deselectUnit(unit.ID);
button.remove();
// Deselect all
// Deselect all
} else {
getApp().getUnitsManager().deselectAllUnits();
getApp().getUnitsManager().selectUnit(unit.ID, true);
@@ -206,14 +224,14 @@ export class UnitControlPanel extends Panel {
}
update() {
if (this.getVisible()){
if (this.getVisible()) {
const element = this.getElement();
if (element != null && this.#units.length > 0) {
/* Toggle visibility of control elements */
var isTanker = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.isTanker();});
var isAWACS = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.isAWACS();});
var isActiveTanker = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getIsActiveTanker()});
var isActiveAWACAS = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getIsActiveAWACS()});
var isTanker = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.isTanker(); });
var isAWACS = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.isAWACS(); });
var isActiveTanker = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getIsActiveTanker() });
var isActiveAWACAS = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getIsActiveAWACS() });
element.toggleAttribute("data-show-categories-tooltip", this.#selectedUnitsTypes.length > 1);
element.toggleAttribute("data-show-speed-slider", this.#selectedUnitsTypes.length == 1);
@@ -221,37 +239,37 @@ export class UnitControlPanel extends Panel {
element.toggleAttribute("data-show-roe", !isTanker && !isAWACS);
element.toggleAttribute("data-show-threat", (this.#selectedUnitsTypes.includes("Aircraft") || this.#selectedUnitsTypes.includes("Helicopter")) && !(this.#selectedUnitsTypes.includes("GroundUnit") || this.#selectedUnitsTypes.includes("NavyUnit")));
element.toggleAttribute("data-show-emissions-countermeasures", (this.#selectedUnitsTypes.includes("Aircraft") || this.#selectedUnitsTypes.includes("Helicopter")) && !(this.#selectedUnitsTypes.includes("GroundUnit") || this.#selectedUnitsTypes.includes("NavyUnit")));
element.toggleAttribute("data-show-shots-scatter", this.#selectedUnitsTypes.includes("GroundUnit")); //TODO: more refined
element.toggleAttribute("data-show-shots-intensity", this.#selectedUnitsTypes.includes("GroundUnit")); //TODO: more refined
element.toggleAttribute("data-show-tanker-button", getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.isTanker();}) === true);
element.toggleAttribute("data-show-AWACS-button", getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.isAWACS();}) === true);
element.toggleAttribute("data-show-shots-scatter", this.#selectedUnitsTypes.length == 1 && this.#selectedUnitsTypes.includes("GroundUnit")); //TODO: more refined
element.toggleAttribute("data-show-shots-intensity", this.#selectedUnitsTypes.length == 1 && this.#selectedUnitsTypes.includes("GroundUnit")); //TODO: more refined
element.toggleAttribute("data-show-tanker-button", getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.isTanker(); }) === true);
element.toggleAttribute("data-show-AWACS-button", getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.isAWACS(); }) === true);
element.toggleAttribute("data-show-on-off", (this.#selectedUnitsTypes.includes("GroundUnit") || this.#selectedUnitsTypes.includes("NavyUnit")) && !(this.#selectedUnitsTypes.includes("Aircraft") || this.#selectedUnitsTypes.includes("Helicopter")));
element.toggleAttribute("data-show-follow-roads", (this.#selectedUnitsTypes.length == 1 && this.#selectedUnitsTypes.includes("GroundUnit")));
element.toggleAttribute("data-show-operate-as", getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) === "neutral");
element.toggleAttribute("data-show-operate-as", this.#selectedUnitsTypes.length == 1 && this.#selectedUnitsTypes.includes("GroundUnit") && getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getCoalition() }) === "neutral");
if (this.#units.length == 1) {
if (isAWACS)
if (isAWACS)
element.toggleAttribute("data-show-advanced-settings-button", isActiveAWACAS);
else if (isTanker)
else if (isTanker)
element.toggleAttribute("data-show-advanced-settings-button", isActiveTanker);
else
else
element.toggleAttribute("data-show-advanced-settings-button", true);
}
if (this.#selectedUnitsTypes.length == 1) {
/* Flight controls */
var desiredAltitude = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitude()});
var desiredAltitudeType = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitudeType()});
var desiredSpeed = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeed()});
var desiredSpeedType = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeedType()});
var isActiveTanker = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getIsActiveTanker()});
var isActiveAWACAS = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getIsActiveAWACS()});
var onOff = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getOnOff()});
var followRoads = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getFollowRoads()});
var operateAs = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getOperateAs()});
this.#altitudeTypeSwitch.setValue(desiredAltitudeType != undefined? desiredAltitudeType == "ASL": undefined, false);
this.#speedTypeSwitch.setValue(desiredSpeedType != undefined? desiredSpeedType == "CAS": undefined, false);
var desiredAltitude = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getDesiredAltitude() });
var desiredAltitudeType = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getDesiredAltitudeType() });
var desiredSpeed = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getDesiredSpeed() });
var desiredSpeedType = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getDesiredSpeedType() });
var isActiveTanker = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getIsActiveTanker() });
var isActiveAWACAS = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getIsActiveAWACS() });
var onOff = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getOnOff() });
var followRoads = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getFollowRoads() });
var operateAs = getApp().getUnitsManager().getSelectedUnitsVariable((unit: Unit) => { return unit.getOperateAs() });
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]]);
@@ -296,7 +314,7 @@ export class UnitControlPanel extends Panel {
this.#AWACSSwitch.setValue(isActiveAWACAS, false);
this.#onOffSwitch.setValue(onOff, false);
this.#followRoadsSwitch.setValue(followRoads, false);
this.#operateAsSwitch.setValue(operateAs? operateAs === "blue": undefined, false);
this.#operateAsSwitch.setValue(operateAs ? operateAs === "blue" : undefined, false);
}
}
}
@@ -305,8 +323,8 @@ export class UnitControlPanel extends Panel {
var contextActionSet = new ContextActionSet();
var units = getApp().getUnitsManager().getSelectedUnits();
var showAltitudeChange = units.some((unit: Unit) => {return ["Aircraft", "Helicopter"].includes(unit.getCategory());});
this.getElement().querySelector("#climb")?.classList.toggle("hide", !showAltitudeChange);
var showAltitudeChange = units.some((unit: Unit) => { return ["Aircraft", "Helicopter"].includes(unit.getCategory()); });
this.getElement().querySelector("#climb")?.classList.toggle("hide", !showAltitudeChange);
this.getElement().querySelector("#descend")?.classList.toggle("hide", !showAltitudeChange);
units.forEach((unit: Unit) => {
@@ -324,6 +342,8 @@ export class UnitControlPanel extends Panel {
let button = document.createElement("button");
button.title = contextAction.getDescription();
button.classList.add("ol-button", "unit-action-button");
if (contextAction.getOptions().isScenic)
button.classList.add("scenic-action");
button.id = key;
rapidControlsContainer.appendChild(button);
button.onclick = () => {
@@ -332,10 +352,8 @@ export class UnitControlPanel extends Panel {
}
}
#updateAdvancedSettingsDialog(units: Unit[])
{
if (units.length == 1)
{
#updateAdvancedSettingsDialog(units: Unit[]) {
if (units.length == 1) {
/* HTML Elements */
const unitNameEl = this.#advancedSettingsDialog.querySelector("#unit-name") as HTMLElement;
const prohibitJettisonCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-jettison-checkbox")?.querySelector("input") as HTMLInputElement;
@@ -348,7 +366,7 @@ export class UnitControlPanel extends Panel {
const TACANCallsignInput = this.#advancedSettingsDialog.querySelector("#tacan-callsign")?.querySelector("input") as HTMLInputElement;
const radioMhzInput = this.#advancedSettingsDialog.querySelector("#radio-mhz")?.querySelector("input") as HTMLInputElement;
const radioCallsignNumberInput = this.#advancedSettingsDialog.querySelector("#radio-callsign-number")?.querySelector("input") as HTMLInputElement;
const unit = units[0];
const isTanker = unit.isTanker();
const isAWACS = unit.isAWACS();
@@ -385,7 +403,7 @@ export class UnitControlPanel extends Panel {
radioMhzInput.value = String(radioMHz);
radioCallsignNumberInput.value = String(unit.getRadio().callsignNumber);
this.#radioDecimalsDropdown.setValue("." + radioDecimals);
if (isTanker) /* Set tanker specific options */
this.#radioCallsignDropdown.setOptions(["Texaco", "Arco", "Shell"]);
else if (isAWACS) /* Set AWACS specific options */
@@ -399,8 +417,7 @@ export class UnitControlPanel extends Panel {
}
}
#applyAdvancedSettings()
{
#applyAdvancedSettings() {
/* HTML Elements */
const prohibitJettisonCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-jettison-checkbox")?.querySelector("input") as HTMLInputElement;
const prohibitAfterburnerCheckbox = this.#advancedSettingsDialog.querySelector("#prohibit-afterburner-checkbox")?.querySelector("input") as HTMLInputElement;
@@ -416,7 +433,7 @@ export class UnitControlPanel extends Panel {
/* TACAN */
const TACAN: TACAN = {
isOn: TACANCheckbox.checked? true: false,
isOn: TACANCheckbox.checked ? true : false,
channel: Number(TACANChannelInput.value),
XY: this.#TACANXYDropdown.getValue(),
callsign: TACANCallsignInput.value as string
@@ -428,18 +445,18 @@ export class UnitControlPanel extends Panel {
const radio: Radio = {
frequency: (radioMHz * 1000 + Number(radioDecimals.substring(1))) * 1000,
callsign: this.#radioCallsignDropdown.getIndex() + 1,
callsignNumber: Number(radioCallsignNumberInput.value)
callsignNumber: Number(radioCallsignNumberInput.value)
}
/* General settings */
const generalSettings: GeneralSettings = {
prohibitJettison: prohibitJettisonCheckbox.checked? true: false,
prohibitAfterburner: prohibitAfterburnerCheckbox.checked? true: false,
prohibitAA: prohibitAACheckbox.checked? true: false,
prohibitAG: prohibitAGCheckbox.checked? true: false,
prohibitAirWpn: prohibitAirWpnCheckbox.checked? true: false
prohibitJettison: prohibitJettisonCheckbox.checked ? true : false,
prohibitAfterburner: prohibitAfterburnerCheckbox.checked ? true : false,
prohibitAA: prohibitAACheckbox.checked ? true : false,
prohibitAG: prohibitAGCheckbox.checked ? true : false,
prohibitAirWpn: prohibitAirWpnCheckbox.checked ? true : false
}
/* Send command and close */
var units = getApp().getUnitsManager().getSelectedUnits();
// TODO: split setAdvancedOptions into setIsTanker, setIsAWACS, setAdvancedOptions

View File

@@ -12,14 +12,16 @@ export class ServerManager {
#connected: boolean = false;
#paused: boolean = false;
#REST_ADDRESS = "http://localhost:30000/olympus";
#DEMO_ADDRESS = window.location.href + "demo";
#DEMO_ADDRESS = window.location.href.split('?')[0] + "demo"; /* Remove query parameters */
#username = "";
#password = "";
#sessionHash: string | null = null;
#lastUpdateTimes: {[key: string]: number} = {}
#lastUpdateTimes: { [key: string]: number } = {}
#demoEnabled = false;
#previousMissionElapsedTime:number = 0; // Track if mission elapsed time is increasing (i.e. is the server paused)
#previousMissionElapsedTime: number = 0; // Track if mission elapsed time is increasing (i.e. is the server paused)
#serverIsPaused: boolean = false;
#intervals: number[] = [];
#requests: { [key: string]: XMLHttpRequest } = {};
constructor() {
this.#lastUpdateTimes[UNITS_URI] = Date.now();
@@ -39,9 +41,20 @@ export class ServerManager {
this.#password = newPassword;
}
GET(callback: CallableFunction, uri: string, options?: ServerRequestOptions, responseType?: string) {
GET(callback: CallableFunction, uri: string, options?: ServerRequestOptions, responseType: string = 'text', force: boolean = false) {
var xmlHttp = new XMLHttpRequest();
/* If a request on this uri is still pending (meaning it's not done or did not yet fail), skip the request, to avoid clogging the TCP workers */
/* If we are forcing the request we don't care if one already exists, just send it. CAREFUL: this makes sense only for low frequency requests, like refreshes, when we
are reasonably confident any previous request will be done before we make a new one on the same URI. */
if (uri in this.#requests && this.#requests[uri].readyState !== 4 && !force) {
console.warn(`GET request on ${uri} URI still pending, skipping...`);
return;
}
if (!force)
this.#requests[uri] = xmlHttp;
/* Assemble the request options string */
var optionsString = '';
if (options?.time != undefined)
@@ -82,9 +95,11 @@ export class ServerManager {
this.setConnected(false);
}
};
xmlHttp.onerror = (res) => {
console.error("An error occurred during the XMLHttpRequest");
this.setConnected(false);
xmlHttp.onreadystatechange = (res) => {
if (xmlHttp.readyState == 4 && xmlHttp.status === 0) {
console.error("An error occurred during the XMLHttpRequest");
this.setConnected(false);
}
};
xmlHttp.send(null);
}
@@ -104,7 +119,7 @@ export class ServerManager {
getConfig(callback: CallableFunction) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", window.location.href + "config", true);
xmlHttp.open("GET", window.location.href.split('?')[0] + "config", true);
xmlHttp.onload = function (e) {
var data = JSON.parse(xmlHttp.responseText);
callback(data);
@@ -129,7 +144,7 @@ export class ServerManager {
}
getLogs(callback: CallableFunction, refresh: boolean = false) {
this.GET(callback, LOGS_URI, { time: refresh ? 0 : this.#lastUpdateTimes[LOGS_URI]});
this.GET(callback, LOGS_URI, { time: refresh ? 0 : this.#lastUpdateTimes[LOGS_URI] }, 'text', refresh);
}
getMission(callback: CallableFunction) {
@@ -137,66 +152,66 @@ export class ServerManager {
}
getUnits(callback: CallableFunction, refresh: boolean = false) {
this.GET(callback, UNITS_URI, { time: refresh ? 0 : this.#lastUpdateTimes[UNITS_URI] }, 'arraybuffer');
this.GET(callback, UNITS_URI, { time: refresh ? 0 : this.#lastUpdateTimes[UNITS_URI] }, 'arraybuffer', refresh);
}
getWeapons(callback: CallableFunction, refresh: boolean = false) {
this.GET(callback, WEAPONS_URI, { time: refresh ? 0 : this.#lastUpdateTimes[WEAPONS_URI] }, 'arraybuffer');
this.GET(callback, WEAPONS_URI, { time: refresh ? 0 : this.#lastUpdateTimes[WEAPONS_URI] }, 'arraybuffer', refresh);
}
isCommandExecuted(callback: CallableFunction, commandHash: string) {
this.GET(callback, COMMANDS_URI, { commandHash: commandHash});
this.GET(callback, COMMANDS_URI, { commandHash: commandHash });
}
addDestination(ID: number, path: any, callback: CallableFunction = () => {}) {
addDestination(ID: number, path: any, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "path": path }
var data = { "setPath": command }
this.PUT(data, callback);
}
spawnSmoke(color: string, latlng: LatLng, callback: CallableFunction = () => {}) {
spawnSmoke(color: string, latlng: LatLng, callback: CallableFunction = () => { }) {
var command = { "color": color, "location": latlng };
var data = { "smoke": command }
this.PUT(data, callback);
}
spawnExplosion(intensity: number, explosionType: string, latlng: LatLng, callback: CallableFunction = () => {}) {
spawnExplosion(intensity: number, explosionType: string, latlng: LatLng, callback: CallableFunction = () => { }) {
var command = { "explosionType": explosionType, "intensity": intensity, "location": latlng };
var data = { "explosion": command }
this.PUT(data, callback);
}
spawnAircrafts(units: any, coalition: string, airbaseName: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
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 }
this.PUT(data, callback);
}
spawnHelicopters(units: any, coalition: string, airbaseName: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
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 }
this.PUT(data, callback);
}
spawnGroundUnits(units: any, coalition: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
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 }
this.PUT(data, callback);
}
spawnNavyUnits(units: any, coalition: string, country: string, immediate: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
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 }
this.PUT(data, callback);
}
attackUnit(ID: number, targetID: number, callback: CallableFunction = () => {}) {
attackUnit(ID: number, targetID: number, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "targetID": targetID };
var data = { "attackUnit": command }
this.PUT(data, callback);
}
followUnit(ID: number, targetID: number, offset: { "x": number, "y": number, "z": number }, callback: CallableFunction = () => {}) {
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
@@ -206,170 +221,172 @@ export class ServerManager {
this.PUT(data, callback);
}
cloneUnits(units: {ID: number, location: LatLng}[], deleteOriginal: boolean, spawnPoints: number, callback: CallableFunction = () => {}) {
cloneUnits(units: { ID: number, location: LatLng }[], deleteOriginal: boolean, spawnPoints: number, callback: CallableFunction = () => { }) {
var command = { "units": units, "deleteOriginal": deleteOriginal, "spawnPoints": spawnPoints };
var data = { "cloneUnits": command }
this.PUT(data, callback);
}
deleteUnit(ID: number, explosion: boolean, explosionType: string, immediate: boolean, callback: CallableFunction = () => {}) {
deleteUnit(ID: number, explosion: boolean, explosionType: string, immediate: boolean, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "explosion": explosion, "explosionType": explosionType, "immediate": immediate };
var data = { "deleteUnit": command }
this.PUT(data, callback);
}
landAt(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
landAt(ID: number, latlng: LatLng, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "location": latlng };
var data = { "landAt": command }
this.PUT(data, callback);
}
changeSpeed(ID: number, speedChange: string, callback: CallableFunction = () => {}) {
changeSpeed(ID: number, speedChange: string, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "change": speedChange }
var data = { "changeSpeed": command }
this.PUT(data, callback);
}
setSpeed(ID: number, speed: number, callback: CallableFunction = () => {}) {
setSpeed(ID: number, speed: number, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "speed": speed }
var data = { "setSpeed": command }
this.PUT(data, callback);
}
setSpeedType(ID: number, speedType: string, callback: CallableFunction = () => {}) {
setSpeedType(ID: number, speedType: string, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "speedType": speedType }
var data = { "setSpeedType": command }
this.PUT(data, callback);
}
changeAltitude(ID: number, altitudeChange: string, callback: CallableFunction = () => {}) {
changeAltitude(ID: number, altitudeChange: string, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "change": altitudeChange }
var data = { "changeAltitude": command }
this.PUT(data, callback);
}
setAltitudeType(ID: number, altitudeType: string, callback: CallableFunction = () => {}) {
setAltitudeType(ID: number, altitudeType: string, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "altitudeType": altitudeType }
var data = { "setAltitudeType": command }
this.PUT(data, callback);
}
setAltitude(ID: number, altitude: number, callback: CallableFunction = () => {}) {
setAltitude(ID: number, altitude: number, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "altitude": altitude }
var data = { "setAltitude": command }
this.PUT(data, callback);
}
createFormation(ID: number, isLeader: boolean, wingmenIDs: number[], callback: CallableFunction = () => {}) {
createFormation(ID: number, isLeader: boolean, wingmenIDs: number[], callback: CallableFunction = () => { }) {
var command = { "ID": ID, "wingmenIDs": wingmenIDs, "isLeader": isLeader }
var data = { "setLeader": command }
this.PUT(data, callback);
}
setROE(ID: number, ROE: string, callback: CallableFunction = () => {}) {
setROE(ID: number, ROE: string, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "ROE": ROEs.indexOf(ROE) }
var data = { "setROE": command }
this.PUT(data, callback);
}
setReactionToThreat(ID: number, reactionToThreat: string, callback: CallableFunction = () => {}) {
setReactionToThreat(ID: number, reactionToThreat: string, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "reactionToThreat": reactionsToThreat.indexOf(reactionToThreat) }
var data = { "setReactionToThreat": command }
this.PUT(data, callback);
}
setEmissionsCountermeasures(ID: number, emissionCountermeasure: string, callback: CallableFunction = () => {}) {
setEmissionsCountermeasures(ID: number, emissionCountermeasure: string, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "emissionsCountermeasures": emissionsCountermeasures.indexOf(emissionCountermeasure) }
var data = { "setEmissionsCountermeasures": command }
this.PUT(data, callback);
}
setOnOff(ID: number, onOff: boolean, callback: CallableFunction = () => {}) {
setOnOff(ID: number, onOff: boolean, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "onOff": onOff }
var data = { "setOnOff": command }
this.PUT(data, callback);
}
setFollowRoads(ID: number, followRoads: boolean, callback: CallableFunction = () => {}) {
setFollowRoads(ID: number, followRoads: boolean, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "followRoads": followRoads }
var data = { "setFollowRoads": command }
this.PUT(data, callback);
}
setOperateAs(ID: number, operateAs: number, callback: CallableFunction = () => {}) {
setOperateAs(ID: number, operateAs: number, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "operateAs": operateAs }
var data = { "setOperateAs": command }
this.PUT(data, callback);
}
refuel(ID: number, callback: CallableFunction = () => {}) {
refuel(ID: number, callback: CallableFunction = () => { }) {
var command = { "ID": ID };
var data = { "refuel": command }
this.PUT(data, callback);
}
bombPoint(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
bombPoint(ID: number, latlng: LatLng, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "location": latlng }
var data = { "bombPoint": command }
this.PUT(data, callback);
}
carpetBomb(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
carpetBomb(ID: number, latlng: LatLng, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "location": latlng }
var data = { "carpetBomb": command }
this.PUT(data, callback);
}
bombBuilding(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
bombBuilding(ID: number, latlng: LatLng, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "location": latlng }
var data = { "bombBuilding": command }
this.PUT(data, callback);
}
fireAtArea(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
fireAtArea(ID: number, latlng: LatLng, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "location": latlng }
var data = { "fireAtArea": command }
this.PUT(data, callback);
}
simulateFireFight(ID: number, latlng: LatLng, altitude: number, callback: CallableFunction = () => {}) {
simulateFireFight(ID: number, latlng: LatLng, altitude: number, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "location": latlng, "altitude": altitude }
var data = { "simulateFireFight": command }
this.PUT(data, callback);
}
scenicAAA(ID: number, coalition: string, callback: CallableFunction = () => {}) {
// TODO: Remove coalition
scenicAAA(ID: number, coalition: string, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "coalition": coalition }
var data = { "scenicAAA": command }
this.PUT(data, callback);
}
missOnPurpose(ID: number, coalition: string, callback: CallableFunction = () => {}) {
// TODO: Remove coalition
missOnPurpose(ID: number, coalition: string, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "coalition": coalition }
var data = { "missOnPurpose": command }
this.PUT(data, callback);
}
landAtPoint(ID: number, latlng: LatLng, callback: CallableFunction = () => {}) {
landAtPoint(ID: number, latlng: LatLng, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "location": latlng }
var data = { "landAtPoint": command }
this.PUT(data, callback);
}
setShotsScatter(ID: number, shotsScatter: number, callback: CallableFunction = () => {}) {
setShotsScatter(ID: number, shotsScatter: number, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "shotsScatter": shotsScatter }
var data = { "setShotsScatter": command }
this.PUT(data, callback);
}
setShotsIntensity(ID: number, shotsIntensity: number, callback: CallableFunction = () => {}) {
setShotsIntensity(ID: number, shotsIntensity: number, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "shotsIntensity": shotsIntensity }
var data = { "setShotsIntensity": command }
this.PUT(data, callback);
}
setAdvacedOptions(ID: number, isActiveTanker: boolean, isActiveAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings, callback: CallableFunction = () => {}) {
setAdvacedOptions(ID: number, isActiveTanker: boolean, isActiveAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings, callback: CallableFunction = () => { }) {
var command = {
"ID": ID,
"isActiveTanker": isActiveTanker,
@@ -383,7 +400,7 @@ export class ServerManager {
this.PUT(data, callback);
}
setCommandModeOptions(restrictSpawns: boolean, restrictToCoalition: boolean, spawnPoints: {blue: number, red: number}, eras: string[], setupTime: number, callback: CallableFunction = () => {}) {
setCommandModeOptions(restrictSpawns: boolean, restrictToCoalition: boolean, spawnPoints: { blue: number, red: number }, eras: string[], setupTime: number, callback: CallableFunction = () => { }) {
var command = {
"restrictSpawns": restrictSpawns,
"restrictToCoalition": restrictToCoalition,
@@ -396,13 +413,17 @@ export class ServerManager {
this.PUT(data, callback);
}
reloadDatabases(callback: CallableFunction = () => {}) {
reloadDatabases(callback: CallableFunction = () => { }) {
var data = { "reloadDatabases": {} };
this.PUT(data, callback);
}
startUpdate() {
window.setInterval(() => {
/* Clear any existing interval */
this.#intervals.forEach((interval: number) => { window.clearInterval(interval); });
this.#intervals = [];
this.#intervals.push(window.setInterval(() => {
if (!this.getPaused()) {
this.getMission((data: MissionData) => {
this.checkSessionHash(data.sessionHash);
@@ -410,9 +431,9 @@ export class ServerManager {
return data.time;
});
}
}, 1000);
}, 1000));
window.setInterval(() => {
this.#intervals.push(window.setInterval(() => {
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
this.getAirbases((data: AirbasesData) => {
this.checkSessionHash(data.sessionHash);
@@ -420,19 +441,19 @@ export class ServerManager {
return data.time;
});
}
}, 10000);
}, 10000));
window.setInterval(() => {
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE){
this.#intervals.push(window.setInterval(() => {
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
this.getBullseye((data: BullseyesData) => {
this.checkSessionHash(data.sessionHash);
getApp().getMissionManager()?.updateBullseyes(data);
return data.time;
});
}
}, 10000);
}, 10000));
window.setInterval(() => {
this.#intervals.push(window.setInterval(() => {
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
this.getLogs((data: any) => {
this.checkSessionHash(data.sessionHash);
@@ -440,41 +461,41 @@ export class ServerManager {
return data.time;
});
}
}, 1000);
}, 1000));
window.setInterval(() => {
this.#intervals.push(window.setInterval(() => {
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
this.getUnits((buffer: ArrayBuffer) => {
var time = getApp().getUnitsManager()?.update(buffer);
var time = getApp().getUnitsManager()?.update(buffer);
return time;
}, false);
}
}, 250);
}, 250));
window.setInterval(() => {
this.#intervals.push(window.setInterval(() => {
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
this.getWeapons((buffer: ArrayBuffer) => {
var time = getApp().getWeaponsManager()?.update(buffer);
var time = getApp().getWeaponsManager()?.update(buffer);
return time;
}, false);
}
}, 250);
}, 250));
window.setInterval(() => {
this.#intervals.push(window.setInterval(() => {
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
this.getUnits((buffer: ArrayBuffer) => {
var time = getApp().getUnitsManager()?.update(buffer);
var time = getApp().getUnitsManager()?.update(buffer);
return time;
}, true);
const elapsedMissionTime = getApp().getMissionManager().getDateAndTime().elapsedTime;
this.#serverIsPaused = ( elapsedMissionTime === this.#previousMissionElapsedTime );
const elapsedMissionTime = getApp().getMissionManager().getDateAndTime().elapsedTime;
this.#serverIsPaused = (elapsedMissionTime === this.#previousMissionElapsedTime);
this.#previousMissionElapsedTime = elapsedMissionTime;
const csp = (getApp().getPanelsManager().get("connectionStatus") as ConnectionStatusPanel);
if ( this.getConnected() ) {
if ( this.getServerIsPaused() ) {
if (this.getConnected()) {
if (this.getServerIsPaused()) {
csp.showServerPaused();
} else {
csp.showConnected();
@@ -484,33 +505,33 @@ export class ServerManager {
}
}
}, ( this.getServerIsPaused() ? 500 : 5000 ));
}, (this.getServerIsPaused() ? 500 : 5000)));
// Mission clock and elapsed time
window.setInterval( () => {
if ( !this.getConnected() || this.#serverIsPaused ) {
this.#intervals.push(window.setInterval(() => {
if (!this.getConnected() || this.#serverIsPaused) {
return;
}
const elapsedMissionTime = getApp().getMissionManager().getDateAndTime().elapsedTime;
const csp = (getApp().getPanelsManager().get("connectionStatus") as ConnectionStatusPanel);
const mt = getApp().getMissionManager().getDateAndTime().time;
const mt = getApp().getMissionManager().getDateAndTime().time;
csp.setMissionTime( [ mt.h, mt.m, mt.s ].map( n => zeroAppend( n, 2 )).join( ":" ) );
csp.setElapsedTime( new Date( elapsedMissionTime * 1000 ).toISOString().substring( 11, 19 ) );
csp.setMissionTime([mt.h, mt.m, mt.s].map(n => zeroAppend(n, 2)).join(":"));
csp.setElapsedTime(new Date(elapsedMissionTime * 1000).toISOString().substring(11, 19));
}, 1000 );
}, 1000));
window.setInterval(() => {
this.#intervals.push(window.setInterval(() => {
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
this.getWeapons((buffer: ArrayBuffer) => {
var time = getApp().getWeaponsManager()?.update(buffer);
var time = getApp().getWeaponsManager()?.update(buffer);
return time;
}, true);
}
}, 5000);
}, 5000));
}
refreshAll() {
@@ -533,12 +554,12 @@ export class ServerManager {
});
this.getWeapons((buffer: ArrayBuffer) => {
var time = getApp().getWeaponsManager()?.update(buffer);
var time = getApp().getWeaponsManager()?.update(buffer);
return time;
}, true);
this.getUnits((buffer: ArrayBuffer) => {
var time = getApp().getUnitsManager()?.update(buffer);
var time = getApp().getUnitsManager()?.update(buffer);
return time;
}, true);
}
@@ -580,4 +601,8 @@ export class ServerManager {
getServerIsPaused() {
return this.#serverIsPaused;
}
getRequests() {
return this.#requests;
}
}

View File

@@ -1,5 +1,9 @@
import { Unit } from "./unit";
export interface ContextActionOptions {
isScenic?: boolean
}
export class ContextAction {
#id: string = "";
#label: string = "";
@@ -7,13 +11,18 @@ export class ContextAction {
#callback: CallableFunction | null = null;
#units: Unit[] = [];
#hideContextAfterExecution: boolean = true
#options: ContextActionOptions;
constructor(id: string, label: string, description: string, callback: CallableFunction, hideContextAfterExecution: boolean = true) {
this.#id = id;
constructor(id: string, label: string, description: string, callback: CallableFunction, hideContextAfterExecution: boolean = true, options: ContextActionOptions) {
this.#id = id;
this.#label = label;
this.#description = description;
this.#callback = callback;
this.#hideContextAfterExecution = hideContextAfterExecution;
this.#options = {
"isScenic": false,
...options
}
}
addUnit(unit: Unit) {
@@ -28,6 +37,10 @@ export class ContextAction {
return this.#label;
}
getOptions() {
return this.#options;
}
getDescription() {
return this.#description;
}

View File

@@ -1,4 +1,4 @@
import { ContextAction } from "./contextaction";
import { ContextAction, ContextActionOptions } from "./contextaction";
import { Unit } from "./unit";
export class ContextActionSet {
@@ -8,9 +8,11 @@ export class ContextActionSet {
}
addContextAction(unit: Unit, id: string, label: string, description: string, callback: CallableFunction, hideContextAfterExecution: boolean = true) {
addContextAction(unit: Unit, id: string, label: string, description: string, callback: CallableFunction, hideContextAfterExecution: boolean = true, options?:ContextActionOptions) {
options = options || {};
if (!(id in this.#contextActions)) {
this.#contextActions[id] = new ContextAction(id, label, description, callback, hideContextAfterExecution);
this.#contextActions[id] = new ContextAction(id, label, description, callback, hideContextAfterExecution, options);
}
this.#contextActions[id].addUnit(unit);
}

View File

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

View File

@@ -3,7 +3,7 @@ import { getApp } from "../..";
import { GAME_MASTER } from "../../constants/constants";
import { UnitBlueprint } from "../../interfaces";
export class UnitDatabase {
export abstract class UnitDatabase {
blueprints: { [key: string]: UnitBlueprint } = {};
#url: string;
@@ -31,9 +31,7 @@ export class UnitDatabase {
}
}
getCategory() {
return "";
}
abstract getCategory(): string;
/* Gets a specific blueprint by name */
getByName(name: string) {
@@ -240,4 +238,15 @@ export class UnitDatabase {
getSpawnPointsByName(name: string) {
return Infinity;
}
getUnkownUnit(name: string): UnitBlueprint {
return {
name: name,
enabled: true,
coalition: 'neutral',
era: 'N/A',
label: name,
shortLabel: ''
}
}
}

View File

@@ -0,0 +1,65 @@
import { Dialog } from "../../dialog/dialog";
import { createCheckboxOption } from "../../other/utils";
var categoryMap = {
"Aircraft": "Aircraft",
"Helicopter": "Helicopter",
"GroundUnit": "Ground units",
"NavyUnit": "Naval units"
}
export abstract class UnitDataFile {
protected data: any;
protected dialog!: Dialog;
constructor() { }
buildCategoryCoalitionTable() {
const categories = this.#getCategoriesFromData();
const coalitions = ["blue", "neutral", "red"];
let headersHTML: string = ``;
let matrixHTML: string = ``;
categories.forEach((category: string, index) => {
matrixHTML += `<tr><td>${categoryMap[category as keyof typeof categoryMap]}</td>`;
coalitions.forEach((coalition: string) => {
if (index === 0)
headersHTML += `<th data-coalition="${coalition}">${coalition[0].toUpperCase() + coalition.substring(1)}</th>`;
const optionIsValid = this.data[category].hasOwnProperty(coalition);
let checkboxHTML = createCheckboxOption(`${category}:${coalition}`, category, optionIsValid, () => { }, {
"disabled": !optionIsValid,
"name": "category-coalition-selection",
"readOnly": !optionIsValid
}).outerHTML;
if (optionIsValid)
checkboxHTML = checkboxHTML.replace(`"checkbox"`, `"checkbox" checked`); // inner and outerHTML screw default checked up
matrixHTML += `<td data-coalition="${coalition}">${checkboxHTML}</td>`;
});
matrixHTML += "</tr>";
});
const table = <HTMLTableElement>this.dialog.getElement().querySelector("table.categories-coalitions");
(<HTMLElement>table.tHead).innerHTML = `<tr><td>&nbsp;</td>${headersHTML}</tr>`;
(<HTMLElement>table.querySelector(`tbody`)).innerHTML = matrixHTML;
}
#getCategoriesFromData() {
const categories = Object.keys(this.data);
categories.sort();
return categories;
}
getData() {
return this.data;
}
}

View File

@@ -0,0 +1,97 @@
import { getApp } from "../..";
import { Dialog } from "../../dialog/dialog";
import { zeroAppend } from "../../other/utils";
import { Unit } from "../unit";
import { UnitDataFile } from "./unitdatafile";
export class UnitDataFileExport extends UnitDataFile {
protected data!: any;
protected dialog: Dialog;
#element!: HTMLElement;
#filename: string = "export.json";
constructor(elementId: string) {
super();
this.dialog = new Dialog(elementId);
this.#element = this.dialog.getElement();
this.#element.querySelector(".start-transfer")?.addEventListener("click", (ev: MouseEventInit) => {
this.#doExport();
});
}
/**
* Show the form to start the export journey
*/
showForm(units: Unit[]) {
const data: any = {};
const unitCanBeExported = (unit: Unit) => !["Aircraft", "Helicopter"].includes(unit.getCategory());
units.filter((unit: Unit) => unit.getAlive() && unitCanBeExported(unit)).forEach((unit: Unit) => {
const category = unit.getCategory();
const coalition = unit.getCoalition();
if (!data.hasOwnProperty(category)) {
data[category] = {};
}
if (!data[category].hasOwnProperty(coalition))
data[category][coalition] = [];
data[category][coalition].push(unit);
});
this.data = data;
this.buildCategoryCoalitionTable();
this.dialog.show();
const date = new Date();
this.#filename = `olympus_${getApp().getMissionManager().getTheatre().replace(/[^\w]/gi, "").toLowerCase()}_${date.getFullYear()}${zeroAppend(date.getMonth() + 1, 2)}${zeroAppend(date.getDate(), 2)}_${zeroAppend(date.getHours(), 2)}${zeroAppend(date.getMinutes(), 2)}${zeroAppend(date.getSeconds(), 2)}.json`;
var input = this.#element.querySelector("#export-filename") as HTMLInputElement;
input.onchange = (ev: Event) => {
this.#filename = (ev.currentTarget as HTMLInputElement).value;
}
if (input)
input.value = this.#filename;
}
#doExport() {
let selectedUnits: Unit[] = [];
this.#element.querySelectorAll(`input[type="checkbox"][name="category-coalition-selection"]:checked`).forEach(<HTMLInputElement>(checkbox: HTMLInputElement) => {
if (checkbox instanceof HTMLInputElement) {
const [category, coalition] = checkbox.value.split(":"); // e.g. "category:coalition"
selectedUnits = selectedUnits.concat(this.data[category][coalition]);
}
});
if (selectedUnits.length === 0) {
alert("Please select at least one option for export.");
return;
}
var unitsToExport: { [key: string]: any } = {};
selectedUnits.forEach((unit: Unit) => {
var data: any = unit.getData();
if (unit.getGroupName() in unitsToExport)
unitsToExport[unit.getGroupName()].push(data);
else
unitsToExport[unit.getGroupName()] = [data];
});
const a = document.createElement("a");
const file = new Blob([JSON.stringify(unitsToExport)], { type: 'text/plain' });
a.href = URL.createObjectURL(file);
var filename = this.#filename;
if (!this.#filename.toLowerCase().endsWith(".json"))
filename += ".json";
a.download = filename;
a.click();
this.dialog.hide();
}
}

View File

@@ -0,0 +1,138 @@
import { getApp } from "../..";
import { Dialog } from "../../dialog/dialog";
import { UnitData } from "../../interfaces";
import { UnitDataFile } from "./unitdatafile";
export class UnitDataFileImport extends UnitDataFile {
protected data!: any;
protected dialog: Dialog;
#fileData!: { [key: string]: UnitData[] };
constructor(elementId: string) {
super();
this.dialog = new Dialog(elementId);
this.dialog.getElement().querySelector(".start-transfer")?.addEventListener("click", (ev: MouseEventInit) => {
this.#doImport();
this.dialog.hide();
});
}
#doImport() {
let selectedCategories: any = {};
const unitsManager = getApp().getUnitsManager();
this.dialog.getElement().querySelectorAll(`input[type="checkbox"][name="category-coalition-selection"]:checked`).forEach(<HTMLInputElement>(checkbox: HTMLInputElement) => {
if (checkbox instanceof HTMLInputElement) {
const [category, coalition] = checkbox.value.split(":"); // e.g. "category:coalition"
selectedCategories[category] = selectedCategories[category] || {};
selectedCategories[category][coalition] = true;
}
});
for (const [groupName, groupData] of Object.entries(this.#fileData)) {
if (groupName === "" || groupData.length === 0 || !this.#unitGroupDataCanBeImported(groupData))
continue;
let { category, coalition } = groupData[0];
if (!selectedCategories.hasOwnProperty(category)
|| !selectedCategories[category].hasOwnProperty(coalition)
|| selectedCategories[category][coalition] !== true)
continue;
let unitsToSpawn = groupData.filter((unitData: UnitData) => this.#unitDataCanBeImported(unitData)).map((unitData: UnitData) => {
return { unitType: unitData.name, location: unitData.position, liveryID: "" }
});
unitsManager.spawnUnits(category, unitsToSpawn, coalition, false);
}
/*
for (let groupName in groups) {
if (groupName !== "" && groups[groupName].length > 0 && (groups[groupName].every((unit: UnitData) => { return unit.category == "GroundUnit"; }) || groups[groupName].every((unit: any) => { return unit.category == "NavyUnit"; }))) {
var aliveUnits = groups[groupName].filter((unit: UnitData) => { return unit.alive });
var units = aliveUnits.map((unit: UnitData) => {
return { unitType: unit.name, location: unit.position, liveryID: "" }
});
getApp().getUnitsManager().spawnUnits(groups[groupName][0].category, units, groups[groupName][0].coalition, true);
}
}
//*/
}
#showForm() {
const data: any = {};
for (const [group, units] of Object.entries(this.#fileData)) {
if (group === "" || units.length === 0)
continue;
if (units.some((unit: UnitData) => !this.#unitDataCanBeImported(unit)))
continue;
const category = units[0].category;
if (!data.hasOwnProperty(category)) {
data[category] = {};
}
units.forEach((unit: UnitData) => {
if (!data[category].hasOwnProperty(unit.coalition))
data[category][unit.coalition] = [];
data[category][unit.coalition].push(unit);
});
}
/*
groups.filter((unit:Unit) => unitCanBeImported(unit)).forEach((unit:Unit) => {
const category = unit.getCategoryLabel();
const coalition = unit.getCoalition();
if (!data.hasOwnProperty(category)) {
data[category] = {};
}
if (!data[category].hasOwnProperty(coalition))
data[category][coalition] = [];
data[category][coalition].push(unit);
});
//*/
this.data = data;
this.buildCategoryCoalitionTable();
this.dialog.show();
}
selectFile() {
var input = document.createElement("input");
input.type = "file";
input.addEventListener("change", (e: any) => {
var file = e.target.files[0];
if (!file) {
return;
}
var reader = new FileReader();
reader.onload = (e: any) => {
this.#fileData = JSON.parse(e.target.result);
this.#showForm();
};
reader.readAsText(file);
})
input.click();
}
#unitDataCanBeImported(unitData: UnitData) {
return unitData.alive && this.#unitGroupDataCanBeImported([unitData]);
}
#unitGroupDataCanBeImported(unitGroupData: UnitData[]) {
return unitGroupData.every((unitData: UnitData) => {
return !["Aircraft", "Helicopter"].includes(unitData.category);
}) && unitGroupData.some((unitData: UnitData) => unitData.alive);
}
}

View File

@@ -1,11 +1,11 @@
import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map, Point, Circle } from 'leaflet';
import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map, Point } from 'leaflet';
import { getApp } from '..';
import { enumToCoalition, enumToEmissioNCountermeasure, getMarkerCategoryByName, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg, bearing, deg2rad, ftToM, getGroundElevation, coalitionToEnum, nmToFt, nmToM } from '../other/utils';
import { enumToCoalition, enumToEmissioNCountermeasure, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg, bearing, deg2rad, ftToM, getGroundElevation, coalitionToEnum, nmToFt, nmToM } from '../other/utils';
import { CustomMarker } from '../map/markers/custommarker';
import { SVGInjector } from '@tanem/svg-injector';
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_UNIT_CONTACTS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, GROUPING_ZOOM_TRANSITION } from '../constants/constants';
import { DLINK, DataIndexes, GAME_MASTER, HIDE_GROUP_MEMBERS, IDLE, IRST, MOVE_UNIT, OPTIC, RADAR, ROEs, RWR, SHOW_UNIT_CONTACTS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, GROUPING_ZOOM_TRANSITION, MAX_SHOTS_SCATTER, SHOTS_SCATTER_DEGREES, GROUND_UNIT_AIR_DEFENCE_REGEX } from '../constants/constants';
import { DataExtractor } from '../server/dataextractor';
import { groundUnitDatabase } from './databases/groundunitdatabase';
import { navyUnitDatabase } from './databases/navyunitdatabase';
@@ -14,7 +14,7 @@ import { Ammo, Contact, GeneralSettings, LoadoutBlueprint, ObjectIconOptions, Of
import { RangeCircle } from "../map/rangecircle";
import { Group } from './group';
import { ContextActionSet } from './contextactionset';
import { ContextAction } from './contextaction';
import * as turf from "@turf/turf";
var pathIcon = new Icon({
iconUrl: '/resources/theme/images/markers/marker-icon.png',
@@ -45,6 +45,7 @@ export abstract class Unit extends CustomMarker {
#horizontalVelocity: number = 0;
#verticalVelocity: number = 0;
#heading: number = 0;
#track: number = 0;
#isActiveTanker: boolean = false;
#isActiveAWACS: boolean = false;
#onOff: boolean = true;
@@ -127,6 +128,7 @@ export abstract class Unit extends CustomMarker {
getHorizontalVelocity() { return this.#horizontalVelocity };
getVerticalVelocity() { return this.#verticalVelocity };
getHeading() { return this.#heading };
getTrack() { return this.#track };
getIsActiveAWACS() { return this.#isActiveAWACS };
getIsActiveTanker() { return this.#isActiveTanker };
getOnOff() { return this.#onOff };
@@ -192,15 +194,17 @@ export abstract class Unit extends CustomMarker {
/* Deselect units if they are hidden */
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300);
this.#updateMarker();
this.setSelected(this.getSelected() && !this.getHidden());
});
document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => {
window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300);
document.addEventListener("toggleMarkerVisibility", (ev: CustomEventInit) => {
this.#updateMarker();
this.setSelected(this.getSelected() && !this.getHidden());
});
/* Update the marker when the visibility options change */
document.addEventListener("mapVisibilityOptionsChanged", (ev: CustomEventInit) => {
/* Update the marker when the options change */
document.addEventListener("mapOptionsChanged", (ev: CustomEventInit) => {
this.#updateMarker();
/* Circles don't like to be updated when the map is zooming */
@@ -233,6 +237,26 @@ export abstract class Unit extends CustomMarker {
*/
abstract appendContextActions(contextActionSet: ContextActionSet, targetUnit: Unit | null, targetPosition: LatLng | null): void;
/**
*
* @returns string containing the marker category
*/
abstract getMarkerCategory(): string;
/**
*
* @returns string containing the default marker
*/
abstract getDefaultMarker(): string;
/** Get the category but for display use - for the user. (i.e. has spaces in it)
*
* @returns string
*/
getCategoryLabel() {
return ((GROUND_UNIT_AIR_DEFENCE_REGEX.test(this.getType())) ? "Air Defence" : this.getCategory()).replace(/([a-z])([A-Z])/g, "$1 $2");
}
/********************** Unit data *************************/
/** This function is called by the units manager to update all the data coming from the backend. It reads the binary raw data using a DataExtractor
*
@@ -264,6 +288,7 @@ export abstract class Unit extends CustomMarker {
case DataIndexes.horizontalVelocity: this.#horizontalVelocity = dataExtractor.extractFloat64(); break;
case DataIndexes.verticalVelocity: this.#verticalVelocity = dataExtractor.extractFloat64(); break;
case DataIndexes.heading: this.#heading = dataExtractor.extractFloat64(); updateMarker = true; break;
case DataIndexes.track: this.#track = dataExtractor.extractFloat64(); updateMarker = true; break;
case DataIndexes.isActiveTanker: this.#isActiveTanker = dataExtractor.extractBool(); break;
case DataIndexes.isActiveAWACS: this.#isActiveAWACS = dataExtractor.extractBool(); break;
case DataIndexes.onOff: this.#onOff = dataExtractor.extractBool(); break;
@@ -313,7 +338,7 @@ export abstract class Unit extends CustomMarker {
}
/* If the unit is selected or if the view is centered on this unit, sent the update signal so that other elements like the UnitControlPanel can be updated. */
if (this.getSelected() || getApp().getMap().getCenterUnit() === this)
if (this.getSelected() || getApp().getMap().getCenteredOnUnit() === this)
document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this }));
}
@@ -324,6 +349,7 @@ export abstract class Unit extends CustomMarker {
getData(): UnitData {
return {
category: this.getCategory(),
categoryDisplayName: this.getCategoryLabel(),
ID: this.ID,
alive: this.#alive,
human: this.#human,
@@ -341,6 +367,7 @@ export abstract class Unit extends CustomMarker {
horizontalVelocity: this.#horizontalVelocity,
verticalVelocity: this.#verticalVelocity,
heading: this.#heading,
track: this.#track,
isActiveTanker: this.#isActiveTanker,
isActiveAWACS: this.#isActiveAWACS,
onOff: this.#onOff,
@@ -371,14 +398,6 @@ export abstract class Unit extends CustomMarker {
}
}
/**
*
* @returns string containing the marker category
*/
getMarkerCategory(): string {
return getMarkerCategoryByName(this.getName());
}
/** Get a database of information also in this unit's category
*
* @returns UnitDatabase
@@ -413,7 +432,7 @@ export abstract class Unit extends CustomMarker {
else {
this.#clearContacts();
this.#clearPath();
this.#clearTarget();
this.#clearTargetPosition();
}
/* When the group leader is selected, if grouping is active, all the other group members are also selected */
@@ -448,7 +467,7 @@ export abstract class Unit extends CustomMarker {
return this.#selected;
}
/** Set the number of the hotgroup to which the unit belongs
/** Set the number of the hotgroup to which the unit belongss
*
* @param hotgroup (number)
*/
@@ -522,7 +541,7 @@ export abstract class Unit extends CustomMarker {
}
getDatabaseEntry() {
return this.getDatabase()?.getByName(this.#name);
return this.getDatabase()?.getByName(this.#name) ?? this.getDatabase()?.getUnkownUnit(this.getName());
}
getGroup() {
@@ -546,6 +565,14 @@ export abstract class Unit extends CustomMarker {
return false;
}
isControlledByDCS() {
return this.getControlled() === false && this.getHuman() === false;
}
isControlledByOlympus() {
return this.getControlled() === true;
}
/********************** Icon *************************/
createIcon(): void {
/* Set the icon */
@@ -592,7 +619,7 @@ export abstract class Unit extends CustomMarker {
/* If a unit does not belong to the commanded coalition or it is not visually detected, show it with the generic aircraft square */
var marker;
if (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)))
marker = this.getDatabaseEntry()?.markerFile ?? this.getMarkerCategory();
marker = this.getDatabaseEntry()?.markerFile ?? this.getDefaultMarker();
else
marker = "aircraft";
img.src = `/resources/theme/images/units/${marker}.svg`;
@@ -672,9 +699,11 @@ export abstract class Unit extends CustomMarker {
const hiddenTypes = getApp().getMap().getHiddenTypes();
var hidden = (
/* Hide the unit if it is a human and humans are hidden */
(this.#human && hiddenTypes.includes("human")) ||
/* Hide the unit if it is DCS controlled and DCS controlled units are hidden */
(this.#controlled == false && hiddenTypes.includes("dcs")) ||
(this.getHuman() && hiddenTypes.includes("human")) ||
/* Hide the unit if it is DCS-controlled and DCS controlled units are hidden */
(this.isControlledByDCS() && hiddenTypes.includes("dcs")) ||
/* Hide the unit if it is Olympus-controlled and Olympus-controlled units are hidden */
(this.isControlledByOlympus() && hiddenTypes.includes("olympus")) ||
/* Hide the unit if this specific category is hidden */
(hiddenTypes.includes(this.getMarkerCategory())) ||
/* Hide the unit if this coalition is hidden */
@@ -682,14 +711,11 @@ export abstract class Unit extends CustomMarker {
/* Hide the unit if it does not belong to the commanded coalition and it is not detected by a method that can pinpoint its location (RWR does not count) */
(!this.belongsToCommandedCoalition() && (this.#detectionMethods.length == 0 || (this.#detectionMethods.length == 1 && this.#detectionMethods[0] === RWR))) ||
/* Hide the unit if grouping is activated, the unit is not the group leader, it is not selected, and the zoom is higher than the grouping threshold */
(getApp().getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] && !this.#isLeader && this.getCategory() == "GroundUnit" && getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION &&
(getApp().getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] && !this.#isLeader && !this.getSelected() && this.getCategory() == "GroundUnit" && getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION &&
(this.belongsToCommandedCoalition() || (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0))));
/* Force dead units to be hidden */
this.setHidden(hidden || !this.getAlive());
/* Force hidden units to be unselected */
this.setSelected(this.getSelected() && !this.getHidden());
}
setHidden(hidden: boolean) {
@@ -773,7 +799,7 @@ export abstract class Unit extends CustomMarker {
return this.getDatabaseEntry()?.canAAA === true;
}
indirectFire() {
isIndirectFire() {
return this.getDatabaseEntry()?.indirectFire === true;
}
@@ -930,6 +956,7 @@ export abstract class Unit extends CustomMarker {
});
}
// TODO: Remove coalition
scenicAAA() {
var coalition = "neutral";
if (this.getCoalition() === "red")
@@ -939,6 +966,7 @@ export abstract class Unit extends CustomMarker {
getApp().getServerManager().scenicAAA(this.ID, coalition);
}
// TODO: Remove coalition
missOnPurpose() {
var coalition = "neutral";
if (this.getCoalition() === "red")
@@ -1162,7 +1190,7 @@ export abstract class Unit extends CustomMarker {
/* Rotate elements according to heading */
element.querySelectorAll("[data-rotate-to-heading]").forEach(el => {
const headingDeg = rad2deg(this.#heading);
const headingDeg = rad2deg(this.#track);
let currentStyle = el.getAttribute("style") || "";
el.setAttribute("style", currentStyle + `transform:rotate(${headingDeg}deg);`);
});
@@ -1252,11 +1280,13 @@ export abstract class Unit extends CustomMarker {
}
#clearPath() {
for (let WP in this.#pathMarkers) {
getApp().getMap().removeLayer(this.#pathMarkers[WP]);
if (this.#pathPolyline.getLatLngs().length != 0) {
for (let WP in this.#pathMarkers) {
getApp().getMap().removeLayer(this.#pathMarkers[WP]);
}
this.#pathMarkers = [];
this.#pathPolyline.setLatLngs([]);
}
this.#pathMarkers = [];
this.#pathPolyline.setLatLngs([]);
}
#drawContacts() {
@@ -1410,7 +1440,7 @@ export abstract class Unit extends CustomMarker {
}
}
else
this.#clearTarget();
this.#clearTargetPosition();
}
#drawTargetPosition(targetPosition: LatLng) {
@@ -1419,10 +1449,25 @@ export abstract class Unit extends CustomMarker {
if (!getApp().getMap().hasLayer(this.#targetPositionPolyline))
this.#targetPositionPolyline.addTo(getApp().getMap());
this.#targetPositionMarker.setLatLng(new LatLng(targetPosition.lat, targetPosition.lng));
this.#targetPositionPolyline.setLatLngs([new LatLng(this.#position.lat, this.#position.lng), new LatLng(targetPosition.lat, targetPosition.lng)])
if (this.getState() === 'simulate-fire-fight' && this.getShotsScatter() != MAX_SHOTS_SCATTER) {
let turfUnitPosition = turf.point([this.getPosition().lng, this.getPosition().lat]);
let turfTargetPosition = turf.point([targetPosition.lng, targetPosition.lat]);
let bearing = turf.bearing(turfUnitPosition, turfTargetPosition);
let scatterDistance = turf.distance(turfUnitPosition, turfTargetPosition) * Math.tan((MAX_SHOTS_SCATTER - this.getShotsScatter()) * deg2rad(SHOTS_SCATTER_DEGREES));
let destination1 = turf.destination(turfTargetPosition, scatterDistance, bearing + 90);
let destination2 = turf.destination(turfTargetPosition, scatterDistance, bearing - 90);
this.#targetPositionPolyline.setStyle({dashArray: "4, 8"});
this.#targetPositionPolyline.setLatLngs([new LatLng(destination1.geometry.coordinates[1], destination1.geometry.coordinates[0]), new LatLng(this.#position.lat, this.#position.lng), new LatLng(destination2.geometry.coordinates[1], destination2.geometry.coordinates[0])])
} else {
this.#targetPositionPolyline.setStyle({dashArray: ""});
this.#targetPositionPolyline.setLatLngs([new LatLng(this.#position.lat, this.#position.lng), new LatLng(targetPosition.lat, targetPosition.lng)])
}
}
#clearTarget() {
#clearTargetPosition() {
if (getApp().getMap().hasLayer(this.#targetPositionMarker))
this.#targetPositionMarker.removeFrom(getApp().getMap());
@@ -1492,6 +1537,14 @@ export class Aircraft extends AirUnit {
contextActionSet.addContextAction(this, "refuel", "Refuel", "Refuel units at the nearest AAR Tanker. If no tanker is available the unit will RTB", (units: Unit[]) => { getApp().getUnitsManager().refuel(units) });
}
}
getMarkerCategory() {
return "aircraft";
}
getDefaultMarker() {
return "aircraft";
}
}
export class Helicopter extends AirUnit {
@@ -1509,6 +1562,14 @@ export class Helicopter extends AirUnit {
if (targetPosition !== null)
contextActionSet.addContextAction(this, "land-at-point", "Land here", "land at this precise location", (units: Unit[]) => { getApp().getUnitsManager().landAtPoint(targetPosition, units) });
}
getMarkerCategory() {
return "helicopter";
}
getDefaultMarker() {
return "helicopter";
}
}
export class GroundUnit extends Unit {
@@ -1549,13 +1610,17 @@ export class GroundUnit extends Unit {
if (targetPosition !== null) {
if (this.canTargetPoint()) {
contextActionSet.addContextAction(this, "fire-at-area", "Fire at area", "Fire at a specific area on the ground", (units: Unit[]) => { getApp().getUnitsManager().fireAtArea(targetPosition, units) });
contextActionSet.addContextAction(this, "simulate-fire-fight", "Simulate fire fight", "Simulate a fire fight by shooting randomly in a certain large area. WARNING: works correctly only on neutral units, blue or red units will aim", (units: Unit[]) => { getApp().getUnitsManager().fireAtArea(targetPosition, units) });
contextActionSet.addContextAction(this, "simulate-fire-fight", "Simulate fire fight", "Simulate a fire fight by shooting randomly in a certain large area.\nWARNING: works correctly only on neutral units, blue or red units will aim", (units: Unit[]) => { getApp().getUnitsManager().simulateFireFight(targetPosition, units) });
}
}
else {
if (this.canAAA()) {
contextActionSet.addContextAction(this, "scenic-aaa", "Scenic AAA", "Shoot AAA in the air without aiming at any target, when a enemy unit gets close enough. WARNING: works correctly only on neutral units, blue or red units will aim", (units: Unit[]) => { getApp().getUnitsManager().scenicAAA(units) });
contextActionSet.addContextAction(this, "miss-aaa", "Misson on purpose", "Shoot AAA towards the closest enemy unit, but don't aim precisely. WARNING: works correctly only on neutral units, blue or red units will aim", (units: Unit[]) => { getApp().getUnitsManager().missOnPurpose(units) });
contextActionSet.addContextAction(this, "scenic-aaa", "Scenic AAA", "Shoot AAA in the air without aiming at any target, when an enemy unit gets close enough.\nWARNING: works correctly only on neutral units, blue or red units will aim", (units: Unit[]) => { getApp().getUnitsManager().scenicAAA(units) }, undefined, {
"isScenic": true
});
contextActionSet.addContextAction(this, "miss-aaa", "Dynamic accuracy AAA", "Shoot AAA towards the closest enemy unit, but don't aim precisely.\nWARNING: works correctly only on neutral units, blue or red units will aim", (units: Unit[]) => { getApp().getUnitsManager().missOnPurpose(units) }, undefined, {
"isScenic": true
});
}
}
}
@@ -1582,9 +1647,9 @@ export class GroundUnit extends Unit {
unitWhenGrouped = (member !== null ? member?.getDatabaseEntry()?.unitWhenGrouped : unitWhenGrouped);
}
if (unitWhenGrouped)
return this.getDatabase()?.getByName(unitWhenGrouped);
return this.getDatabase()?.getByName(unitWhenGrouped) ?? this.getDatabase()?.getUnkownUnit(this.getName());
else
return this.getDatabase()?.getByName(this.getName());
return this.getDatabase()?.getByName(this.getName()) ?? this.getDatabase()?.getUnkownUnit(this.getName());
}
/* When we zoom past the grouping limit, grouping is enabled and the unit is a leader, we redraw the unit to apply any possible grouped marker */
@@ -1593,6 +1658,17 @@ export class GroundUnit extends Unit {
(getApp().getMap().getZoom() >= GROUPING_ZOOM_TRANSITION && getApp().getMap().getPreviousZoom() < GROUPING_ZOOM_TRANSITION ||
getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION && getApp().getMap().getPreviousZoom() >= GROUPING_ZOOM_TRANSITION))
}
getMarkerCategory() {
if (/\bAAA|SAM\b/.test(this.getType()) || /\bmanpad|stinger\b/i.test(this.getType()))
return "groundunit-sam";
else
return "groundunit";
}
getDefaultMarker() {
return "groundunit";
}
}
export class NavyUnit extends Unit {
@@ -1634,10 +1710,6 @@ export class NavyUnit extends Unit {
}
}
getMarkerCategory() {
return "navyunit";
}
getCategory() {
return "NavyUnit";
}
@@ -1646,4 +1718,12 @@ export class NavyUnit extends Unit {
var blueprint = navyUnitDatabase.getByName(this.getName());
return blueprint?.type ? blueprint.type : "";
}
getMarkerCategory() {
return "navyunit";
}
getDefaultMarker() {
return "navyunit";
}
}

View File

@@ -6,7 +6,7 @@ import { CoalitionArea } from "../map/coalitionarea/coalitionarea";
import { groundUnitDatabase } from "./databases/groundunitdatabase";
import { DELETE_CYCLE_TIME, DELETE_SLOW_THRESHOLD, DataIndexes, GAME_MASTER, IADSDensities, IDLE, MOVE_UNIT } from "../constants/constants";
import { DataExtractor } from "../server/dataextractor";
import { citiesDatabase } from "./citiesDatabase";
import { citiesDatabase } from "./databases/citiesdatabase";
import { aircraftDatabase } from "./databases/aircraftdatabase";
import { helicopterDatabase } from "./databases/helicopterdatabase";
import { navyUnitDatabase } from "./databases/navyunitdatabase";
@@ -16,6 +16,8 @@ import { HotgroupPanel } from "../panels/hotgrouppanel";
import { Contact, UnitData, UnitSpawnTable } from "../interfaces";
import { Dialog } from "../dialog/dialog";
import { Group } from "./group";
import { UnitDataFileExport } from "./importexport/unitdatafileexport";
import { UnitDataFileImport } from "./importexport/unitdatafileimport";
/** 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
@@ -29,6 +31,8 @@ export class UnitsManager {
#slowDeleteDialog!: Dialog;
#units: { [ID: number]: Unit };
#groups: { [groupName: string]: Group } = {};
#unitDataExport!:UnitDataFileExport;
#unitDataImport!:UnitDataFileImport;
constructor() {
this.#copiedUnits = [];
@@ -321,11 +325,13 @@ export class UnitsManager {
getUnitsVariable(variableGetter: CallableFunction, units: Unit[]) {
if (units.length == 0)
return undefined;
return units.map((unit: Unit) => {
return variableGetter(unit);
})?.reduce((a: any, b: any) => {
return a === b ? a : undefined
var value: any = variableGetter(units[0]);
units.forEach((unit: Unit) => {
if (variableGetter(unit) !== value)
value = undefined;
});
return value;
};
/** For a given unit, it returns if and how it is being detected by other units. NOTE: this function will return how a unit is being detected, i.e. how other units are detecting it. It will not return
@@ -353,6 +359,7 @@ export class UnitsManager {
* @param latlng Position of the new destination
* @param mantainRelativePosition If true, the selected units will mantain their relative positions when reaching the target. This is useful to maintain a formation for groun/navy units
* @param rotation Rotation in radians by which the formation will be rigidly rotated. E.g. a ( V ) formation will look like this ( < ) if rotated pi/4 radians (90 degrees)
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
addDestination(latlng: L.LatLng, mantainRelativePosition: boolean, rotation: number, units: Unit[] | null = null) {
if (units === null)
@@ -412,6 +419,7 @@ export class UnitsManager {
/** Instruct all the selected units to land at a specific location
*
* @param latlng Location where to land at
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
landAt(latlng: LatLng, units: Unit[] | null = null) {
if (units === null)
@@ -428,6 +436,7 @@ export class UnitsManager {
/** Instruct all the selected units to change their speed
*
* @param speedChange Speed change, either "stop", "slow", or "fast". The specific value depends on the unit category
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
changeSpeed(speedChange: string, units: Unit[] | null = null) {
if (units === null)
@@ -442,6 +451,7 @@ export class UnitsManager {
/** Instruct all the selected units to change their altitude
*
* @param altitudeChange Altitude change, either "climb" or "descend". The specific value depends on the unit category
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
changeAltitude(altitudeChange: string, units: Unit[] | null = null) {
if (units === null)
@@ -456,6 +466,7 @@ export class UnitsManager {
/** Set a specific speed to all the selected units
*
* @param speed Value to set, in m/s
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
setSpeed(speed: number, units: Unit[] | null = null) {
if (units === null)
@@ -471,6 +482,7 @@ export class UnitsManager {
/** Set a specific speed type to all the selected units
*
* @param speedType Value to set, either "CAS" or "GS". If "CAS" is selected, the unit will try to maintain the selected Calibrated Air Speed, but DCS will still only maintain a Ground Speed value so errors may arise depending on wind.
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
setSpeedType(speedType: string, units: Unit[] | null = null) {
if (units === null)
@@ -486,6 +498,7 @@ export class UnitsManager {
/** Set a specific altitude to all the selected units
*
* @param altitude Value to set, in m
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
setAltitude(altitude: number, units: Unit[] | null = null) {
if (units === null)
@@ -501,6 +514,7 @@ export class UnitsManager {
/** Set a specific altitude type to all the selected units
*
* @param altitudeType Value to set, either "ASL" or "AGL". If "AGL" is selected, the unit will try to maintain the selected Above Ground Level altitude. Due to a DCS bug, this will only be true at the final position.
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
setAltitudeType(altitudeType: string, units: Unit[] | null = null) {
if (units === null)
@@ -516,6 +530,7 @@ export class UnitsManager {
/** Set a specific ROE to all the selected units
*
* @param ROE Value to set, see constants for acceptable values
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
setROE(ROE: string, units: Unit[] | null = null) {
if (units === null)
@@ -531,6 +546,7 @@ export class UnitsManager {
/** Set a specific reaction to threat to all the selected units
*
* @param reactionToThreat Value to set, see constants for acceptable values
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
setReactionToThreat(reactionToThreat: string, units: Unit[] | null = null) {
if (units === null)
@@ -546,6 +562,7 @@ export class UnitsManager {
/** Set a specific emissions & countermeasures to all the selected units
*
* @param emissionCountermeasure Value to set, see constants for acceptable values
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
setEmissionsCountermeasures(emissionCountermeasure: string, units: Unit[] | null = null) {
if (units === null)
@@ -561,6 +578,7 @@ export class UnitsManager {
/** Turn selected units on or off, only works on ground and navy units
*
* @param onOff If true, the unit will be turned on
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
setOnOff(onOff: boolean, units: Unit[] | null = null) {
if (units === null)
@@ -576,6 +594,7 @@ export class UnitsManager {
/** Instruct the selected units to follow roads, only works on ground units
*
* @param followRoads If true, units will follow roads
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
setFollowRoads(followRoads: boolean, units: Unit[] | null = null) {
if (units === null)
@@ -591,6 +610,7 @@ export class UnitsManager {
/** Instruct selected units to operate as a certain coalition
*
* @param operateAsBool If true, units will operate as blue
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
setOperateAs(operateAsBool: boolean, units: Unit[] | null = null) {
var operateAs = operateAsBool ? "blue" : "red";
@@ -607,6 +627,7 @@ export class UnitsManager {
/** Instruct units to attack a specific unit
*
* @param ID ID of the unit to attack
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
attackUnit(ID: number, units: Unit[] | null = null) {
if (units === null)
@@ -620,7 +641,7 @@ export class UnitsManager {
}
/** Instruct units to refuel at the nearest tanker, if possible. Else units will RTB
*
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
refuel(units: Unit[] | null = null) {
if (units === null)
@@ -638,6 +659,7 @@ export class UnitsManager {
* @param ID ID of the unit to follow
* @param offset Optional parameter, defines a static offset. X: front-rear, positive front, Y: top-bottom, positive top, Z: left-right, positive right
* @param formation Optional parameter, defines a predefined formation type. Values are: "trail", "echelon-lh", "echelon-rh", "line-abreast-lh", "line-abreast-rh", "front", "diamond"
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
followUnit(ID: number, offset?: { "x": number, "y": number, "z": number }, formation?: string, units: Unit[] | null = null) {
if (units === null)
@@ -690,6 +712,7 @@ export class UnitsManager {
/** Instruct the selected units to perform precision bombing of specific coordinates
*
* @param latlng Location to bomb
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
bombPoint(latlng: LatLng, units: Unit[] | null = null) {
if (units === null)
@@ -705,6 +728,7 @@ export class UnitsManager {
/** Instruct the selected units to perform carpet bombing of specific coordinates
*
* @param latlng Location to bomb
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
carpetBomb(latlng: LatLng, units: Unit[] | null = null) {
if (units === null)
@@ -720,6 +744,7 @@ export class UnitsManager {
/** Instruct the selected units to fire at specific coordinates
*
* @param latlng Location to fire at
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
fireAtArea(latlng: LatLng, units: Unit[] | null = null) {
if (units === null)
@@ -735,6 +760,7 @@ export class UnitsManager {
/** Instruct the selected units to simulate a fire fight at specific coordinates
*
* @param latlng Location to fire at
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
simulateFireFight(latlng: LatLng, units: Unit[] | null = null) {
if (units === null)
@@ -756,7 +782,7 @@ export class UnitsManager {
}
/** Instruct units to enter into scenic AAA mode. Units will shoot in the air without aiming
*
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
scenicAAA(units: Unit[] | null = null) {
if (units === null)
@@ -769,8 +795,8 @@ export class UnitsManager {
this.#showActionMessage(units, `unit set to perform scenic AAA`);
}
/** Instruct units to enter into miss on purpose mode. Units will aim to the nearest enemy unit but not precisely.
*
/** Instruct units to enter into dynamic accuracy/miss on purpose mode. Units will aim to the nearest enemy unit but not precisely.
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
missOnPurpose(units: Unit[] | null = null) {
if (units === null)
@@ -786,6 +812,7 @@ export class UnitsManager {
/** Instruct units to land at specific point
*
* @param latlng Point where to land
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
landAtPoint(latlng: LatLng, units: Unit[] | null = null) {
if (units === null)
@@ -802,6 +829,7 @@ export class UnitsManager {
/** Set a specific shots scatter to all the selected units
*
* @param shotsScatter Value to set
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
setShotsScatter(shotsScatter: number, units: Unit[] | null = null) {
if (units === null)
@@ -817,6 +845,7 @@ export class UnitsManager {
/** Set a specific shots intensity to all the selected units
*
* @param shotsScatter Value to set
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
setShotsIntensity(shotsIntensity: number, units: Unit[] | null = null) {
if (units === null)
@@ -870,6 +899,7 @@ export class UnitsManager {
/** Set the hotgroup for the selected units. It will be the only hotgroup of the unit
*
* @param hotgroup Hotgroup number
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
setHotgroup(hotgroup: number, units: Unit[] | null = null) {
this.getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHotgroup(null));
@@ -879,6 +909,7 @@ export class UnitsManager {
/** Add the selected units to a hotgroup. Units can be in multiple hotgroups at the same type
*
* @param hotgroup Hotgroup number
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
*/
addToHotgroup(hotgroup: number, units: Unit[] | null = null) {
if (units === null)
@@ -891,6 +922,7 @@ export class UnitsManager {
/** Delete the selected units
*
* @param explosion If true, the unit will be deleted using an explosion
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
* @returns
*/
delete(explosion: boolean = false, explosionType: string = "", units: Unit[] | null = null) {
@@ -929,6 +961,7 @@ export class UnitsManager {
*
* @param latlng Center of the group after the translation
* @param rotation Rotation of the group, in radians
* @param units (Optional) Array of units to apply the control to. If not provided, the operation will be completed on all selected units.
* @returns Array of positions for each unit, in order
*/
computeGroupDestination(latlng: LatLng, rotation: number, units: Unit[] | null = null) {
@@ -972,6 +1005,9 @@ export class UnitsManager {
*
*/
copy(units: Unit[] | null = null) {
if ( !getApp().getContextManager().getCurrentContext().getAllowUnitCopying() )
return;
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true });
@@ -989,6 +1025,9 @@ export class UnitsManager {
* @returns True if units were pasted successfully
*/
paste() {
if ( !getApp().getContextManager().getCurrentContext().getAllowUnitPasting() )
return;
let spawnPoints = 0;
/* If spawns are restricted, check that the user has the necessary spawn points */
@@ -1069,11 +1108,38 @@ export class UnitsManager {
const activeEras = Object.keys(eras).filter((key: string) => { return eras[key]; });
const activeRanges = Object.keys(ranges).filter((key: string) => { return ranges[key]; });
var airbases = getApp().getMissionManager().getAirbases();
Object.keys(airbases).forEach((airbaseName: string) => {
var airbase = airbases[airbaseName];
/* Check if the city is inside the coalition area */
if (polyContains(new LatLng(airbase.getLatLng().lat, airbase.getLatLng().lng), coalitionArea)) {
/* Arbitrary formula to obtain a number of units */
var pointsNumber = 2 + 10 * density / 100;
for (let i = 0; i < pointsNumber; i++) {
/* Place the unit nearby the airbase, depending on the distribution parameter */
var bearing = Math.random() * 360;
var distance = Math.random() * distribution * 100;
const latlng = bearingAndDistanceToLatLng(airbase.getLatLng().lat, airbase.getLatLng().lng, bearing, distance);
/* Make sure the unit is still inside the coalition area */
if (polyContains(latlng, coalitionArea)) {
const type = activeTypes[Math.floor(Math.random() * activeTypes.length)];
if (Math.random() < IADSDensities[type]) {
/* Get a random blueprint depending on the selected parameters and spawn the unit */
const unitBlueprint = randomUnitBlueprint(groundUnitDatabase, { type: type, eras: activeEras, ranges: activeRanges });
if (unitBlueprint)
this.spawnUnits("GroundUnit", [{ unitType: unitBlueprint.name, location: latlng, liveryID: "" }], coalitionArea.getCoalition(), false, "", "");
}
}
}
}
})
citiesDatabase.forEach((city: { lat: number, lng: number, pop: number }) => {
/* Check if the city is inside the coalition area */
if (polyContains(new LatLng(city.lat, city.lng), coalitionArea)) {
/* Arbitrary formula to obtain a number of units depending on the city population */
var pointsNumber = 2 + Math.pow(city.pop, 0.2) * density / 100;
var pointsNumber = 2 + Math.pow(city.pop, 0.15) * density / 100;
for (let i = 0; i < pointsNumber; i++) {
/* Place the unit nearby the city, depending on the distribution parameter */
var bearing = Math.random() * 360;
@@ -1086,8 +1152,8 @@ export class UnitsManager {
if (Math.random() < IADSDensities[type]) {
/* Get a random blueprint depending on the selected parameters and spawn the unit */
const unitBlueprint = randomUnitBlueprint(groundUnitDatabase, { type: type, eras: activeEras, ranges: activeRanges });
if (unitBlueprint)
this.spawnUnits("GroundUnit", [{ unitType: unitBlueprint.name, location: latlng, liveryID: "" }], coalitionArea.getCoalition(), true);
if (unitBlueprint)
this.spawnUnits("GroundUnit", [{ unitType: unitBlueprint.name, location: latlng, liveryID: "" }], coalitionArea.getCoalition(), false, "", "");
}
}
}
@@ -1099,52 +1165,18 @@ export class UnitsManager {
* TODO: Extend to aircraft and helicopters
*/
exportToFile() {
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
unitsToExport[unit.getGroupName()] = [data];
}
}
var a = document.createElement("a");
var file = new Blob([JSON.stringify(unitsToExport)], { type: 'text/plain' });
a.href = URL.createObjectURL(file);
a.download = 'export.json';
a.click();
if (!this.#unitDataExport)
this.#unitDataExport = new UnitDataFileExport("unit-export-dialog");
this.#unitDataExport.showForm(Object.values(this.#units));
}
/** Import ground and navy units from file
* TODO: extend to support aircraft and helicopters
*/
importFromFile() {
var input = document.createElement("input");
input.type = "file";
input.addEventListener("change", (e: any) => {
var file = e.target.files[0];
if (!file) {
return;
}
var reader = new FileReader();
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: UnitData) => { return unit.category == "GroundUnit"; }) || groups[groupName].every((unit: any) => { return unit.category == "NavyUnit"; }))) {
var aliveUnits = groups[groupName].filter((unit: UnitData) => { return unit.alive });
var units = aliveUnits.map((unit: UnitData) => {
return { unitType: unit.name, location: unit.position, liveryID: "" }
});
getApp().getUnitsManager().spawnUnits(groups[groupName][0].category, units, groups[groupName][0].coalition, true);
}
}
};
reader.readAsText(file);
})
input.click();
if (!this.#unitDataImport)
this.#unitDataImport = new UnitDataFileImport("unit-import-dialog");
this.#unitDataImport.selectFile();
}
/** Spawn a new group of units
@@ -1290,7 +1322,7 @@ export class UnitsManager {
const map = getApp().getMap();
const units = this.getSelectedUnits();
const numSelectedUnits = units.length;
const numProtectedUnits = units.filter((unit: Unit) => map.unitIsProtected(unit)).length;
const numProtectedUnits = units.filter((unit: Unit) => map.getIsUnitProtected(unit)).length;
if (numProtectedUnits === 1 && numSelectedUnits === numProtectedUnits)
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Notice: unit is protected`);
@@ -1300,6 +1332,6 @@ export class UnitsManager {
}
#unitIsProtected(unit: Unit) {
return getApp().getMap().unitIsProtected(unit)
return getApp().getMap().getIsUnitProtected(unit)
}
}

View File

@@ -36,17 +36,9 @@ export class Weapon extends CustomMarker {
super(new LatLng(0, 0), { riseOnHover: true, keyboard: false });
this.ID = ID;
/* Deselect units if they are hidden */
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
window.setTimeout(() => { !this.getHidden() }, 300);
});
document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => {
window.setTimeout(() => { !this.getHidden() }, 300);
});
document.addEventListener("mapVisibilityOptionsChanged", (ev: CustomEventInit) => {
/* Update the marker when the options change */
document.addEventListener("mapOptionsChanged", (ev: CustomEventInit) => {
this.#updateMarker();
});
}

View File

@@ -2,14 +2,14 @@
<div id="airbase-active-coalition-label" data-coalition="blue"></div>
<div class="upper-bar ol-panel">
<button data-coalition="blue" id="airbase-aircraft-spawn-button" title="Spawn aircraft" data-on-click="mapContextMenuShow"
data-on-click-params='{ "type": "aircraft" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/aircraft.svg" inject-svg></button>
data-on-click-params='{ "type": "aircraft" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/aircraft.svg" inject-svg></button>
<button data-coalition="blue" id="airbase-helicopter-spawn-button" title="Spawn helicopter" data-on-click="mapContextMenuShow"
data-on-click-params='{ "type": "helicopter" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/helicopter.svg" inject-svg></button>
data-on-click-params='{ "type": "helicopter" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/helicopter.svg" inject-svg></button>
</div>
<div id="airbase-aircraft-spawn-menu" class="ol-contexmenu-panel ol-panel hide">
<div id="airbase-aircraft-spawn-menu" class="ol-context-menu-panel ol-panel hide">
<!-- Here the aircraft spawn menu will be shown -->
</div>
<div id="airbase-helicopter-spawn-menu" class="ol-contexmenu-panel ol-panel hide">
<div id="airbase-helicopter-spawn-menu" class="ol-context-menu-panel ol-panel hide">
<!-- Here the helicopter spawn menu will be shown -->
</div>
</div>

View File

@@ -1,17 +1,17 @@
<div id="coalition-area-contextmenu" class="ol-context-menu" oncontextmenu="return false;">
<div id="area-coalition-label" data-coalition="blue"></div>
<div class="upper-bar ol-panel">
<div id="coalition-area-switch" class="ol-switch ol-coalition-switch"></div>
<div class="switch-control coalition no-label"><div id="coalition-area-switch" class="ol-switch"></div></div>
<button data-coalition="blue" id="iads-button" title="Create Integrated Air Defense System" data-on-click="coalitionAreaContextMenuShow"
data-on-click-params='{ "type": "iads" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/sam.svg" inject-svg></button>
data-on-click-params='{ "type": "iads" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/sam.svg" inject-svg></button>
<!--<button data-coalition="blue" id="cap-button" title="Create Combat Air Patrols" data-on-click="coalitionAreaContextMenuShow"
data-on-click-params='{ "type": "cap" }' class="ol-contexmenu-button"></button>-->
<button data-coalition="blue" id="coalitionarea-back-button" title="Bring area to back" data-on-click="coalitionAreaBringToBack"
class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/other/back.svg" inject-svg></button>
data-on-click-params='{ "type": "cap" }' class="ol-context-menu-button"></button>-->
<button data-coalition="blue" id="coalitionarea-back-button" title="Send to back" data-on-click="coalitionAreaBringToBack"
class="ol-context-menu-button"><img src="/resources/theme/images/buttons/other/back.svg" inject-svg></button>
<button data-coalition="blue" id="coalitionarea-delete-button" title="Delete area" data-on-click="coalitionAreaDelete"
class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/other/delete.svg" inject-svg></button>
class="ol-context-menu-button"><img src="/resources/theme/images/buttons/other/delete.svg" inject-svg></button>
</div>
<div id="iads-menu" class="ol-panel ol-contexmenu-panel hide">
<div id="iads-menu" class="ol-panel ol-context-menu-panel hide">
<div id="iads-units-type-options" class="ol-select">
<div class="ol-select-value">Unit types</div>
<div class="ol-select-options">

View File

@@ -1,60 +1,58 @@
<div id="map-contextmenu" class="ol-context-menu" oncontextmenu="return false;">
<div id="active-coalition-label" data-coalition="blue"></div>
<div class="upper-bar ol-panel">
<div id="coalition-switch" class="ol-switch ol-coalition-switch"></div>
<div class="switch-control coalition no-label"><div id="coalition-switch" class="ol-switch"></div></div>
<button data-coalition="blue" id="aircraft-spawn-button" title="Spawn aircraft" data-on-click="mapContextMenuShow"
data-on-click-params='{ "type": "aircraft" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/aircraft.svg" inject-svg></button>
data-on-click-params='{ "type": "aircraft" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/aircraft.svg" inject-svg></button>
<button data-coalition="blue" id="helicopter-spawn-button" title="Spawn helicopter" data-on-click="mapContextMenuShow"
data-on-click-params='{ "type": "helicopter" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/helicopter.svg" inject-svg></button>
data-on-click-params='{ "type": "helicopter" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/helicopter.svg" inject-svg></button>
<button data-coalition="blue" id="air-defence-spawn-button" title="Spawn air defence unit" data-on-click="mapContextMenuShow"
data-on-click-params='{ "type": "air-defence" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/sam.svg" inject-svg></button>
data-on-click-params='{ "type": "air-defence" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/sam.svg" inject-svg></button>
<button data-coalition="blue" id="groundunit-spawn-button" title="Spawn ground unit" data-on-click="mapContextMenuShow"
data-on-click-params='{ "type": "groundunit" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/groundunit.svg" inject-svg></button>
data-on-click-params='{ "type": "groundunit" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/groundunit.svg" inject-svg></button>
<button data-coalition="blue" id="coalition-area-button" title="Edit coalition area" data-on-click="editCoalitionArea"
class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/other/edit.svg" inject-svg></button>
class="ol-context-menu-button"><img src="/resources/theme/images/buttons/other/edit.svg" inject-svg></button>
<button data-coalition="blue" id="more-options-button" title="More options" data-on-click="mapContextMenuShow"
data-on-click-params='{ "type": "more" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/more.svg" inject-svg></button>
data-on-click-params='{ "type": "more" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/more.svg" inject-svg></button>
</div>
<div id="more-options-button-bar" class="upper-bar ol-panel hide">
<div id="coalition-switch" class="ol-switch ol-coalition-switch"></div>
<button data-coalition="blue" id="navyunit-spawn-button" title="Spawn navy unit" data-on-click="mapContextMenuShow"
data-on-click-params='{ "type": "navyunit" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/navyunit.svg" inject-svg></button>
data-on-click-params='{ "type": "navyunit" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/navyunit.svg" inject-svg></button>
<button data-coalition="blue" id="smoke-spawn-button" title="Spawn smoke" data-on-click="mapContextMenuShow"
data-on-click-params='{ "type": "smoke" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/smoke.svg" inject-svg></button>
data-on-click-params='{ "type": "smoke" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/smoke.svg" inject-svg></button>
<button data-coalition="blue" id="explosion-spawn-button" title="Explosion" data-on-click="mapContextMenuShow"
data-on-click-params='{ "type": "explosion" }' class="ol-contexmenu-button"><img src="/resources/theme/images/buttons/spawn/explosion.svg" inject-svg></button>
data-on-click-params='{ "type": "explosion" }' class="ol-context-menu-button"><img src="/resources/theme/images/buttons/spawn/explosion.svg" inject-svg></button>
<button data-coalition="blue" id="polygon-draw-button" title="Enter polygon draw mode" data-on-click="toggleCoalitionAreaDraw"
data-on-click-params='{"type": "polygon"}' class="ol-contexmenu-button"><img src="resources/theme/images/buttons/tools/draw-polygon-solid.svg" inject-svg></button>
data-on-click-params='{"type": "polygon"}' class="ol-context-menu-button"><img src="resources/theme/images/buttons/tools/draw-polygon-solid.svg" inject-svg></button>
</div>
<div id="aircraft-spawn-menu" class="ol-contexmenu-panel ol-panel hide">
<div id="aircraft-spawn-menu" class="ol-context-menu-panel ol-panel hide">
<!-- Here the aircraft spawn menu will be shown -->
</div>
<div id="helicopter-spawn-menu" class="ol-contexmenu-panel ol-panel hide">
<div id="helicopter-spawn-menu" class="ol-context-menu-panel ol-panel hide">
<!-- Here the helicopter spawn menu will be shown -->
</div>
<div id="air-defence-spawn-menu" class="ol-panel ol-contexmenu-panel hide">
<div id="air-defence-spawn-menu" class="ol-panel ol-context-menu-panel hide">
<!-- Here the air defence units' spawn menu will be shown -->
</div>
<div id="groundunit-spawn-menu" class="ol-panel ol-contexmenu-panel hide">
<div id="groundunit-spawn-menu" class="ol-panel ol-context-menu-panel hide">
<!-- Here the ground units' spawn menu will be shown -->
</div>
<div id="navyunit-spawn-menu" class="ol-panel ol-contexmenu-panel hide">
<div id="navyunit-spawn-menu" class="ol-panel ol-context-menu-panel hide">
<!-- Here the navy units' spawn menu will be shown -->
</div>
<div id="smoke-spawn-menu" class="ol-panel ol-contexmenu-panel hide">
<div id="smoke-spawn-menu" class="ol-panel ol-context-menu-panel hide">
<button class="smoke-button" title="" data-smoke-color="white" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "white" }'>White smoke</button>
<button class="smoke-button" title="" data-smoke-color="blue" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "blue" }'>Blue smoke</button>
<button class="smoke-button" title="" data-smoke-color="red" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "red" }'>Red smoke</button>
<button class="smoke-button" title="" data-smoke-color="green" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "green" }'>Green smoke</button>
<button class="smoke-button" title="" data-smoke-color="orange" data-on-click="contextMenuDeploySmoke" data-on-click-params='{ "color": "orange" }'>Orange smoke</button>
</div>
<div id="explosion-menu" class="ol-panel ol-contexmenu-panel hide">
<div id="explosion-menu" class="ol-panel ol-context-menu-panel hide">
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "normal", "strength": 1 }'>Small explosion</button>
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "normal", "strength": 10 }'>Big explosion</button>
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "phosphorous"}'>White phosphorous</button>
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "napalm"}'>Napalm</button>
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "secondary"}'>Explosion with secondaries</button>
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "secondary"}'>Explosion with debries</button>
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "fire"}'>Static fire</button>
<button class="explosion-button" title="" data-on-click="contextMenuExplosion" data-on-click-params='{ "explosionType": "depthCharge"}'>Depth charge</button>
</div>
</div>

View File

@@ -1,5 +1,17 @@
<html>
<script>
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const theme = urlParams.get('theme');
if (theme != undefined) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("PUT", "/resources/theme/" + theme);
xmlHttp.send("");
console.log("Setting theme to " + theme)
}
</script>
<head>
<title>Olympus client</title>
<link rel="stylesheet" type="text/css" href="stylesheets/olympus.css" />
@@ -50,6 +62,12 @@
<!-- Grayout effect of the background when login prompt is visible -->
<div id="gray-out"></div>
<!-- Loading screen -->
<div id="loading-screen">
<img src="images/olympus-500x500.png">
<div>Loading DCS Olympus...</div>
</div>
<script src="javascripts/bundle.js"></script>
</body>

View File

@@ -1,333 +1,19 @@
<div id="splash-screen" class="ol-dialog" oncontextmenu="return false;">
<div id="splash-content" class="ol-dialog-content">
<div id="app-summary">
<h2>DCS Olympus</h2>
<h4>Dynamic Unit Command</h4>
<div class="app-version">Version <span class="app-version-number">v0.4.7-alpha</span></div>
</div>
<form id="authentication-form">
<div><h5>Name</h5> <input type="text" id="username" name="username" required autocomplete="username" placeholder="Enter username..."></div>
<div><h5>Server password</h5> <input type="password" id="password" name="password" minlength="8" required autocomplete="current-password" placeholder="Enter password..."></div>
<button type="submit" id="connection-button" class="ol-button-apply">Connect</button>
</form>
<h5 id="login-status"><br></h5>
<div id="legal-stuff">
<h5>DISCLAIMER</h5>
<p>
Copyright (C) 2023 Veltro & Gang
</p>
<p>
DCS Olympus (the "MATERIAL" or "Software") is provided completely free
to users subject to the it under both the terms of version 3 of the GNU
General Public License as published by the Free Software Foundation, and
the additional terms set out below; except where such terms conflict with this
disclaimer, in which case, the terms of this disclaimer shall prevail.
</p>
<p>
The authors and/or copyright holders of the Software have not received any
financial benefit in connection with the Software. In any event, the
Software is provided “as is”, without warranty of any kind, express or
implied, including but not limited to the warranties of merchantability,
fitness for a particular purpose and non-infringement. In no event shall
the authors and/or copyright holders be liable for any claim, damages or
other liability, whether in an action of contract, tort or otherwise,
arising from, out of or in connection with the Software or the use or o
ther dealings in the Software.
</p>
<p>
Any party making use of the Software in any manner agrees to be
bound by the terms set out in this disclaimer, version 3 of the GNU
General Public Licence, and the Additional Terms below.
</p>
<p>
THIS MATERIAL IS NOT MADE OR SUPPORTED BY EAGLE DYNAMICS SA.
</p>
</div>
</div>
</div>
<div id="advanced-settings-dialog" class="ol-panel ol-dialog olympus-dialog-close hide" oncontextmenu="return false;">
<div class="ol-dialog-close" data-on-click="closeDialog"></div>
<div class="ol-dialog-header">
<h3 id="unit-name">Olympus 1-1</h3>
</div>
<div class="ol-dialog-content">
<!-- General settings -->
<div id="general-settings">
<div class="ol-group">
<h4>General settings</h4>
<hr>
</div>
<div id="general-settings-grid">
<div id="prohibit-jettison-checkbox" class="ol-checkbox air-unit-checkbox">
<label title="The unit will not jettison external stores">
<input type="checkbox"/>
Prohibit jettison
</label>
</div>
<div id="prohibit-afterburner-checkbox" class="ol-checkbox air-unit-checkbox">
<label title="The unit will not engage the afterburner">
<input type="checkbox" />
Prohibit afterburner
</label>
</div>
<div id="prohibit-AA-checkbox" class="ol-checkbox air-unit-checkbox">
<label title="The unit will not engage airborne targets">
<input type="checkbox" />
Prohibit A/A
</label>
</div>
<div id="prohibit-AG-checkbox" class="ol-checkbox air-unit-checkbox">
<label title="The unit will not engage ground targets">
<input type="checkbox" />
Prohibit A/G
</label>
</div>
<div id="prohibit-air-wpn-checkbox" class="ol-checkbox">
<label title="The unit will not engage air weapons (e.g. SAM sites will not engage HARMs)">
<input type="checkbox" />
Prohibit air wpn engage
</label>
</div>
</div>
</div>
<!-- TACAN options -->
<div id="TACAN-options">
<div class="ol-group">
<h4>TACAN options</h4>
<hr>
</div>
<div id="TACAN-checkbox" class="ol-checkbox">
<label title="Turn ON the TACAN">
<input type="checkbox" />
Activate TACAN
</label>
</div>
<div class="ol-group">
<label>TACAN: </label>
<div class="ol-group">
<div id="TACAN-channel" class="ol-text-input">
<input type="number" onkeypress='return event.charCode >= 48 && event.charCode <= 57' onkeyup="if (value > 126) value = 126;">
</div>
<div id="TACAN-XY" class="ol-select">
<div class="ol-select-value">X</div>
<div class="ol-select-options">
</div>
</div>
<div id="TACAN-callsign" class="ol-text-input">
<input type="text" maxlength="3" value="TKR" style="width: 50px">
</div>
</div>
</div>
</div>
<!-- Radio options -->
<div id="radio-options">
<div class="ol-group">
<h4>Radio options</h4>
<hr>
</div>
<div class="ol-group">
<label> Radio frequency: </label>
<div class="ol-group">
<div id="radio-mhz" class="ol-text-input">
<input type="number" onkeypress='return event.charCode >= 48 && event.charCode <= 57' onkeyup="if (value > 999) value = 999;">
</div>
<div id="radio-decimals" class="ol-select">
<div class="ol-select-value">.000</div>
<div class="ol-select-options">
</div>
</div>
</div>
</div>
<div class="ol-group">
<label> Radio callsign: </label>
<div class="ol-group">
<div id="radio-callsign" class="ol-select">
<div class="ol-select-value"></div>
<div class="ol-select-options">
</div>
</div>
<label>
-
</label>
<div id="radio-callsign-number" class="ol-text-input">
<input type="number" min="1" max="999" step="1" value="1">
</div>
</div>
</div>
</div>
</div>
<div class="ol-dialog-footer ol-group">
<button class="ol-button-apply" data-on-click="applyAdvancedSettings">Apply</button>
<button class="ol-button-close" data-on-click="closeDialog">Close</button>
</div>
</div>
<div id="custom-formation-dialog" class="ol-panel ol-dialog olympus-dialog-close hide" oncontextmenu="return false;">
<div class="ol-dialog-close" data-on-click="closeDialog"></div>
<div class="ol-dialog-header">
<h3 id="unit-name">Custom formation</h3>
</div>
<div class="ol-dialog-content">
<div class="formation-position-clock">
<div class="clock-hand" style="top: 0px; left: 50px;"><input type="radio" id="formation-1" name="formation-position" value="1"></div>
<div class="clock-hand" style="top: 14px; left: 14px;"><input type="radio" id="formation-2" name="formation-position" value="2"></div>
<div class="clock-hand" style="top: 50px; left: 0px; "><input type="radio" id="formation-3" name="formation-position" value="3"></div>
<div class="clock-hand" style="top: 86px; left: 14px;"><input type="radio" id="formation-4" name="formation-position" value="4"></div>
<div class="clock-hand" style="top: 100px; left: 50px;"><input type="radio" id="formation-5" name="formation-position" value="5"></div>
<div class="clock-hand" style="top: 86px; left: 86px;"><input type="radio" id="formation-6" name="formation-position" value="6" checked></div>
<div class="clock-hand" style="top: 50px; left: 100px"><input type="radio" id="formation-7" name="formation-position" value="7"></div>
<div class="clock-hand" style="top: 14px; left: 86px;"><input type="radio" id="formation-8" name="formation-position" value="8"></div>
<div style="top: 50px; left: 50px;" id="reference-system"></div>
</div>
<div class="ol-group">
<label>Distance: </label>
<div class="ol-group">
<div id="distance" class="ol-text-input">
<input type="number" min="0" max="999999" step="1" value="150">
</div>
<label>ft</label>
</div>
</div>
<div class="ol-group">
<label>Up-down: </label>
<div class="ol-group">
<div id="up-down" class="ol-text-input">
<input type="number" min="-99999" max="99999" step="1" value="30">
</div>
<label>ft</label>
</div>
</div>
</div>
<div class="ol-dialog-footer ol-group">
<button class="ol-button-apply" data-on-click="applyCustomFormation">Apply</button>
<button class="ol-button-close" data-on-click="closeDialog">Close</button>
</div>
</div>
<div id="command-mode-settings-dialog" class="ol-panel ol-dialog olympus-dialog-close hide" oncontextmenu="return false;">
<div class="ol-dialog-close" data-on-click="closeDialog"></div>
<div class="ol-dialog-header">
<h3 id="unit-name">Command mode settings</h3>
</div>
<div class="ol-dialog-content">
<div id="restrict-spawns" class="ol-checkbox">
<label title="If false, no spawn restrictions will be applied">
<input type="checkbox"/>
Restrict spawns
</label>
</div>
<div id="restrict-to-coalition" class="ol-checkbox">
<label title="If true, commanders will be allowed to only spawn units that belong to their coalition. E.g. blue commanders will be able to spawn F/A-18 Hornets, but not MiG-29s.">
<input type="checkbox"/>
Restrict units to coalition
</label>
</div>
<div class="ol-group">
<label>Setup time: </label>
<div class="ol-group">
<div id="setup-time" class="ol-text-input">
<input type="number" min="-99999" max="99999" step="1" value="10">
</div>
<label>minutes</label>
</div>
</div>
<div class="ol-group">
<label>Available eras: </label>
<div id="command-mode-era-options" class="ol-select">
<div class="ol-select-value">Select eras</div>
<div class="ol-select-options">
<!-- This is where all the available era buttons will be shown-->
</div>
</div>
</div>
<div class="ol-group">
<h4>Spawn points</h4>
<hr>
</div>
<div class="ol-group">
<label>Blue spawn points: </label>
<div id="blue-spawn-points" class="ol-text-input">
<input type="number" min="0" max="999999" step="1" value="1000">
</div>
</div>
<div class="ol-group">
<label>Red spawn points: </label>
<div id="red-spawn-points" class="ol-text-input">
<input type="number" min="0" max="999999" step="1" value="1000">
</div>
</div>
</div>
<div class="ol-dialog-footer ol-group">
<button class="ol-button-apply" data-on-click="applycommandModeOptions">Apply</button>
<button class="ol-button-close" data-on-click="closeDialog">Close</button>
</div>
</div>
<div id="slow-delete-dialog" class="ol-panel ol-dialog hide" oncontextmenu="return false;">
<div class="ol-dialog-header">
<h3 id="unit-name">Confirm deletion method</h3>
</div>
<div class="ol-dialog-content">
<p>You are trying to delete a large amount of units (<span class="deletion-count"></span>), which can cause the server to lag for players.</p>
<p>You may:
<ul>
<li>delete in batches (less lag but Olympus cannot process any additional orders until<br /> all units have been deleted);</li>
<li>delete immediately (you can continue to give Olympus orders but players may<br />experience lag while this happens);</li>
<li>cancel this instruction.</li>
</ul></p>
</div>
<div class="ol-dialog-footer ol-group">
<button data-action="delete-slow">Delete in batches (~<span class="deletion-time"></span>s)</button>
<button data-action="delete-immediate">Delete immediately</button>
<button data-action="delete-cancel">Cancel</button>
</div>
</div>
<%- include('dialogs/advancedsettings.ejs') %>
<%- include('dialogs/commandmodesettings.ejs') %>
<%- include('dialogs/customformation.ejs') %>
<%- include('dialogs/importexport.ejs', {
"dialogId": "unit-export-dialog",
"submitButtonText": "Export units to file",
"textContent": "Select the unit categories you would like to export. Note: only ground and naval units can be exported at this time.",
"title": "Export",
"showFilenameInput": true
}) %>
<%- include('dialogs/importexport.ejs', {
"dialogId": "unit-import-dialog",
"submitButtonText": "Import units into mission",
"textContent": "Select the unit categories you would like to import.",
"title": "Import",
"showFilenameInput": false
}) %>
<%- include('dialogs/slowdelete.ejs') %>
<%- include('dialogs/splash.ejs') %>

View File

@@ -0,0 +1,162 @@
<div id="advanced-settings-dialog" class="ol-panel ol-dialog olympus-dialog-close hide" oncontextmenu="return false;">
<div class="ol-dialog-close" data-on-click="closeDialog"></div>
<div class="ol-dialog-header">
<h3 id="unit-name">Olympus 1-1</h3>
</div>
<div class="ol-dialog-content">
<!-- General settings -->
<div id="general-settings">
<div class="ol-group">
<h4>General settings</h4>
<hr>
</div>
<div id="general-settings-grid">
<div id="prohibit-jettison-checkbox" class="ol-checkbox air-unit-checkbox">
<label title="The unit will not jettison external stores">
<input type="checkbox"/>
Prohibit jettison
</label>
</div>
<div id="prohibit-afterburner-checkbox" class="ol-checkbox air-unit-checkbox">
<label title="The unit will not engage the afterburner">
<input type="checkbox" />
Prohibit afterburner
</label>
</div>
<div id="prohibit-AA-checkbox" class="ol-checkbox air-unit-checkbox">
<label title="The unit will not engage airborne targets">
<input type="checkbox" />
Prohibit A/A
</label>
</div>
<div id="prohibit-AG-checkbox" class="ol-checkbox air-unit-checkbox">
<label title="The unit will not engage ground targets">
<input type="checkbox" />
Prohibit A/G
</label>
</div>
<div id="prohibit-air-wpn-checkbox" class="ol-checkbox">
<label title="The unit will not engage air weapons (e.g. SAM sites will not engage HARMs)">
<input type="checkbox" />
Prohibit air wpn engage
</label>
</div>
</div>
</div>
<!-- Tasking -->
<!--
<div id="tasking">
<div class="ol-group">
<h4>Tasking</h4>
<hr>
</div>
<div id="tanker-checkbox" class="ol-checkbox">
<label title="The unit will operate as Air to Air Refuelling tanker for airplanes that have a compatible refuelling system">
<input type="checkbox" />
Operate as AAR tanker
</label>
</div>
<div id="AWACS-checkbox" class="ol-checkbox">
<label title="The unit will operate as AWACS on datalink">
<input type="checkbox" />
Operate as AWACS
</label>
</div>
</div>
-->
<!-- TACAN options -->
<div id="TACAN-options">
<div class="ol-group">
<h4>TACAN options</h4>
<hr>
</div>
<div id="TACAN-checkbox" class="ol-checkbox">
<label title="Turn ON the TACAN">
<input type="checkbox" />
Activate TACAN
</label>
</div>
<div class="ol-group">
<label>TACAN: </label>
<div class="ol-group">
<div id="TACAN-channel" class="ol-text-input">
<input type="number" min="1" max="126" step="1" value="40">
</div>
<div id="TACAN-XY" class="ol-select">
<div class="ol-select-value">X</div>
<div class="ol-select-options">
</div>
</div>
<div id="TACAN-callsign" class="ol-text-input">
<input type="text" maxlength="3" value="TKR" style="width: 50px">
</div>
</div>
</div>
</div>
<!-- Radio options -->
<div id="radio-options">
<div class="ol-group">
<h4>Radio options</h4>
<hr>
</div>
<div class="ol-group">
<label> Radio frequency: </label>
<div class="ol-group">
<div id="radio-mhz" class="ol-text-input">
<input type="number" min="1" max="999" step="1" value="260">
</div>
<div id="radio-decimals" class="ol-select">
<div class="ol-select-value">.000</div>
<div class="ol-select-options">
</div>
</div>
</div>
</div>
<div class="ol-group">
<label> Radio callsign: </label>
<div class="ol-group">
<div id="radio-callsign" class="ol-select">
<div class="ol-select-value"></div>
<div class="ol-select-options">
</div>
</div>
<label>
-
</label>
<div id="radio-callsign-number" class="ol-text-input">
<input type="number" min="1" max="999" step="1" value="1">
</div>
</div>
</div>
</div>
</div>
<div class="ol-dialog-footer ol-group">
<button class="ol-button-apply" data-on-click="applyAdvancedSettings">Apply</button>
<button class="ol-button-close" data-on-click="closeDialog">Close</button>
</div>
</div>

View File

@@ -0,0 +1,70 @@
<div id="command-mode-settings-dialog" class="ol-panel ol-dialog olympus-dialog-close hide" oncontextmenu="return false;">
<div class="ol-dialog-close" data-on-click="closeDialog"></div>
<div class="ol-dialog-header">
<h3 id="unit-name">Command mode settings</h3>
</div>
<div class="ol-dialog-content">
<div id="restrict-spawns" class="ol-checkbox">
<label title="If false, no spawn restrictions will be applied">
<input type="checkbox"/>
Restrict spawns
</label>
</div>
<div id="restrict-to-coalition" class="ol-checkbox">
<label title="If true, commanders will be allowed to only spawn units that belong to their coalition. E.g. blue commanders will be able to spawn F/A-18 Hornets, but not MiG-29s.">
<input type="checkbox"/>
Restrict units to coalition
</label>
</div>
<div class="ol-group">
<label>Setup time: </label>
<div class="ol-group">
<div id="setup-time" class="ol-text-input">
<input type="number" min="-99999" max="99999" step="1" value="10">
</div>
<label>minutes</label>
</div>
</div>
<div class="ol-group">
<label>Available eras: </label>
<div id="command-mode-era-options" class="ol-select">
<div class="ol-select-value">Select eras</div>
<div class="ol-select-options">
<!-- This is where all the available era buttons will be shown-->
</div>
</div>
</div>
<div class="ol-group">
<h4>Spawn points</h4>
<hr>
</div>
<div class="ol-group">
<label>Blue spawn points: </label>
<div id="blue-spawn-points" class="ol-text-input">
<input type="number" min="0" max="999999" step="1" value="1000">
</div>
</div>
<div class="ol-group">
<label>Red spawn points: </label>
<div id="red-spawn-points" class="ol-text-input">
<input type="number" min="0" max="999999" step="1" value="1000">
</div>
</div>
</div>
<div class="ol-dialog-footer ol-group">
<button class="ol-button-apply" data-on-click="applycommandModeOptions">Apply</button>
<button class="ol-button-close" data-on-click="closeDialog">Close</button>
</div>
</div>

View File

@@ -0,0 +1,48 @@
<div id="custom-formation-dialog" class="ol-panel ol-dialog olympus-dialog-close hide" oncontextmenu="return false;">
<div class="ol-dialog-close" data-on-click="closeDialog"></div>
<div class="ol-dialog-header">
<h3 id="unit-name">Custom formation</h3>
</div>
<div class="ol-dialog-content">
<div class="formation-position-clock">
<div class="clock-hand" style="top: 0px; left: 50px;"><input type="radio" id="formation-1" name="formation-position" value="1"></div>
<div class="clock-hand" style="top: 14px; left: 14px;"><input type="radio" id="formation-2" name="formation-position" value="2"></div>
<div class="clock-hand" style="top: 50px; left: 0px; "><input type="radio" id="formation-3" name="formation-position" value="3"></div>
<div class="clock-hand" style="top: 86px; left: 14px;"><input type="radio" id="formation-4" name="formation-position" value="4"></div>
<div class="clock-hand" style="top: 100px; left: 50px;"><input type="radio" id="formation-5" name="formation-position" value="5"></div>
<div class="clock-hand" style="top: 86px; left: 86px;"><input type="radio" id="formation-6" name="formation-position" value="6" checked></div>
<div class="clock-hand" style="top: 50px; left: 100px"><input type="radio" id="formation-7" name="formation-position" value="7"></div>
<div class="clock-hand" style="top: 14px; left: 86px;"><input type="radio" id="formation-8" name="formation-position" value="8"></div>
<div style="top: 50px; left: 50px;" id="reference-system"></div>
</div>
<div class="ol-group">
<label>Distance: </label>
<div class="ol-group">
<div id="distance" class="ol-text-input">
<input type="number" min="0" max="999999" step="1" value="150">
</div>
<label>ft</label>
</div>
</div>
<div class="ol-group">
<label>Up-down: </label>
<div class="ol-group">
<div id="up-down" class="ol-text-input">
<input type="number" min="-99999" max="99999" step="1" value="30">
</div>
<label>ft</label>
</div>
</div>
</div>
<div class="ol-dialog-footer ol-group">
<button class="ol-button-apply" data-on-click="applyCustomFormation">Apply</button>
<button class="ol-button-close" data-on-click="closeDialog">Close</button>
</div>
</div>

View File

@@ -0,0 +1,29 @@
<div id="<%= dialogId %>" class="ol-panel ol-dialog file-import-export hide" oncontextmenu="return false;">
<div class="ol-dialog-header">
<h3><%= title %></h3>
</div>
<div class="ol-dialog-content">
<p><%= textContent %></p>
<% if (showFilenameInput) { %>
<div class="export-filename-container">
<label>Filename:</label>
<input id="export-filename">
<img src="resources/theme/images/icons/keyboard-solid.svg">
</div>
<% } %>
<table class="categories-coalitions">
<thead>
</thead>
<tbody>
</tbody>
</table>
</div>
<div class="ol-dialog-footer ol-group">
<button class="start-transfer"><%= submitButtonText %></button>
<button data-on-click="closeDialog">Close</button>
</div>
</div>

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