128 Commits

Author SHA1 Message Date
Pax1601
0459d5c625 v0.4.13-alpha-rc5 2023-12-08 15:38:56 +01:00
Pax1601
43d28ebe19 Removed notify and added warning follow roads 2023-12-08 14:27:16 +01:00
Pax1601
187b9be57a Added ability to add mods 2023-12-08 11:54:12 +01:00
Pax1601
564a650403 Merge branch 'main' of https://github.com/Pax1601/DCSOlympus 2023-12-07 15:49:31 +01:00
Pax1601
0e403e8b74 v0.4.12-alpha-rc4 2023-12-07 15:49:27 +01:00
Pax1601
840cdd9049 Update README.md 2023-12-07 15:39:48 +01:00
Pax1601
e5e7e9be14 Added increased load for onRoad movement 2023-12-07 15:22:59 +01:00
Pax1601
e677968ba7 Merge branch 'main' of https://github.com/Pax1601/DCSOlympus 2023-12-07 15:08:01 +01:00
Pax1601
d24b955d52 Multiple bugfixes 2023-12-07 15:07:57 +01:00
Pax1601
48860ff514 Merge pull request #677 from Pax1601/674-airbases-visibility-button-still-dark-when-off
Fixed incorrect colour
2023-12-06 20:39:37 +01:00
PeekabooSteam
0e78b7559b Robots are no longer considered for waypoints 2023-12-06 15:27:05 +00:00
PeekabooSteam
25edfc45e5 Added coalition visibility tooltips 2023-12-06 15:03:15 +00:00
PeekabooSteam
885825e5cc Fixed incorrect colour 2023-12-06 14:54:36 +00:00
Pax1601
344413ae74 Undone wrong change in pcall 2023-12-06 13:08:52 +01:00
Pax1601
fa0643987b Remove need for path variable 2023-12-06 12:37:59 +01:00
Pax1601
dcff462b32 v0.4.11-alpha-rc3 2023-12-06 09:15:44 +01:00
Pax1601
fa04e5f8bb Merge pull request #670 from Pax1601/650-robots-can-be-sent-to-refuel-while-protected
Added extra protection for robots
2023-12-06 08:17:21 +01:00
Pax1601
c8d5f9ce0e Formatted file 2023-12-06 08:17:01 +01:00
Pax1601
55642a89b1 Merge pull request #671 from Pax1601/651-control-tips-quick-options-shown-but-nothing-happens
Removed Quick Options option
2023-12-06 08:12:00 +01:00
PeekabooSteam
f305aa3929 Removed Quick Options option 2023-12-05 21:51:32 +00:00
PeekabooSteam
47ee88c339 Added extra protection for robots 2023-12-05 20:44:26 +00:00
Pax1601
022e041f68 Merge branch 'main' of https://github.com/Pax1601/DCSOlympus 2023-12-05 19:30:50 +01:00
Pax1601
0cc53890c1 v0.4.10-alpha-rc2 2023-12-05 19:30:34 +01:00
Pax1601
e37f7a4977 v0.4.10-alpha-rc1 2023-12-05 19:18:37 +01:00
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
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
124 changed files with 30254 additions and 24615 deletions

14
LEGAL
View File

@@ -2,7 +2,8 @@ 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
@@ -665,4 +666,13 @@ 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,68 @@
![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:
# Frequently Asked Questions
### Can I join up and help out with the project? ###
We are currently running towards first release in the very near future so we are not looking to add more people to the core team for the moment. However that does not mean we are not open to collaborations and help going forward, if you want to help for now we are committed to the free and open source model so feel free to check out the github, familiarize yourself with the project and maybe even start submitting pull requests for open issues.
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.
Post-release we will be more interested in developing partnerships/collaborations with other teams/projects and potentially bringing in more team members, we will update this after release on how that will be managed!
### Can I be a beta/alpha-tester? ###
With first public release planned for the very-near future we are fully committed to the final sprint, as such we will not be formally recruiting more people to test pre-release.
Post-release we will be eager to hear feedback of all forms and take in bug-reports, at this time after release we will begin considering bringing in more team members to test in development versions as we go.
### Do you have a roadmap? ###
We do not have a roadmap no, we have a laundry list of things we are hoping to do.
These include but are not limited to:
1) Enhancements to helicopter play
2) More features around use of ground units
3) More unique effects and behaviours
4) ATC/AIC features
5) Usability features like unit painters etc
However we cannot commit to specific features, feature release order, or timelines, please remember this isn't our job and we work on it in our free time because we love DCS
### Does Olympus support mods? ###
Generally OIympus will not have any issues with other mods, however you may need to tell olympus about modded units in order to be able to dynamically spawn them etc
Keep in mind that any mods you do choose to spawn your players will need to have, some mod unit just appear as a su27 or leo2 etc. when a player is missing them, others can cause client crashes. So be smart about how you use them
### Is Olympus compatible with mission scripts? ###
We have tried hard to keep Olympus from interfering with other scripts, we have tested with a variety of new and old mission scripts and generally expect it will not be an issue.
However we cannot foresee everything people come up with so we suggest testing with what you have in mind once olympus releases
### How does it work? ###
The quick answer is magic.
The long answer is well all the code is there for you to read.
The middle answer is a bit like SRS does. Olympus consists of two parts.
(A) Olympus back end: A dll, run by DCS, that sends data out and gets commands in via a REST API;
(B) Webserver exe: The one you start when starting the server via the desktop shortcut.
A and B never communicate when you connect the client you download the web page and some other minor stuff from B, and you get the DCS data from and send commands to A.
### How much does Olympus impact performance? ###
Olympus by itself should not have a noticeable impact on server performance, however the ability for the user to spawn arbitrary units and command engagements means Olympus can be used in such a way that brings the game to it's knees.
Be cognizant of how you play, whether it's done through Olympus or the mission editor 500 MLRS units firing at once is not going to go over well with most servers

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,28 +1,2 @@
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 ..
call build.bat
call "C:\Program Files (x86)\Inno Setup 6\iscc.exe" "installer\olympus.iss"

View File

@@ -128,7 +128,16 @@ declare module "constants/constants" {
export const altitudeIncrements: {
[key: string]: number;
};
export const minimapBoundaries: LatLng[][];
export const minimapBoundaries: {
Nevada: LatLng[];
Syria: LatLng[];
Caucasus: LatLng[];
PersianGulf: LatLng[];
MarianaIslands: LatLng[];
Falklands: LatLng[];
Normandy: LatLng[];
SinaiMap: LatLng[];
};
export const mapBounds: {
Syria: {
bounds: LatLngBounds;
@@ -150,6 +159,18 @@ declare module "constants/constants" {
bounds: LatLngBounds;
zoom: number;
};
Falklands: {
bounds: LatLngBounds;
zoom: number;
};
Normandy: {
bounds: LatLngBounds;
zoom: number;
};
SinaiMap: {
bounds: LatLngBounds;
zoom: number;
};
};
export const mapLayers: {
"ArcGIS Satellite": {
@@ -200,6 +221,7 @@ declare module "constants/constants" {
export const IADSDensities: {
[key: string]: number;
};
export const GROUND_UNIT_AIR_DEFENCE_REGEX: RegExp;
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)";
@@ -228,33 +250,34 @@ declare module "constants/constants" {
horizontalVelocity = 15,
verticalVelocity = 16,
heading = 17,
isActiveTanker = 18,
isActiveAWACS = 19,
onOff = 20,
followRoads = 21,
fuel = 22,
desiredSpeed = 23,
desiredSpeedType = 24,
desiredAltitude = 25,
desiredAltitudeType = 26,
leaderID = 27,
formationOffset = 28,
targetID = 29,
targetPosition = 30,
ROE = 31,
reactionToThreat = 32,
emissionsCountermeasures = 33,
TACAN = 34,
radio = 35,
generalSettings = 36,
ammo = 37,
contacts = 38,
activePath = 39,
isLeader = 40,
operateAs = 41,
shotsScatter = 42,
shotsIntensity = 43,
health = 44,
track = 18,
isActiveTanker = 19,
isActiveAWACS = 20,
onOff = 21,
followRoads = 22,
fuel = 23,
desiredSpeed = 24,
desiredSpeedType = 25,
desiredAltitude = 26,
desiredAltitudeType = 27,
leaderID = 28,
formationOffset = 29,
targetID = 30,
targetPosition = 31,
ROE = 32,
reactionToThreat = 33,
emissionsCountermeasures = 34,
TACAN = 35,
radio = 36,
generalSettings = 37,
ammo = 38,
contacts = 39,
activePath = 40,
isLeader = 41,
operateAs = 42,
shotsScatter = 43,
shotsIntensity = 44,
health = 45,
endOfData = 255
}
export const MGRS_PRECISION_10KM = 2;
@@ -265,6 +288,9 @@ declare module "constants/constants" {
export const DELETE_CYCLE_TIME = 0.05;
export const DELETE_SLOW_THRESHOLD = 50;
export const GROUPING_ZOOM_TRANSITION = 13;
export const MAX_SHOTS_SCATTER = 3;
export const MAX_SHOTS_INTENSITY = 3;
export const SHOTS_SCATTER_DEGREES = 10;
}
declare module "map/markers/custommarker" {
import { Map, Marker } from "leaflet";
@@ -315,15 +341,40 @@ declare module "controls/dropdown" {
#private;
constructor(ID: string | null, callback: CallableFunction, options?: string[] | null, defaultText?: string);
getContainer(): HTMLElement;
setOptions(optionsList: string[], sort?: "" | "string" | "number" | "string+number"): void;
/** 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", labelsList?: string[] | undefined): void;
getOptionsList(): string[];
getLabelsList(): string[] | undefined;
/** 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[]): void;
getOptionElements(): HTMLCollection;
addOptionElement(optionElement: HTMLElement): void;
selectText(text: string): void;
/** 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): boolean;
reset(): void;
getValue(): string;
/** Manually set the selected value of the dropdown
*
* @param value The value to select. Must be one of the valid options
*/
setValue(value: string): void;
getValue(): string;
/** 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): void;
getIndex(): number;
clip(): void;
@@ -497,6 +548,7 @@ declare module "interfaces" {
}
export interface UnitData {
category: string;
categoryDisplayName: string;
ID: number;
alive: boolean;
human: boolean;
@@ -514,6 +566,7 @@ declare module "interfaces" {
horizontalVelocity: number;
verticalVelocity: number;
heading: number;
track: number;
isActiveTanker: boolean;
isActiveAWACS: boolean;
onOff: boolean;
@@ -786,7 +839,7 @@ declare module "other/utils" {
export function enumToCoalition(coalitionID: number): "" | "blue" | "red" | "neutral";
export function coalitionToEnum(coalition: string): 0 | 1 | 2;
export function convertDateAndTimeToDate(dateAndTime: DateAndTime): Date;
export function createCheckboxOption(value: string, text: string, checked?: boolean, callback?: CallableFunction): HTMLElement;
export function createCheckboxOption(value: string, text: string, checked?: boolean, callback?: CallableFunction, options?: any): HTMLElement;
export function getCheckboxOptions(dropdown: Dropdown): {
[key: string]: boolean;
};
@@ -814,17 +867,20 @@ declare module "controls/unitspawnmenu" {
import { UnitDatabase } from "unit/databases/unitdatabase";
import { Airbase } from "mission/airbase";
import { UnitSpawnOptions } 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 {
#private;
protected showRangeCircles: boolean;
protected spawnOptions: UnitSpawnOptions;
protected unitTypeFilter: (unit: any) => boolean;
protected spawnOptions: UnitSpawnOptions;
constructor(ID: string, unitDatabase: UnitDatabase, orderByRole: boolean);
abstract deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number): void;
getContainer(): HTMLElement;
getVisible(): boolean;
reset(): void;
setCountries(): void;
refreshOptions(): void;
showCirclesPreviews(): void;
clearCirclesPreviews(): void;
setAirbase(airbase: Airbase | undefined): void;
@@ -838,7 +894,8 @@ declare module "controls/unitspawnmenu" {
getLiveryDropdown(): Dropdown;
getLoadoutPreview(): HTMLDivElement;
getAltitudeSlider(): Slider;
deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number): void;
setShowLoadout(showLoadout: boolean): void;
setShowAltitudeSlider(showAltitudeSlider: boolean): void;
}
export class AircraftSpawnMenu extends UnitSpawnMenu {
/**
@@ -1091,6 +1148,7 @@ declare module "unit/unit" {
getHorizontalVelocity(): number;
getVerticalVelocity(): number;
getHeading(): number;
getTrack(): number;
getIsActiveAWACS(): boolean;
getIsActiveTanker(): boolean;
getOnOff(): boolean;
@@ -1146,6 +1204,11 @@ declare module "unit/unit" {
* @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(): string;
/********************** 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
*
@@ -1219,6 +1282,8 @@ declare module "unit/unit" {
setGroup(group: Group | null): void;
drawLines(): void;
checkZoomRedraw(): boolean;
isControlledByDCS(): boolean;
isControlledByOlympus(): boolean;
/********************** Icon *************************/
createIcon(): void;
/********************** Visibility *************************/
@@ -1676,22 +1741,63 @@ declare module "mission/missionmanager" {
export class MissionManager {
#private;
constructor();
/** Update location of bullseyes
*
* @param object <BulleyesData>
*/
updateBullseyes(data: BullseyesData): void;
/** Update airbase information
*
* @param object <AirbasesData>
*/
updateAirbases(data: AirbasesData): void;
/** Update mission information
*
* @param object <MissionData>
*/
updateMission(data: MissionData): void;
/** Get the bullseyes set in this theatre
*
* @returns object
*/
getBullseyes(): {
[name: string]: Bullseye;
};
/** Get the airbases in this theatre
*
* @returns object
*/
getAirbases(): {
[name: string]: Airbase;
};
/** Get the options/settings as set in the command mode
*
* @returns object
*/
getCommandModeOptions(): CommandModeOptions;
/** Get the current date and time of the mission (based on local time)
*
* @returns object
*/
getDateAndTime(): DateAndTime;
/**
* Get the number of seconds left of setup time
* @returns number
*/
getRemainingSetupTime(): number;
/** Get an object with the coalitions in it
*
* @returns object
*/
getCoalitions(): {
red: string[];
blue: string[];
};
/** Get the current theatre (map) name
*
* @returns string
*/
getTheatre(): string;
getAvailableSpawnPoints(): number;
getCommandedCoalition(): "blue" | "red" | "all";
refreshSpawnPoints(): void;
@@ -1860,6 +1966,43 @@ declare module "dialog/dialog" {
show(): void;
}
}
declare module "unit/importexport/unitdatafile" {
import { Dialog } from "dialog/dialog";
export abstract class UnitDataFile {
#private;
protected data: any;
protected dialog: Dialog;
constructor();
buildCategoryCoalitionTable(): void;
getData(): any;
}
}
declare module "unit/importexport/unitdatafileexport" {
import { Dialog } from "dialog/dialog";
import { Unit } from "unit/unit";
import { UnitDataFile } from "unit/importexport/unitdatafile";
export class UnitDataFileExport extends UnitDataFile {
#private;
protected data: any;
protected dialog: Dialog;
constructor(elementId: string);
/**
* Show the form to start the export journey
*/
showForm(units: Unit[]): void;
}
}
declare module "unit/importexport/unitdatafileimport" {
import { Dialog } from "dialog/dialog";
import { UnitDataFile } from "unit/importexport/unitdatafile";
export class UnitDataFileImport extends UnitDataFile {
#private;
protected data: any;
protected dialog: Dialog;
constructor(elementId: string);
selectFile(): void;
}
}
declare module "unit/unitsmanager" {
import { LatLng, LatLngBounds } from "leaflet";
import { Unit } from "unit/unit";
@@ -1897,6 +2040,19 @@ declare module "unit/unitsmanager" {
* @param category Either "Aircraft", "Helicopter", "GroundUnit", or "NavyUnit". Determines what class will be used to create the new unit accordingly.
*/
addUnit(ID: number, category: string): void;
/** Sort units segregated groups based on controlling type and protection, if DCS-controlled
*
* @param units <Unit[]>
* @returns Object
*/
segregateUnits(units: Unit[]): {
[key: string]: [];
};
/**
*
* @param numOfProtectedUnits number
*/
showProtectedUnitsPopup(numOfProtectedUnits: number): void;
/** Update the data of all the units. The data is directly decoded from the binary buffer received from the REST Server. This is necessary for performance and bandwidth reasons.
*
* @param buffer The arraybuffer, encoded according to the ICD defined in: TODO Add reference to ICD
@@ -2102,7 +2258,7 @@ declare module "unit/unitsmanager" {
* @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): void;
/** 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): void;
@@ -2266,7 +2422,7 @@ declare module "server/servermanager" {
constructor();
toggleDemoEnabled(): void;
setCredentials(newUsername: string, newPassword: string): void;
GET(callback: CallableFunction, uri: string, options?: ServerRequestOptions, responseType?: string): void;
GET(callback: CallableFunction, uri: string, options?: ServerRequestOptions, responseType?: string, force?: boolean): void;
PUT(request: object, callback: CallableFunction): void;
getConfig(callback: CallableFunction): void;
setAddress(address: string, port: number): void;
@@ -2334,6 +2490,9 @@ declare module "server/servermanager" {
setPaused(newPaused: boolean): void;
getPaused(): boolean;
getServerIsPaused(): boolean;
getRequests(): {
[key: string]: XMLHttpRequest;
};
}
}
declare module "panels/unitlistpanel" {

View File

@@ -112,5 +112,5 @@ function onListening() {
debug('Listening on ' + bind);
}
console.log("DCS Olympus server v0.4.8 started correctly!")
console.log("DCS Olympus server v0.4.13-alpha-rc5 started correctly!")
console.log("Waiting for connections...")

View File

@@ -35,7 +35,7 @@ class DemoDataGenerator {
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, isActiveTanker: false, isActiveAWACS: false, onOff: true, followRoads: false, fuel: 50,
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,
@@ -53,9 +53,9 @@ class DemoDataGenerator {
}
/*
// 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,9 +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";
@@ -164,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));
}
}

View File

@@ -1,12 +1,12 @@
{
"name": "DCSOlympus",
"version": "v0.4.8-alpha",
"version": "v0.4.13-alpha-rc5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "DCSOlympus",
"version": "v0.4.8-alpha",
"version": "v0.4.13-alpha-rc5",
"dependencies": {
"@turf/turf": "^6.5.0",
"body-parser": "^1.20.2",
@@ -19,11 +19,12 @@
"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",
@@ -1714,12 +1715,12 @@
"dev": true
},
"node_modules/@babel/runtime": {
"version": "7.21.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz",
"integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==",
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz",
"integrity": "sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==",
"dev": true,
"dependencies": {
"regenerator-runtime": "^0.13.11"
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
@@ -2015,14 +2016,14 @@
"dev": true
},
"node_modules/@tanem/svg-injector": {
"version": "10.1.55",
"resolved": "https://registry.npmjs.org/@tanem/svg-injector/-/svg-injector-10.1.55.tgz",
"integrity": "sha512-xh8ejdvjDaH1eddZC0CdI45eeid4BIU2ppjNEhiTiWMYcLGT19KWjbES/ttDS4mq9gIAQfXx57g5zimEVohqYA==",
"version": "10.1.68",
"resolved": "https://registry.npmjs.org/@tanem/svg-injector/-/svg-injector-10.1.68.tgz",
"integrity": "sha512-UkJajeR44u73ujtr5GVSbIlELDWD/mzjqWe54YMK61ljKxFcJoPd9RBSaO7xj02ISCWUqJW99GjrS+sVF0UnrA==",
"dev": true,
"dependencies": {
"@babel/runtime": "^7.21.5",
"@babel/runtime": "^7.23.2",
"content-type": "^1.0.5",
"tslib": "^2.5.0"
"tslib": "^2.6.2"
}
},
"node_modules/@turf/along": {
@@ -7672,9 +7673,9 @@
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==",
"dev": true
},
"node_modules/regenerator-transform": {
@@ -8820,6 +8821,18 @@
"node": ">= 0.4.0"
}
},
"node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",

View File

@@ -2,11 +2,11 @@
"name": "DCSOlympus",
"node-main": "./bin/www",
"main": "http://localhost:3000",
"version": "v0.4.8-alpha",
"version": "v0.4.13-alpha-rc5",
"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-release": "browserify .\\src\\index.ts -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ] -p [ tinyify ] && 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",
@@ -25,11 +25,12 @@
"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",

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"
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
"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"
"build-release": "browserify ./src/index.ts -p [ tsify --noImplicitAny] > index.js && copy.bat"
},
"dependencies": {},
"devDependencies": {

View File

@@ -125,13 +125,6 @@ export class ControlTipsPlugin implements OlympusPlugin {
"showIfHoveringOverAirbase": false,
"showIfHoveringOverUnit": false
},
{
"key": `Mouse2`,
"action": `Quick options`,
"showIfUnitSelected": false,
"showIfHoveringOverAirbase": false,
"showIfHoveringOverUnit": true
},
{
"key": `Mouse2`,
"action": `Airbase menu`,

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@
"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": {

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": "Frigate",
"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": "Frigate",
"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;
}

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,8 +252,8 @@
}
.unit-label-count-container {
display: flex;
flex-direction: row;
display: grid;
grid-template-columns: 187px 1fr 1fr;
align-items: center;
column-gap: 5px;
}
@@ -400,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 {
@@ -580,6 +631,7 @@
#iads-menu {
row-gap: 10px;
padding: 10px;
}
#coalition-area-contextmenu>div:nth-child(2) {
@@ -596,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

@@ -107,147 +107,4 @@
#coordinates-tool[data-location-system="MGRS"] [data-location-system="MGRS"],
#coordinates-tool[data-location-system="UTM"] [data-location-system="UTM"] {
display:flex;
}
/*
#mouse-info-panel dl {
margin-bottom: 4px;
row-gap: 5px;
}
#mouse-info-panel dt {
height: fit-content;
width: 30%;
}
#mouse-info-panel dt::after, #coordinates-tool [data-label] {
align-items: center;
background-color: white;
border-radius: var(--border-radius-sm);
color: var(--background-steel);
display: flex;
font-size: 15.6px;
font-weight: bolder;
height: 16px;
justify-content: center;
line-height: 16px;
padding: 4px;
text-transform: uppercase;
width: 16px;
}
#coordinates-tool [data-label] {
height:24px;
width:24px;
}
#mouse-info-panel #measuring-tool dt {
height: 24px;
width: 24px;
background-color: var(--background-offwhite);
border-radius: var(--border-radius-sm);
}
#mouse-info-panel #measuring-tool svg {
padding: 3px;
height: 100%;
width: 100%;
}
#mouse-info-panel #measuring-tool dt svg>* {
fill: black;
stroke: black;
}
#mouse-info-panel [data-label]::after {
content: attr(data-label);
}
#mouse-info-panel dt[data-coalition="blue"]::after {
background-color: var(--primary-blue);
}
#mouse-info-panel dt[data-coalition="red"]::after {
background-color: var(--primary-red);
}
#mouse-info-panel [data-tooltip]:hover::before {
background-color: var(--background-grey);
border-radius: 5px;
content: attr(data-tooltip);
display: flex;
flex-wrap: nowrap;
padding: 5px;
position: absolute;
translate: calc(-100% - 15px) 0;
white-space: nowrap;
}
#coordinates-tool[data-location-system] [data-location-system] {
display:none;
flex-direction: column;
}
#coordinates-tool[data-location-system="LatLng"] [data-location-system="LatLng"],
#coordinates-tool[data-location-system="MGRS"] [data-location-system="MGRS"] {
display:flex;
}
#coordinates-tool > * > * {
align-items: center;
display:flex;
flex-flow: row nowrap;
}
#coordinates-tool > * > * > * {
display:table-cell;
width:fit-content;
}
#coordinates-tool > * > * > :last-child {
text-align: right;
width:100%;
}
.br-info::after {
content: attr(data-bearing) '\00B0 / ' attr(data-distance) " " attr(data-distance-units);
font-weight: bold;
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--background-offwhite);
}
.br-info[data-coalition="blue"]::after {
color: var(--primary-blue)
}
.br-info[data-coalition="red"]::after {
color: var(--primary-red)
}
.br-info[data-message]::after {
content: attr(data-message);
}
.coordinates::after {
content: attr(data-value);
font-weight: bold;
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--background-offwhite);
}
.elevation::after {
content: attr(data-value);
font-weight: bold;
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--background-offwhite);
} */
}

View File

@@ -6,65 +6,71 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
#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;
#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;
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;
background-color: white;
border-color: white;
}
#unit-control-panel .ol-option-button button.selected svg * {
fill: var(--background-steel);
stroke: var(--background-steel);
fill: var(--background-steel);
stroke: var(--background-steel);
}
#rapid-controls {
display: flex;
flex-direction: column;
row-gap: 5px;
height: fit-content;
width: fit-content;
display: flex;
flex-direction: column;
row-gap: 5px;
height: fit-content;
width: fit-content;
}
#rapid-controls button {
padding: 4px;
padding: 4px;
}
#rapid-controls button.pulse {
animation: pulse 1.5s linear infinite;
animation: pulse 1.5s linear infinite;
}
#rapid-controls svg {
height: 20px;
width: 20px;
fill: white;
stroke: white;
height: 20px;
width: 20px;
fill: white;
stroke: white;
}
#rapid-controls button:before {
display: inline-block;
filter: invert(100%);
height: 20px;
width: 20px;
display: inline-block;
filter: invert(100%);
height: 20px;
width: 20px;
}
#unit-control-panel {
display: flex;
flex-direction: row;
@@ -112,7 +118,7 @@ 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;
}
@@ -199,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 {
@@ -269,7 +275,6 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
width: 40px;
}
.ol-slider-value {
color: var(--accent-light-blue);
cursor: pointer;
@@ -279,13 +284,15 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
.switch-control {
align-items: center;
align-content: center;
display: flex;
width: 100%;
justify-content: space-between;
}
.switch-control h4 {
margin: 0px;
margin: 0px !important;
padding: 0px;
display: flex;
align-items: center;
}
@@ -306,30 +313,30 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
position: relative;
}
#advanced-settings-div > button {
#advanced-settings-div>button {
background-color: var(--background-grey);
box-shadow: 0px 2px 5px #000A;
font-size:13px;
box-shadow: 0px 2px 5px #000A;
font-size: 13px;
height: 40px;
padding:0 20px;
padding: 0 20px;
}
#delete-options {
font-size:13px;
font-size: 13px;
}
#delete-options.ol-select > .ol-select-value:after {
#delete-options.ol-select>.ol-select-value:after {
content: "";
}
#delete-options.ol-select > .ol-select-value svg {
#delete-options.ol-select>.ol-select-value svg {
background-color: transparent;
position: absolute;
right:2px;
translate:0 1px;
right: 2px;
translate: 0 1px;
}
#delete-options.ol-select > .ol-select-value svg * {
#delete-options.ol-select>.ol-select-value svg * {
fill: var(--primary-red);
}
@@ -337,21 +344,21 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
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 {
#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: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 .ol-select-options>div:last-of-type {
margin-bottom: 12px;
padding-bottom: 0;
}
#delete-options button {
@@ -392,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

@@ -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 {
@@ -372,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;
@@ -384,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;
@@ -426,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 {
@@ -652,13 +676,13 @@ nav.ol-panel> :last-child {
}
.ol-navbar-buttons-group button.off svg * {
fill: white !important;
stroke: white !important;
fill: white !important; /* Higher price than the Soul Stone but inline styling is causing issues. */
stroke: white !important; /* I'm sorry, daughter. */
}
.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 {
@@ -777,6 +801,7 @@ nav.ol-panel> :last-child {
width: 70%;
max-width: 1200px;
z-index: 999999;
min-width: 500px;
}
@media (min-width: 1700px) {
@@ -827,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;
@@ -839,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 {
@@ -852,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%;
}
}
@@ -898,13 +936,7 @@ nav.ol-panel> :last-child {
#loading-screen div {
color: white;
font-size: 18px;
animation: blinker 3s linear infinite;
}
@keyframes blinker {
50% {
opacity: 0;
}
animation: pulse 3s linear infinite;
}
.fade-out {
@@ -962,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);
@@ -1290,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;
}
@@ -1328,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 {
@@ -1531,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;
@@ -1540,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)
}
@@ -1567,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;
}
@@ -1574,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,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

@@ -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)"
];
@@ -67,58 +67,74 @@ export const minAltitudeValues: { [key: string]: number } = { Aircraft: 0, Helic
export const maxAltitudeValues: { [key: string]: number } = { Aircraft: 50000, Helicopter: 10000 };
export const altitudeIncrements: { [key: string]: number } = { Aircraft: 500, Helicopter: 100 };
export const minimapBoundaries = [
[ // NTTR
export const minimapBoundaries = {
"Nevada": [ // NTTR
new LatLng(39.7982463, -119.985425),
new LatLng(34.4037128, -119.7806729),
new LatLng(34.3483316, -112.4529351),
new LatLng(39.7372411, -112.1130805),
new LatLng(39.7982463, -119.985425)
],
[ // Syria
"Syria": [ // Syria
new LatLng(37.3630556, 29.2686111),
new LatLng(31.8472222, 29.8975),
new LatLng(32.1358333, 42.1502778),
new LatLng(37.7177778, 42.3716667),
new LatLng(37.3630556, 29.2686111)
],
[ // Caucasus
"Caucasus": [ // Caucasus
new LatLng(39.6170191, 27.634935),
new LatLng(38.8735863, 47.1423108),
new LatLng(47.3907982, 49.3101946),
new LatLng(48.3955879, 26.7753625),
new LatLng(39.6170191, 27.634935)
],
[ // Persian Gulf
"PersianGulf": [ // Persian Gulf
new LatLng(32.9355285, 46.5623682),
new LatLng(21.729393, 47.572675),
new LatLng(21.8501348, 63.9734737),
new LatLng(33.131584, 64.7313594),
new LatLng(32.9355285, 46.5623682)
],
[ // Marianas
"MarianaIslands": [ // Marianas
new LatLng(22.09, 135.0572222),
new LatLng(10.5777778, 135.7477778),
new LatLng(10.7725, 149.3918333),
new LatLng(22.5127778, 149.5427778),
new LatLng(22.09, 135.0572222)
],
[ // South Atlantic
"Falklands": [ // 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": [ // 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)
],
"SinaiMap": [ // 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)
]
];
};
export const mapBounds = {
"Syria": { bounds: new LatLngBounds([31.8472222, 29.8975], [37.7177778, 42.3716667]), zoom: 5 },
"MarianaIslands": { bounds: new LatLngBounds([10.5777778, 135.7477778], [22.5127778, 149.5427778]), zoom: 5 },
"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 },
"PersianGulf": { bounds: new LatLngBounds([21.729393, 47.572675], [33.131584, 64.7313594]), zoom: 4 },
"Caucasus": { bounds: new LatLngBounds([39.6170191, 27.634935], [47.3907982, 49.3101946]), zoom: 4 },
"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 = {
@@ -126,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}',
@@ -167,11 +183,19 @@ export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area";
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: 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,
@@ -211,9 +235,9 @@ export const MAP_MARKER_CONTROLS: MapMarkerVisibilityControl[] = [{
"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)";
@@ -243,6 +267,7 @@ export enum DataIndexes {
horizontalVelocity,
verticalVelocity,
heading,
track,
isActiveTanker,
isActiveAWACS,
onOff,

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

@@ -26,7 +26,7 @@ export class Dropdown {
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))) {

View File

@@ -3,7 +3,7 @@ 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";
@@ -12,8 +12,14 @@ import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
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, (name: string) => this.#setUnitName(name), 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,6 +187,7 @@ 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)
blueprints = this.#unitDatabase.getByRole(this.spawnOptions.roleType);
@@ -175,7 +210,7 @@ export class UnitSpawnMenu {
let name = this.#unitLabelDropdown.getOptionsList()[idx];
let element = this.#unitLabelDropdown.getOptionElements()[idx] as HTMLElement;
let entry = this.#unitDatabase.getByName(name);
if (entry) {
if (entry && entry.tags?.trim() !== "") {
element.querySelectorAll("button")[0]?.append(...(entry.tags?.split(",").map((tag: string) => {
tag = tag.trim();
let el = document.createElement("div");
@@ -188,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;
@@ -198,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();
@@ -235,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") : "";
@@ -255,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();
})
@@ -267,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;
}
@@ -283,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();
@@ -292,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) }));
@@ -315,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();
@@ -425,6 +504,14 @@ 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"));
@@ -482,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");
@@ -614,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))};
/**
*
@@ -625,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");
}
@@ -656,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)};
/**
*
@@ -677,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) {

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,7 +7,7 @@ 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";
@@ -64,6 +64,7 @@ export class Map extends L.Map {
#centerUnit: Unit | null = null;
#miniMap: ClickableMiniMap | null = null;
#miniMapLayerGroup: L.LayerGroup;
#miniMapPolyline: L.Polyline;
#temporaryMarkers: TemporaryUnitMarker[] = [];
#selecting: boolean = false;
#isZooming: boolean = false;
@@ -123,8 +124,8 @@ export class Map extends L.Map {
/* Minimap */
var minimapLayer = new L.TileLayer(mapLayers[Object.keys(mapLayers)[0] as keyof typeof mapLayers].urlTemplate, { minZoom: 0, maxZoom: 13 });
this.#miniMapLayerGroup = new L.LayerGroup([minimapLayer]);
var miniMapPolyline = new L.Polyline(this.#getMinimapBoundaries(), { color: '#202831' });
miniMapPolyline.addTo(this.#miniMapLayerGroup);
this.#miniMapPolyline = new L.Polyline([], { color: '#202831' });
this.#miniMapPolyline.addTo(this.#miniMapLayerGroup);
/* Scale */
//@ts-ignore TODO more hacking because the module is provided as a pure javascript module only
@@ -418,6 +419,9 @@ export class Map extends L.Map {
if (this.#miniMap)
this.setView(e.latlng);
})
const boundaries = this.#getMinimapBoundaries();
this.#miniMapPolyline.setLatLngs(boundaries[theatre as keyof typeof boundaries]);
}
getMiniMapLayerGroup() {
@@ -559,7 +563,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
@@ -662,7 +666,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);
@@ -791,7 +795,7 @@ export class Map extends L.Map {
#showDestinationCursors() {
const singleCursor = !this.#shiftKey;
const selectedUnitsCount = getApp().getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length;
const selectedUnitsCount = getApp().getUnitsManager().getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }).length;
if (singleCursor) {
this.#hideDestinationCursors();
}
@@ -817,7 +821,7 @@ export class Map extends L.Map {
}
#updateDestinationCursors() {
const selectedUnitsCount = getApp().getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length;
const selectedUnitsCount = getApp().getUnitsManager().getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }).length;
if (selectedUnitsCount > 1) {
const groupLatLng = this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : this.getMouseCoordinates();
if (this.#destinationPreviewCursors.length == 1)

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

@@ -29,9 +29,13 @@ import { UnitListPanel } from "./panels/unitlistpanel";
import { ContextManager } from "./context/contextmanager";
import { Context } from "./context/context";
var VERSION = "v0.4.13-alpha-rc5";
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;
@@ -50,7 +54,6 @@ export class OlympusApp {
#weaponsManager: WeaponsManager | null = null;
constructor() {
}
// TODO add checks on null
@@ -178,7 +181,6 @@ export class OlympusApp {
start() {
/* Initialize base functionalitites */
this.#contextManager = new ContextManager();
this.#contextManager.add( "olympus", {} );
@@ -245,8 +247,24 @@ export class OlympusApp {
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";
latestVersionSpan.classList.toggle("new-version", this.#latestVersion !== VERSION);
}
})
}
#setupEvents() {
@@ -279,7 +297,7 @@ export class OlympusApp {
shortcutManager.addKeyboardShortcut("toggleDemo", {
"altKey": false,
"callback": () => {
this.getServerManager().toggleDemoEnabled();
if (DEBUG === true) this.getServerManager().toggleDemoEnabled();
},
"code": "KeyT",
"context": "olympus",
@@ -411,6 +429,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 */
@@ -435,7 +454,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

@@ -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

@@ -9,6 +9,7 @@ import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, em
import { ftToM, knotsToMs, mToFt, msToKnots } from "../other/utils";
import { GeneralSettings, Radio, TACAN } from "../interfaces";
import { ContextActionSet } from "../unit/contextactionset";
import { Popup } from "../popups/popup";
export class UnitControlPanel extends Panel {
#altitudeSlider: Slider;
@@ -97,6 +98,8 @@ export class UnitControlPanel extends Panel {
/* Follow roads switch */
this.#followRoadsSwitch = new Switch("follow-roads-switch", (value: boolean) => {
getApp().getUnitsManager().setFollowRoads(value);
if (value)
(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Warning: follow roads movements can cause lag");
});
/* Operate as */

View File

@@ -12,15 +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();
@@ -40,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)
@@ -83,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);
}
@@ -105,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);
@@ -130,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) {
@@ -138,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
@@ -207,172 +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);
}
// TODO: Remove coalition
scenicAAA(ID: number, coalition: string, callback: CallableFunction = () => {}) {
scenicAAA(ID: number, coalition: string, callback: CallableFunction = () => { }) {
var command = { "ID": ID, "coalition": coalition }
var data = { "scenicAAA": command }
this.PUT(data, callback);
}
// TODO: Remove coalition
missOnPurpose(ID: number, coalition: string, callback: CallableFunction = () => {}) {
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,
@@ -386,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,
@@ -399,7 +413,7 @@ export class ServerManager {
this.PUT(data, callback);
}
reloadDatabases(callback: CallableFunction = () => {}) {
reloadDatabases(callback: CallableFunction = () => { }) {
var data = { "reloadDatabases": {} };
this.PUT(data, callback);
}
@@ -430,7 +444,7 @@ export class ServerManager {
}, 10000));
this.#intervals.push(window.setInterval(() => {
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE){
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
this.getBullseye((data: BullseyesData) => {
this.checkSessionHash(data.sessionHash);
getApp().getMissionManager()?.updateBullseyes(data);
@@ -452,7 +466,7 @@ export class ServerManager {
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);
}
@@ -461,7 +475,7 @@ export class ServerManager {
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);
}
@@ -470,18 +484,18 @@ export class ServerManager {
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();
@@ -491,29 +505,29 @@ export class ServerManager {
}
}
}, ( this.getServerIsPaused() ? 500 : 5000 )));
}, (this.getServerIsPaused() ? 500 : 5000)));
// Mission clock and elapsed time
this.#intervals.push(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));
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);
}
@@ -540,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);
}
@@ -587,4 +601,8 @@ export class ServerManager {
getServerIsPaused() {
return this.#serverIsPaused;
}
getRequests() {
return this.#requests;
}
}

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

@@ -5,7 +5,7 @@ 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, MAX_SHOTS_SCATTER, SHOTS_SCATTER_DEGREES } 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';
@@ -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 };
@@ -247,6 +249,14 @@ export abstract class Unit extends CustomMarker {
*/
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
*
@@ -278,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;
@@ -338,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,
@@ -355,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,
@@ -552,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 */
@@ -678,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 */
@@ -1167,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);`);
});
@@ -1592,10 +1615,10 @@ export class GroundUnit extends Unit {
}
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.\nWARNING: works correctly only on neutral units, blue or red units will aim", (units: Unit[]) => { getApp().getUnitsManager().scenicAAA(units) }, undefined, {
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", "Miss on purpose", "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, {
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
});
}

View File

@@ -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 = [];
@@ -96,6 +100,50 @@ export class UnitsManager {
}
}
/** Sort units segregated groups based on controlling type and protection, if DCS-controlled
*
* @param units <Unit[]>
* @returns Object
*/
segregateUnits(units: Unit[]): { [key: string]: [] } {
const data: any = {
controllable: [],
dcsProtected: [],
dcsUnprotected: [],
human: [],
olympus: []
};
const map = getApp().getMap();
units.forEach(unit => {
if (unit.getHuman())
data.human.push(unit);
else if (unit.isControlledByOlympus())
data.olympus.push(unit);
else if (map.getIsUnitProtected(unit))
data.dcsProtected.push(unit);
else
data.dcsUnprotected.push(unit);
});
data.controllable = [].concat(data.dcsUnprotected, data.human, data.olympus);
return data;
}
/**
*
* @param numOfProtectedUnits number
*/
showProtectedUnitsPopup(numOfProtectedUnits: number) {
if (numOfProtectedUnits < 1)
return;
const messageText = (numOfProtectedUnits === 1) ? `Unit is protected` : `All selected units are protected`;
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(messageText);
// Cheap way for now until we use more locks
let lock = <HTMLElement>document.querySelector("#unit-visibility-control button.lock");
lock.classList.add("prompt");
setTimeout(() => lock.classList.remove("prompt"), 4000);
}
/** Update the data of all the units. The data is directly decoded from the binary buffer received from the REST Server. This is necessary for performance and bandwidth reasons.
*
* @param buffer The arraybuffer, encoded according to the ICD defined in: TODO Add reference to ICD
@@ -257,14 +305,8 @@ export class UnitsManager {
}
}
if (options) {
if (options.showProtectionReminder === true && numProtectedUnits > selectedUnits.length && selectedUnits.length === 0) {
const messageText = (numProtectedUnits === 1) ? `Unit is protected` : `All selected units are protected`;
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(messageText);
// Cheap way for now until we use more locks
let lock = <HTMLElement>document.querySelector("#unit-visibility-control button.lock");
lock.classList.add("prompt");
setTimeout(() => lock.classList.remove("prompt"), 4000);
}
if (options.showProtectionReminder === true && numProtectedUnits > selectedUnits.length && selectedUnits.length === 0)
this.showProtectedUnitsPopup(numProtectedUnits);
if (options.onlyOnePerGroup) {
var temp: Unit[] = [];
@@ -361,8 +403,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
/* Compute the destination for each unit. If mantainRelativePosition is true, compute the destination so to hold the relative positions */
var unitDestinations: { [key: number]: LatLng } = {};
@@ -395,8 +442,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: false });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
for (let idx in units) {
const unit = units[idx];
@@ -421,8 +473,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.landAt(latlng));
@@ -438,8 +495,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.changeSpeed(speedChange));
}
@@ -453,8 +515,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.changeAltitude(altitudeChange));
}
@@ -468,8 +535,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.setSpeed(speed));
this.#showActionMessage(units, `setting speed to ${msToKnots(speed)} kts`);
@@ -484,8 +556,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.setSpeedType(speedType));
this.#showActionMessage(units, `setting speed type to ${speedType}`);
@@ -500,8 +577,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.setAltitude(altitude));
this.#showActionMessage(units, `setting altitude to ${mToFt(altitude)} ft`);
@@ -516,8 +598,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.setAltitudeType(altitudeType));
this.#showActionMessage(units, `setting altitude type to ${altitudeType}`);
@@ -532,8 +619,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.setROE(ROE));
this.#showActionMessage(units, `ROE set to ${ROE}`);
@@ -548,8 +640,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.setReactionToThreat(reactionToThreat));
this.#showActionMessage(units, `reaction to threat set to ${reactionToThreat}`);
@@ -564,8 +661,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.setEmissionsCountermeasures(emissionCountermeasure));
this.#showActionMessage(units, `emissions & countermeasures set to ${emissionCountermeasure}`);
@@ -580,8 +682,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.setOnOff(onOff));
this.#showActionMessage(units, `unit active set to ${onOff}`);
@@ -596,8 +703,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.setFollowRoads(followRoads));
this.#showActionMessage(units, `follow roads set to ${followRoads}`);
@@ -613,8 +725,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.setOperateAs(operateAs));
this.#showActionMessage(units, `operate as set to ${operateAs}`);
@@ -629,8 +746,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.attackUnit(ID));
this.#showActionMessage(units, `attacking unit ${this.getUnitByID(ID)?.getUnitName()}`);
@@ -643,11 +765,14 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units.forEach((unit: Unit) => unit.refuel());
this.#showActionMessage(units, `sent to nearest tanker`);
segregatedUnits.controllable.forEach((unit: Unit) => unit.refuel());
this.#showActionMessage(segregatedUnits.controllable, `sent to nearest tanker`);
}
/** Instruct the selected units to follow another unit in a formation. Only works for aircrafts and helicopters.
@@ -661,8 +786,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
if (offset == undefined) {
/* Simple formations with fixed offsets */
@@ -679,8 +809,7 @@ export class UnitsManager {
var count = 1;
var xr = 0; var yr = 1; var zr = -1;
var layer = 1;
for (let idx in units) {
var unit = units[idx];
units.forEach((unit: Unit) => {
if (unit.ID !== ID) {
if (offset != undefined)
/* Offset is set, apply it */
@@ -701,7 +830,7 @@ export class UnitsManager {
}
count++;
}
}
})
this.#showActionMessage(units, `following unit ${this.getUnitByID(ID)?.getUnitName()}`);
}
@@ -714,8 +843,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.bombPoint(latlng));
this.#showActionMessage(units, `unit bombing point`);
@@ -730,8 +864,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.carpetBomb(latlng));
this.#showActionMessage(units, `unit carpet bombing point`);
@@ -746,8 +885,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.fireAtArea(latlng));
this.#showActionMessage(units, `unit firing at area`);
@@ -762,8 +906,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
getGroundElevation(latlng, (response: string) => {
var groundElevation: number | null = null;
@@ -784,22 +933,32 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.scenicAAA());
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)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.missOnPurpose());
this.#showActionMessage(units, `unit set to perform miss-on-purpose AAA`);
@@ -814,9 +973,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.landAtPoint(latlng));
this.#showActionMessage(units, `unit landing at point`);
@@ -831,8 +994,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.setShotsScatter(shotsScatter));
this.#showActionMessage(units, `shots scatter set to ${shotsScatter}`);
@@ -847,8 +1015,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
units.forEach((unit: Unit) => unit.setShotsIntensity(shotsIntensity));
this.#showActionMessage(units, `shots intensity set to ${shotsIntensity}`);
@@ -879,8 +1052,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: false, showProtectionReminder: true });
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
if (this.getUnitsCategories(units).length == 1) {
var unitsData: { ID: number, location: LatLng }[] = [];
@@ -925,8 +1103,13 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeProtected: true, showProtectionReminder: true }); /* Can be applied to humans too */
if (units.length === 0)
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return;
}
units = segregatedUnits.controllable;
const selectionContainsAHuman = units.some((unit: Unit) => {
return unit.getHuman() === true;
@@ -964,6 +1147,14 @@ export class UnitsManager {
if (units === null)
units = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true });
const segregatedUnits = this.segregateUnits(units);
if (segregatedUnits.controllable.length === 0) {
this.showProtectedUnitsPopup(segregatedUnits.dcsProtected.length);
return {};
}
units = segregatedUnits.controllable;
if (units.length === 0)
return {};
@@ -1001,7 +1192,7 @@ export class UnitsManager {
*
*/
copy(units: Unit[] | null = null) {
if ( !getApp().getContextManager().getCurrentContext().getAllowUnitCopying() )
if (!getApp().getContextManager().getCurrentContext().getAllowUnitCopying())
return;
if (units === null)
@@ -1021,7 +1212,7 @@ export class UnitsManager {
* @returns True if units were pasted successfully
*/
paste() {
if ( !getApp().getContextManager().getCurrentContext().getAllowUnitPasting() )
if (!getApp().getContextManager().getCurrentContext().getAllowUnitPasting())
return;
let spawnPoints = 0;
@@ -1104,11 +1295,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;
@@ -1122,7 +1340,7 @@ export class UnitsManager {
/* 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);
this.spawnUnits("GroundUnit", [{ unitType: unitBlueprint.name, location: latlng, liveryID: "" }], coalitionArea.getCoalition(), false, "", "");
}
}
}
@@ -1134,52 +1352,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

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

@@ -3,57 +3,56 @@
<div class="upper-bar ol-panel">
<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">
<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" />

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.8-alpha</span></div>
</div>
<form id="authentication-form">
<div><h5>Name</h5> <input type="text" id="username" name="username" required autocomplete="username" placeholder="Enter name..."></div>
<div><h5>Server password</h5> <input type="password" id="password" name="password" minlength="1" required autocomplete="current-password" placeholder="Enter server 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>

View File

@@ -0,0 +1,21 @@
<div id="slow-delete-dialog" class="ol-panel ol-dialog hide" oncontextmenu="return false;">
<div class="ol-dialog-header">
<h3>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>

View File

@@ -0,0 +1,51 @@
<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.13-alpha-rc5</span></div>
<div class="app-version">Latest version <span id="latest-version" class="app-version-number"></span></div>
</div>
<form id="authentication-form">
<div><h5>Name</h5> <input type="text" id="username" name="username" required autocomplete="username" placeholder="Enter name..."></div>
<div><h5>Server password</h5> <input type="password" id="password" name="password" required autocomplete="current-password" placeholder="Enter server 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" class="ol-scrollable">
<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>

View File

@@ -100,7 +100,7 @@
</div>
<div id="follow-roads" class="switch-control yes-no">
<h4>Follow roads <img src="/resources/theme/images/icons/circle-question-regular.svg" title=""></h4>
<h4>Follow roads <img src="/resources/theme/images/icons/circle-question-regular.svg" title="WARNING: follow roads movements can cause lag"></h4>
<div id="follow-roads-switch" class="ol-switch"></div>
</div>
</div>
@@ -112,13 +112,12 @@
<div id="delete-options" class="ol-select">
<div class="ol-select-value ol-select-warning">
Delete unit
<img src="/resources/theme/images/icons/chevron-down.svg" inject-svg />
</div>
<div class="ol-select-options">
<div><button class="ol-button-white" data-on-click="deleteSelectedUnits" title="Immediately remove the unit from the simulation"><img src="/resources/theme/images/icons/trash-can-regular.svg" inject-svg>Delete</button></div>
<div class="hr"><hr></div>
<div><button class="ol-button-warning" data-on-click="explodeSelectedUnits" data-on-click-params='{ "type": "normal" }' title="Normal explosion"><img src="/resources/theme/images/icons/explosion-solid.svg" inject-svg>Blow up</button></div>
<div><button class="ol-button-warning" data-on-click="explodeSelectedUnits" data-on-click-params='{ "type": "secondary" }' title="The unit will keep exploding at random intervals, simulating ammunition cooking"><img src="/resources/theme/images/icons/burst-solid.svg" inject-svg>Cook off</button></div>
<div><button class="ol-button-warning" data-on-click="explodeSelectedUnits" data-on-click-params='{ "type": "secondary" }' title="Small explosion with debries"><img src="/resources/theme/images/icons/burst-solid.svg" inject-svg>Cook off</button></div>
<div><button class="ol-button-warning" data-on-click="explodeSelectedUnits" data-on-click-params='{ "type": "phosphorous" }' title="White phosphorous explosion"><img src="/resources/theme/images/icons/smog-solid.svg" inject-svg>Phosp.</button></div>
<div><button class="ol-button-warning" data-on-click="explodeSelectedUnits" data-on-click-params='{ "type": "napalm" }' title="Napalm"><img src="/resources/theme/images/icons/fire-solid.svg" inject-svg>Napalm</button></div>
</div>

View File

@@ -6,7 +6,7 @@
<div class="ol-select-options">
<div id="toolbar-summary">
<h3>DCS Olympus</h3>
<div class="accent-green app-version-number">version v0.4.8-alpha</div>
<div class="accent-green app-version-number">version v0.4.13-alpha-rc5</div>
</div>
<div>
<a href="https://discord.gg/wWXyVVBZT7" target="_blank">Discord</a>
@@ -14,6 +14,9 @@
<div>
<a href="https://github.com/Pax1601/DCSOlympus" target="_blank">Github</a>
</div>
<div>
<a href="https://github.com/Pax1601/DCSOlympus/wiki/User-Guide" target="_blank">User guide</a>
</div>
<div data-on-click="exportToFile">
<button>Export to file</button>
</div>
@@ -28,14 +31,14 @@
<div class="ol-group">
<div id="map-type" class="ol-select">
<div class="ol-select-value"><img src="resources/theme/images/icons/map-source.svg" inject-svg><span class="ol-select-value-text">ArcGIS Satellite</span></div>
<div class="ol-select-value"><img src="resources/theme/images/icons/map-source.svg" inject-svg /><span class="ol-select-value-text">ArcGIS Satellite</span></div>
<div class="ol-select-options">
<!-- Here the available map sources will be listed-->
</div>
</div>
<div id="map-visibility-options" class="ol-select">
<div class="ol-select-value"><img src="/resources/theme/images/icons/gears-solid.svg" inject-svg>Options</div>
<div class="ol-select-value"><img src="/resources/theme/images/icons/gears-solid.svg" inject-svg />Options</div>
<div class="ol-select-options">
<!-- This is where the advanced visibility options will be listed -->
</div>
@@ -43,25 +46,28 @@
</div>
</nav>
<nav class="ol-panel" oncontextmenu="return false;">
<img src="resources/theme/images/icons/eye-solid.svg" inject-svg>
<div id="view-label" class="ol-panel-tab">
<img src="resources/theme/images/icons/eye-solid.svg" inject-svg />
<span>View</span>
</div>
<div id="unit-visibility-control" class="ol-group ol-navbar-buttons-group">
<!-- Here the available visibility controls will be listed -->
</div>
<div id="coalition-visibility-control" class="ol-group ol-group-button-toggle">
<div id="coalition-visibility-control" class="ol-group ol-navbar-buttons-group">
<div>
<button id="coalition-visibility-control-blue" data-on-click="toggleCoalitionVisibility"
data-on-click-params='{ "coalition": "blue" }'><span class="accent-bluefor">BLUEFOR</span></button>
data-on-click-params='{ "coalition": "blue" }' title="Toggle Blue coalition visibility"><img src="/resources/theme/images/buttons/visibility/shield.svg" class="fill-coalition" data-coalition="blue" inject-svg /></button>
</div>
<div>
<button id="coalition-visibility-control-red" data-on-click="toggleCoalitionVisibility"
data-on-click-params='{ "coalition": "red" }'><span class="accent-redfor">REDFOR</span></button>
data-on-click-params='{ "coalition": "red" }' title="Toggle Red coalition visibility"><img src="/resources/theme/images/buttons/visibility/shield.svg" class="fill-coalition" data-coalition="red" inject-svg /></button>
</div>
<div>
<button id="coalition-visibility-control-neutral" data-on-click="toggleCoalitionVisibility"
data-on-click-params='{ "coalition": "neutral" }'><span class="accent-neutral">NEUTRAL</span></button>
data-on-click-params='{ "coalition": "neutral" }' title="Toggle Neutral coalition visibility"><img src="/resources/theme/images/buttons/visibility/shield.svg" class="fill-coalition" data-coalition="neutral" inject-svg /></button>
</div>
</div>
</nav>

View File

@@ -1,6 +1,6 @@
#define nwjsFolder "..\..\nwjs\"
#define nodejsFolder "..\..\node\"
#define version "v0.4.8-alpha"
#define version "v0.4.13-alpha-rc5"
[Setup]
AppName=DCS Olympus
@@ -44,62 +44,40 @@ Source: "..\img\olympus.ico"; DestDir: "{app}\Mods\Services\Olympus\img"; Flags:
Source: "..\img\olympus_server.ico"; DestDir: "{app}\Mods\Services\Olympus\img"; Flags: ignoreversion;
Source: "..\img\olympus_configurator.ico"; DestDir: "{app}\Mods\Services\Olympus\img"; Flags: ignoreversion;
Source: "..\img\configurator_logo.png"; DestDir: "{app}\Mods\Services\Olympus\img"; Flags: ignoreversion;
Source: "{#nwjsFolder}\*.*"; DestDir: "{app}\Mods\Services\Olympus\client\bin\nw"; Flags: ignoreversion recursesubdirs; Check: CheckLocalInstall
Source: "{#nodejsFolder}\*.*"; DestDir: "{app}\Mods\Services\Olympus\client\bin\node"; Flags: ignoreversion recursesubdirs; Check: CheckServerInstall
Source: "..\scripts\python\configurator\dist\configurator.exe"; DestDir: "{app}\Mods\Services\Olympus"; Flags: ignoreversion; Check: CheckServerInstall
Source: "{#nwjsFolder}\*.*"; DestDir: "{app}\Mods\Services\Olympus\client"; Flags: ignoreversion recursesubdirs; Check: CheckLocalInstall
Source: "{#nodejsFolder}\*.*"; DestDir: "{app}\Mods\Services\Olympus\client"; Flags: ignoreversion recursesubdirs; Check: CheckServerInstall
Source: "..\scripts\python\configurator\dist\configurator.exe"; DestDir: "{app}\Mods\Services\Olympus"; Flags: ignoreversion;
Source: "..\LEGAL"; DestDir: "{app}\Mods\Services\Olympus"; Flags: ignoreversion;
[Run]
Filename: "{app}\Mods\Services\Olympus\configurator.exe"; Parameters: -a {code:GetAddress} -c {code:GetClientPort} -b {code:GetBackendPort} -p {code:GetPassword} -bp {code:GetBluePassword} -rp {code:GetRedPassword}
[Registry]
Root: HKCU; Subkey: "Environment"; ValueType: string; ValueName: "DCSOLYMPUS_PATH"; ValueData: "{app}\Mods\Services\Olympus"; Flags: preservestringtype
Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};%DCSOLYMPUS_PATH%\bin"; Check: NeedsAddPath('%DCSOLYMPUS_PATH%\bin');
[Setup]
; Tell Windows Explorer to reload the environment
ChangesEnvironment=yes
Filename: "{app}\Mods\Services\Olympus\configurator.exe"; Parameters: -a {code:GetAddress} -c {code:GetClientPort} -b {code:GetBackendPort} -p {code:GetPassword} -bp {code:GetBluePassword} -rp {code:GetRedPassword}; Check: CheckCallConfigurator
[Icons]
Name: "{userdesktop}\DCS Olympus Client"; Filename: "{app}\Mods\Services\Olympus\client\bin\nw\nw.exe"; Tasks: desktopicon; IconFilename: "{app}\Mods\Services\Olympus\img\olympus.ico"; Check: CheckLocalInstall
Name: "{userdesktop}\DCS Olympus Server"; Filename: "{app}\Mods\Services\Olympus\client\bin\node\node.exe"; Tasks: desktopicon; IconFilename: "{app}\Mods\Services\Olympus\img\olympus_server.ico"; WorkingDir: "{app}\Mods\Services\Olympus\client"; Parameters: ".\bin\www"; Check: CheckServerInstall
Name: "{userdesktop}\DCS Olympus Client"; Filename: "{app}\Mods\Services\Olympus\client\nw.exe"; Tasks: desktopicon; IconFilename: "{app}\Mods\Services\Olympus\img\olympus.ico"; Check: CheckLocalInstall
Name: "{userdesktop}\DCS Olympus Server"; Filename: "{app}\Mods\Services\Olympus\client\node.exe"; Tasks: desktopicon; IconFilename: "{app}\Mods\Services\Olympus\img\olympus_server.ico"; Parameters: ".\bin\www"; Check: CheckServerInstall
Name: "{userdesktop}\DCS Olympus Configurator"; Filename: "{app}\Mods\Services\Olympus\configurator.exe"; Tasks: desktopicon; IconFilename: "{app}\Mods\Services\Olympus\img\olympus_configurator.ico"; Check: CheckServerInstall
[Code]
function NeedsAddPath(Param: string): boolean;
var
OrigPath: string;
begin
if not RegQueryStringValue(HKCU,
'Environment',
'Path', OrigPath)
then begin
Result := True;
exit;
end;
{ look for the path with leading and trailing semicolon }
{ Pos() returns 0 if not found }
Result := Pos(';' + Param + ';', ';' + OrigPath + ';') = 0;
end;
[Code]
var
lblLocalInstall: TLabel;
lblLocalInstallInstructions: TNewStaticText;
lblServerInstall: TLabel;
lblServerInstallInstructions: TNewStaticText;
lblKeepOld: TLabel;
lblClientPort: TLabel;
lblBackendPort: TLabel;
lblPassword: TLabel;
lblBluePassword: TLabel;
lblRedPassword: TLabel;
txtLocalInstall: TNewRadioButton;
txtServerInstall: TNewRadioButton;
radioLocalInstall: TNewRadioButton;
radioServerInstall: TNewRadioButton;
checkKeepOld: TNewCheckBox;
txtClientPort: TEdit;
txtBackendPort: TEdit;
txtPassword: TPasswordEdit;
txtBluePassword: TPasswordEdit;
txtRedPassword: TPasswordEdit;
AddressPage: Integer;
InstallationTypePage: Integer;
PasswordPage: Integer;
lblPasswordInstructions: TNewStaticText;
@@ -136,14 +114,14 @@ procedure frmAddress_CancelButtonClick(Page: TWizardPage; var Cancel, Confirm: B
begin
end;
function frmAddress_CreatePage(PreviousPageId: Integer): Integer;
function frmInstallationType_CreatePage(PreviousPageId: Integer): Integer;
var
Page: TWizardPage;
begin
Page := CreateCustomPage(
PreviousPageId,
'DCS Olympus configuration',
'Setup DCS Olympus connectivity'
'Select installation type'
);
{ lblLocalInstall }
@@ -172,9 +150,9 @@ begin
Caption := 'Select this to install DCS Olympus locally. DCS Olympus will not be reachable by external clients (i.e. browsers running on different PCs)';
end;
{ txtLocalInstall }
txtLocalInstall := TNewRadioButton.Create(Page);
with txtLocalInstall do
{ radioLocalInstall }
radioLocalInstall := TNewRadioButton.Create(Page);
with radioLocalInstall do
begin
Parent := Page.Surface;
Left := ScaleX(10);
@@ -211,9 +189,9 @@ begin
Caption := 'Select this to install DCS Olympus on a dedicated server. DCS Olympus will be reachable by external clients. NOTE: to enable external connections, TCP port forwarding must be enabled on the selected ports.';
end;
{ txtServerInstall }
txtServerInstall := TNewRadioButton.Create(Page);
with txtServerInstall do
{ radioServerInstall }
radioServerInstall := TNewRadioButton.Create(Page);
with radioServerInstall do
begin
Parent := Page.Surface;
Left := ScaleX(10);
@@ -223,58 +201,6 @@ begin
TabOrder := 1;
end;
{ lblClientPort }
lblClientPort := TLabel.Create(Page);
with lblClientPort do
begin
Parent := Page.Surface;
Left := ScaleX(24);
Top := ScaleY(168);
Width := ScaleX(46);
Height := ScaleY(13);
Caption := 'Webserver port';
end;
{ txtClientPort }
txtClientPort := TEdit.Create(Page);
with txtClientPort do
begin
Parent := Page.Surface;
Left := ScaleX(180);
Top := ScaleY(165);
Width := ScaleX(185);
Height := ScaleY(21);
Text := '3000';
OnKeyPress := @AcceptNumbersOnlyKeyPress;
TabOrder := 3;
end;
{ lblBackendPort }
lblBackendPort := TLabel.Create(Page);
with lblBackendPort do
begin
Parent := Page.Surface;
Left := ScaleX(24);
Top := ScaleY(198);
Width := ScaleX(46);
Height := ScaleY(13);
Caption := 'Backend port';
end;
{ txtBackendPort }
txtBackendPort := TEdit.Create(Page);
with txtBackendPort do
begin
Parent := Page.Surface;
Left := ScaleX(180);
Top := ScaleY(195);
Width := ScaleX(185);
Height := ScaleY(21);
Text := '3001';
OnKeyPress := @AcceptNumbersOnlyKeyPress;
TabOrder := 4;
end;
with Page do
begin
OnActivate := @frmAddress_Activate;
@@ -289,6 +215,8 @@ end;
procedure frmPassword_Activate(Page: TWizardPage);
begin
checkKeepOld.Enabled := FileExists(ExpandConstant('{app}\Mods\Services\Olympus\olympus.json'));
checkKeepOld.Checked := FileExists(ExpandConstant('{app}\Mods\Services\Olympus\olympus.json'));
end;
function frmPassword_ShouldSkipPage(Page: TWizardPage): Boolean;
@@ -303,11 +231,11 @@ end;
function frmPassword_NextButtonClick(Page: TWizardPage): Boolean;
begin
if (Trim(txtPassword.Text) <> '') and (Trim(txtBluePassword.Text) <> '') and (Trim(txtRedPassword.Text) <> '') then begin
if checkKeepOld.Checked or ((Trim(txtClientPort.Text) <> '') and (Trim(txtBackendPort.Text) <> '') and (Trim(txtPassword.Text) <> '') and (Trim(txtBluePassword.Text) <> '') and (Trim(txtRedPassword.Text) <> '')) then begin
Result := True;
end else
begin
MsgBox('All password fields must be filled to proceed.', mbInformation, MB_OK);
MsgBox('Either keep the configuration from the previous installation (if present) or fill all the fields to continue.', mbInformation, MB_OK);
Result := False;
end;
end;
@@ -316,6 +244,15 @@ procedure frmPassword_CancelButtonClick(Page: TWizardPage; var Cancel, Confirm:
begin
end;
procedure checkKeepOldOnChange(Sender: TObject);
begin
txtPassword.Enabled := not checkKeepOld.Checked;
txtBluePassword.Enabled := not checkKeepOld.Checked;
txtRedPassword.Enabled := not checkKeepOld.Checked;
txtBackendPort.Enabled := not checkKeepOld.Checked;
txtClientPort.Enabled := not checkKeepOld.Checked;
end;
function frmPassword_CreatePage(PreviousPageId: Integer): Integer;
var
Page: TWizardPage;
@@ -323,16 +260,40 @@ begin
Page := CreateCustomPage(
PreviousPageId,
'DCS Olympus passwords',
'Set DCS Olympus Admin and Commander passwords'
'Set DCS Olympus ports and passwords'
);
{ lblKeepOld }
lblKeepOld := TLabel.Create(Page);
with lblKeepOld do
begin
Parent := Page.Surface;
Left := ScaleX(54);
Top := ScaleY(0);
Width := ScaleX(46);
Height := ScaleY(13);
Caption := 'Keep configuration from previous installation';
end;
{ checkKeepOld }
checkKeepOld := TNewCheckBox.Create(Page);
with checkKeepOld do
begin
Parent := Page.Surface;
Left := ScaleX(24);
Top := ScaleY(0);
Width := ScaleX(46);
Height := ScaleY(13);
OnClick := @checkKeepOldOnChange;
end;
{ lblPassword }
lblPassword := TLabel.Create(Page);
with lblPassword do
begin
Parent := Page.Surface;
Left := ScaleX(24);
Top := ScaleY(28);
Top := ScaleY(38);
Width := ScaleX(46);
Height := ScaleY(13);
Caption := 'Game Master password';
@@ -344,10 +305,10 @@ begin
begin
Parent := Page.Surface;
Left := ScaleX(180);
Top := ScaleY(25);
Top := ScaleY(35);
Width := ScaleX(185);
Height := ScaleY(21);
TabOrder := 2;
TabOrder := 1;
end;
{ lblBluePassword }
@@ -356,7 +317,7 @@ begin
begin
Parent := Page.Surface;
Left := ScaleX(24);
Top := ScaleY(58);
Top := ScaleY(66);
Width := ScaleX(46);
Height := ScaleY(13);
Caption := 'Blue Commander password';
@@ -368,7 +329,7 @@ begin
begin
Parent := Page.Surface;
Left := ScaleX(180);
Top := ScaleY(55);
Top := ScaleY(63);
Width := ScaleX(185);
Height := ScaleY(21);
TabOrder := 2;
@@ -380,7 +341,7 @@ begin
begin
Parent := Page.Surface;
Left := ScaleX(24);
Top := ScaleY(88);
Top := ScaleY(94);
Width := ScaleX(46);
Height := ScaleY(13);
Caption := 'Red Commander password';
@@ -392,24 +353,76 @@ begin
begin
Parent := Page.Surface;
Left := ScaleX(180);
Top := ScaleY(85);
Top := ScaleY(91);
Width := ScaleX(185);
Height := ScaleY(21);
TabOrder := 2;
TabOrder := 3;
end;
{ lblClientPort }
lblClientPort := TLabel.Create(Page);
with lblClientPort do
begin
Parent := Page.Surface;
Left := ScaleX(24);
Top := ScaleY(122);
Width := ScaleX(46);
Height := ScaleY(13);
Caption := 'Webserver port';
end;
{ txtClientPort }
txtClientPort := TEdit.Create(Page);
with txtClientPort do
begin
Parent := Page.Surface;
Left := ScaleX(180);
Top := ScaleY(119);
Width := ScaleX(185);
Height := ScaleY(21);
Text := '3000';
OnKeyPress := @AcceptNumbersOnlyKeyPress;
TabOrder := 4;
end;
{ lblBackendPort }
lblBackendPort := TLabel.Create(Page);
with lblBackendPort do
begin
Parent := Page.Surface;
Left := ScaleX(24);
Top := ScaleY(149);
Width := ScaleX(46);
Height := ScaleY(13);
Caption := 'Backend port';
end;
{ txtBackendPort }
txtBackendPort := TEdit.Create(Page);
with txtBackendPort do
begin
Parent := Page.Surface;
Left := ScaleX(180);
Top := ScaleY(147);
Width := ScaleX(185);
Height := ScaleY(21);
Text := '3001';
OnKeyPress := @AcceptNumbersOnlyKeyPress;
TabOrder := 5;
end;
{ lblPasswordInstructions }
lblPasswordInstructions := TNewStaticText.Create(Page);
with lblPasswordInstructions do
begin
Parent := Page.Surface;
Left := ScaleX(24);
Top := ScaleY(120);
Top := ScaleY(180);
Width := ScaleX(340);
Height := ScaleY(13);
WordWrap := True;
Caption := 'Passwords can be changed in the future by editing the file "olympus.json". For more information, see the DCS Olympus Wiki';
Caption := 'Passwords and ports can be changed in the future by using the DCS Olympus configurator. For more information, see the DCS Olympus Wiki.';
end;
with Page do
@@ -427,13 +440,13 @@ end;
procedure InitializeWizard();
begin
{this page will come after welcome page}
AddressPage := frmAddress_CreatePage(wpSelectDir);
PasswordPage:= frmPassword_CreatePage(AddressPage);
InstallationTypePage := frmInstallationType_CreatePage(wpSelectDir);
PasswordPage := frmPassword_CreatePage(InstallationTypePage);
end;
function CheckLocalInstall(): boolean;
begin
if txtLocalInstall.Checked then begin
if radioLocalInstall.Checked then begin
Result := True
end else
begin
@@ -443,7 +456,17 @@ end;
function CheckServerInstall(): boolean;
begin
if txtLocalInstall.Checked then begin
if radioLocalInstall.Checked then begin
Result := False
end else
begin
Result := True
end
end;
function CheckCallConfigurator(): boolean;
begin
if checkKeepOld.Checked then begin
Result := False
end else
begin
@@ -453,7 +476,7 @@ end;
function GetAddress(Value: string): string;
begin
if txtLocalInstall.Checked then begin
if radioLocalInstall.Checked then begin
Result := 'localhost'
end else
begin

View File

@@ -15,7 +15,7 @@ declare_plugin(self_ID,
shortName = "Olympus",
fileMenuName = "Olympus",
version = "v0.4.8-alpha",
version = "v0.4.13-alpha-rc5",
state = "installed",
developerName= "DCS Refugees 767 squadron",
info = _("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."),

View File

@@ -1,11 +1,10 @@
local version = "v0.4.8-alpha"
local version = "v0.4.13-alpha-rc5"
local debug = false -- True enables debug printing using DCS messages
-- .dll related variables
Olympus.OlympusDLL = nil
Olympus.DLLsloaded = false
Olympus.OlympusModPath = os.getenv('DCSOLYMPUS_PATH')..'\\bin\\'
-- Logger reference
Olympus.log = mist.Logger:new("Olympus", 'info')
@@ -21,6 +20,7 @@ Olympus.cloneDatabase = {} -- Database of spawn options, used for units cloning
Olympus.unitIndex = 0 -- Counter used to spread the computational load of data retrievial from DCS
Olympus.unitStep = 50 -- Max number of units that get updated each cycle
Olympus.units = {} -- Table holding references to all the currently existing units
Olympus.unitsInitialLife = {} -- getLife0 returns 0 for ships, so we need to store the initial life of units
Olympus.weaponIndex = 0 -- Counter used to spread the computational load of data retrievial from DCS
Olympus.weaponStep = 50 -- Max number of weapons that get updated each cycle
@@ -30,25 +30,31 @@ Olympus.weapons = {} -- Table holding references to all the currently existing
Olympus.missionStartTime = DCS.getRealTime()
Olympus.napalmCounter = 1
Olympus.fireCounter = 1
-- Load the lua file system
local lfs = require('lfs')
------------------------------------------------------------------------------------------------------
-- Olympus functions
------------------------------------------------------------------------------------------------------
-- Print a debug message if the debug option is true
function Olympus.debug(message, displayFor)
if debug == true then
Olympus.log:info(message)
trigger.action.outText(message, displayFor)
end
end
-- Print a notify message
function Olympus.notify(message, displayFor)
Olympus.log:info(message)
trigger.action.outText(message, displayFor)
end
-- Loads the olympus .dll
function Olympus.loadDLLs()
-- Add the .dll paths
package.cpath = package.cpath..';'..Olympus.OlympusModPath..'?.dll;'
package.cpath = package.cpath..';'..Olympus.instancePath..'?.dll;'
local status
status, Olympus.OlympusDLL = pcall(require, 'olympus')
@@ -509,10 +515,11 @@ function Olympus.removeFire (smokeName)
end
function Olympus.secondaries(vec3)
trigger.action.explosion(vec3, 1)
for i = 1, 10 do
timer.scheduleFunction(Olympus.randomDebries, vec3, timer.getTime() + math.random(0, 180))
end
Olympus.randomDebries(vec3)
--trigger.action.explosion(vec3, 1)
--for i = 1, 10 do
-- timer.scheduleFunction(Olympus.randomDebries, vec3, timer.getTime() + math.random(0, 180))
--end
end
function Olympus.randomDebries(vec3)
@@ -545,7 +552,7 @@ function Olympus.spawnUnits(spawnTable)
local route = nil
local category = nil
-- Generate the units table and rout as per DCS requirements
-- Generate the units table and route as per DCS requirements
if spawnTable.category == 'Aircraft' then
unitsTable = Olympus.generateAirUnitsTable(spawnTable.units)
route = Olympus.generateAirUnitsRoute(spawnTable)
@@ -605,10 +612,10 @@ function Olympus.generateAirUnitsTable(units)
-- Define the loadout
if payload == nil then
if loadout and loadout ~= "" and Olympus.unitPayloads[unit.unitType] and Olympus.unitPayloads[unit.unitType][loadout] then
payload = Olympus.unitPayloads[unit.unitType][loadout]
if loadout ~= nil and loadout ~= "" and Olympus.unitPayloads[unit.unitType] and Olympus.unitPayloads[unit.unitType][loadout] then
payload = { ["pylons"] = Olympus.unitPayloads[unit.unitType][loadout], ["fuel"] = 999999, ["flare"] = 60, ["ammo_type"] = 1, ["chaff"] = 60, ["gun"] = 100 }
else
payload = {}
payload = { ["pylons"] = {}, ["fuel"] = 999999, ["flare"] = 60, ["ammo_type"] = 1, ["chaff"] = 60, ["gun"] = 100 }
end
end
@@ -622,7 +629,7 @@ function Olympus.generateAirUnitsTable(units)
["alt"] = unit.alt,
["alt_type"] = "BARO",
["skill"] = "Excellent",
["payload"] = { ["pylons"] = payload, ["fuel"] = 999999, ["flare"] = 60, ["ammo_type"] = 1, ["chaff"] = 60, ["gun"] = 100, },
["payload"] = payload,
["heading"] = unit.heading,
["callsign"] = { [1] = 1, [2] = 1, [3] = 1, ["name"] = "Olympus" .. Olympus.unitCounter.. "-" .. #unitsTable + 1 },
["name"] = "Olympus-" .. Olympus.unitCounter .. "-" .. #unitsTable + 1,
@@ -913,7 +920,7 @@ function Olympus.setTask(groupName, taskOptions)
end
end
-- Reset the dask of a group
-- Reset the task of a group
function Olympus.resetTask(groupName)
Olympus.debug("Olympus.resetTask " .. groupName, 2)
local group = Group.getByName(groupName)
@@ -979,10 +986,16 @@ function Olympus.setUnitsData(arg, time)
table["category"] = "GroundUnit"
elseif unit:getDesc().category == Unit.Category.SHIP then
table["category"] = "NavyUnit"
elseif Olympus.modsList ~= nil and Olympus.modsList[unit:getDesc().typeName] ~= nil then
table["category"] = Olympus.modsList[unit:getDesc().typeName]
end
else
units[ID] = {isAlive = false}
Olympus.units[ID] = nil
if Olympus.modsList ~= nil and Olympus.modsList[unit:getDesc().typeName] ~= nil then
table["category"] = Olympus.modsList[unit:getDesc().typeName]
else
units[ID] = {isAlive = false}
Olympus.units[ID] = nil
end
end
-- If the category is handled by Olympus, get the data
@@ -992,7 +1005,7 @@ function Olympus.setUnitsData(arg, time)
local position = unit:getPosition()
local heading = math.atan2( position.x.z, position.x.x )
local velocity = unit:getVelocity();
-- Fill the data table
table["name"] = unit:getTypeName()
table["coalitionID"] = unit:getCoalition()
@@ -1004,6 +1017,16 @@ function Olympus.setUnitsData(arg, time)
table["horizontalVelocity"] = math.sqrt(velocity.x * velocity.x + velocity.z * velocity.z)
table["verticalVelocity"] = velocity.y
table["heading"] = heading
-- Track angles are wrong because of weird reference systems, approximate it using latitude and longitude differences
if Olympus.unitsData["units"] ~= nil and Olympus.unitsData["units"][ID] ~= nil and Olympus.unitsData["units"][ID]["position"] ~= nil and Olympus.unitsData["units"][ID]["position"]["lat"] ~= nil and Olympus.unitsData["units"][ID]["position"]["lng"] ~= nil then
local latDifference = lat - Olympus.unitsData["units"][ID]["position"]["lat"]
local lngDifference = lng - Olympus.unitsData["units"][ID]["position"]["lng"]
table["track"] = math.atan2(lngDifference * math.cos(lat / 57.29577), latDifference)
else
table["track"] = math.atan2(velocity.z, velocity.x)
end
table["isAlive"] = unit:isExist() and unit:isActive() and unit:getLife() >= 1
local group = unit:getGroup()
@@ -1025,6 +1048,17 @@ function Olympus.setUnitsData(arg, time)
end
end
-- getLife0 does not seem to work for ships, so we need to keep a reference to the initial life of the unit
if Olympus.unitsInitialLife[ID] == nil then
Olympus.unitsInitialLife[ID] = unit:getLife()
end
-- Get the initial life of the unit to compute the current health
local initialLife = 1
if Olympus.unitsInitialLife[ID] ~= nil then
initialLife = Olympus.unitsInitialLife[ID]
end
table["country"] = unit:getCountry()
table["unitName"] = unit:getName()
table["groupName"] = group:getName()
@@ -1032,11 +1066,55 @@ function Olympus.setUnitsData(arg, time)
table["hasTask"] = controller:hasTask()
table["ammo"] = unit:getAmmo() --TODO remove a lot of stuff we don't really need
table["fuel"] = unit:getFuel()
table["health"] = unit:getLife() / unit:getLife0() * 100
table["health"] = unit:getLife() / initialLife * 100
table["contacts"] = contacts
-- Update the database used for unit cloning
local name = unit:getName()
-- If the unit is not in the clone database it means it was not spawned by Olympus. Let's try and recover it using mist
if Olympus.cloneDatabase[name] == nil then
if mist.DBs ~= nil and mist.DBs.unitsByName ~= nil and mist.DBs.unitsByName[name] ~= nil then
-- Payloads can be copied from ME units only TODO: can we fix this?
local payload = {}
if mist.DBs.MEunitsByName[name] then
payload = mist.getPayload(name)
end
-- Create a mock spawn table to generate the database
local unitsTable = nil
local spawnTable = {}
spawnTable.units = {
[1] = {
["unitType"] = table["name"],
["lat"] = table["position"]["lat"],
["lng"] = table["position"]["lng"],
["alt"] = table["position"]["alt"],
["payload"] = payload,
["liveryID"] = mist.DBs.unitsByName[name]["livery_id"]
}
}
-- Generate the units table as per DCS requirements
if table["category"] == 'Aircraft' then
unitsTable = Olympus.generateAirUnitsTable(spawnTable.units)
elseif table["category"] == 'Helicopter' then
unitsTable = Olympus.generateAirUnitsTable(spawnTable.units)
elseif table["category"] == 'GroundUnit' then
unitsTable = Olympus.generateGroundUnitsTable(spawnTable.units)
elseif table["category"] == 'NavyUnit' then
unitsTable = Olympus.generateNavyUnitsTable(spawnTable.units)
end
-- Save the units in the database, for cloning
for idx, unitTable in pairs(unitsTable) do
-- Force the name of the unit to be equal to the original name
unitTable["name"] = name
Olympus.addToDatabase(mist.utils.deepCopy(unitTable))
end
end
end
-- Update the database used for unit cloning
if Olympus.cloneDatabase[name] ~= nil then
Olympus.cloneDatabase[name]["ID"] = ID
Olympus.cloneDatabase[name]["category"] = unit:getDesc().category
@@ -1294,6 +1372,9 @@ end
------------------------------------------------------------------------------------------------------
-- Olympus startup script
------------------------------------------------------------------------------------------------------
Olympus.instancePath = lfs.writedir().."Mods\\Services\\Olympus\\bin\\"
Olympus.notify("Starting DCS Olympus backend session in "..Olympus.instancePath, 2)
local OlympusName = 'Olympus ' .. version .. ' C++ module';
Olympus.DLLsloaded = Olympus.loadDLLs()
if Olympus.DLLsloaded then

View File

@@ -1,10 +1,10 @@
local version = 'v0.4.8-alpha'
local version = 'v0.4.13-alpha-rc5'
local lfs = require("lfs")
Olympus = {}
Olympus.OlympusDLL = nil
Olympus.cppRESTDLL = nil
Olympus.DLLsloaded = false
Olympus.OlympusModPath = os.getenv('DCSOLYMPUS_PATH')..'\\bin\\'
Olympus.OlympusModPath = lfs.writedir().."Mods\\Services\\Olympus\\bin\\"
log.write('Olympus.HOOKS.LUA', log.INFO,'Executing OlympusHook.lua')
@@ -15,7 +15,7 @@ function Olympus.loadDLLs()
local status
log.write('Olympus.HOOKS.LUA', log.INFO, 'Loading olympus.dll from ['..Olympus.OlympusModPath..']')
status, Olympus.OlympusDLL = pcall(require, 'olympus')
if status then
if status then
log.write('Olympus.HOOKS.LUA', log.INFO, 'olympus.dll loaded successfully')
return true
else

View File

@@ -0,0 +1,24 @@
function disableAutoCapture(airbaseName)
trigger.action.outText("Olympus.disableAutoCapture " .. airbaseName, 2)
local airbase = Airbase.getByName(airbaseName)
if airbase then
airbase:autoCapture(false)
trigger.action.outText("Olympus.disableAutoCapture " .. airbaseName .. " completed successfully", 2)
else
trigger.action.outText("Olympus.disableAutoCapture failed", 2)
end
end
function setAirbaseCoalition(airbaseName, coalitionColor)
trigger.action.outText("Olympus.setAirbaseCoalition trying to set " .. airbaseName .. " to " .. coalitionColor, 2)
local airbase = Airbase.getByName(airbaseName)
if airbase then
disableAutoCapture(airbaseName)
airbase:setCoalition(coalition.side[coalitionColor])
trigger.action.outText("Olympus.setAirbaseCoalition " .. airbaseName .. " set to " .. coalitionColor .. " completed successfully", 5)
else
trigger.action.outText("Olympus.setAirbaseCoalition Airbase not found: " .. airbaseName, 5)
end
end
setAirbaseCoalition("Khasab", "RED")

11
scripts/mods.lua Normal file
View File

@@ -0,0 +1,11 @@
-- Enter here any mods required by your mission as in the example below.
-- Possible categories are:
-- Aircraft
-- Helicopter
-- GroundUnit
-- NavyUnit
Olympus.modsList = {
["A-4E-C"] = "Aircraft",
["Bronco-OV-10A"] = "Aircraft"
}

View File

@@ -1,9 +1,6 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['configurator.py'],
pathex=[],
@@ -14,18 +11,14 @@ a = Analysis(
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='configurator',

View File

@@ -1,6 +1,8 @@
#pragma once
#include "airunit.h"
#define AIRCRAFT_DEST_DIST_THR 2000 // Meters
class Aircraft : public AirUnit
{
public:
@@ -11,6 +13,8 @@ public:
virtual void changeSpeed(string change);
virtual void changeAltitude(string change);
virtual double getDestinationReachedThreshold() { return AIRCRAFT_DEST_DIST_THR; }
protected:
static json::value database;
};

View File

@@ -17,6 +17,7 @@ public:
virtual void changeSpeed(string change) = 0;
virtual void changeAltitude(string change) = 0;
virtual double getDestinationReachedThreshold() { return AIR_DEST_DIST_THR; }
protected:
virtual void AIloop();

View File

@@ -113,7 +113,7 @@ class Move : public Command
{
public:
Move(string groupName, Coords destination, double speed, string speedType, double altitude,
string altitudeType, string taskOptions, string category, function<void(void)> callback = []() {}) :
string altitudeType, string taskOptions, string category, bool onRoad, function<void(void)> callback = []() {}) :
Command(callback),
groupName(groupName),
destination(destination),
@@ -122,12 +122,13 @@ public:
altitude(altitude),
altitudeType(altitudeType),
taskOptions(taskOptions),
category(category)
category(category),
onRoad(onRoad)
{
priority = CommandPriority::HIGH;
priority = CommandPriority::MEDIUM;
};
virtual string getString();
virtual unsigned int getLoad() { return 2; }
virtual unsigned int getLoad() { return onRoad? 45: 5; }
private:
const string groupName;
@@ -138,6 +139,7 @@ private:
const string altitudeType;
const string taskOptions;
const string category;
const bool onRoad;
};
/* Smoke command */
@@ -173,7 +175,7 @@ public:
priority = immediate? CommandPriority::IMMEDIATE: CommandPriority::LOW;
};
virtual string getString();
virtual unsigned int getLoad() { return immediate? 1: 30; }
virtual unsigned int getLoad() { return immediate? 5: 30; }
private:
const string coalition;
@@ -196,7 +198,7 @@ public:
priority = immediate ? CommandPriority::IMMEDIATE : CommandPriority::LOW;
};
virtual string getString();
virtual unsigned int getLoad() { return immediate ? 1 : 30; }
virtual unsigned int getLoad() { return immediate ? 5 : 60; }
private:
const string coalition;
@@ -220,7 +222,7 @@ public:
priority = immediate ? CommandPriority::IMMEDIATE : CommandPriority::LOW;
};
virtual string getString();
virtual unsigned int getLoad() { return immediate ? 1 : 30; }
virtual unsigned int getLoad() { return immediate ? 5 : 45; }
private:
const string coalition;
@@ -245,7 +247,7 @@ public:
priority = immediate ? CommandPriority::IMMEDIATE : CommandPriority::LOW;
};
virtual string getString();
virtual unsigned int getLoad() { return immediate ? 1 : 30; }
virtual unsigned int getLoad() { return immediate ? 5 : 45; }
private:
const string coalition;
@@ -289,7 +291,7 @@ public:
immediate = immediate;
};
virtual string getString();
virtual unsigned int getLoad() { return immediate? 1: 5; }
virtual unsigned int getLoad() { return immediate? 1: 30; }
private:
const unsigned int ID;
@@ -310,7 +312,7 @@ public:
priority = CommandPriority::MEDIUM;
};
virtual string getString();
virtual unsigned int getLoad() { return 1; }
virtual unsigned int getLoad() { return 5; }
private:
const string groupName;
@@ -328,7 +330,7 @@ public:
priority = CommandPriority::HIGH;
};
virtual string getString();
virtual unsigned int getLoad() { return 1; }
virtual unsigned int getLoad() { return 5; }
private:
const string groupName;
@@ -346,7 +348,7 @@ public:
priority = CommandPriority::HIGH;
};
virtual string getString();
virtual unsigned int getLoad() { return 1; }
virtual unsigned int getLoad() { return 5; }
private:
const string groupName;
@@ -379,7 +381,7 @@ public:
priority = CommandPriority::HIGH;
};
virtual string getString();
virtual unsigned int getLoad() { return 1; }
virtual unsigned int getLoad() { return 5; }
private:
const string groupName;
@@ -401,7 +403,7 @@ public:
priority = CommandPriority::HIGH;
};
virtual string getString();
virtual unsigned int getLoad() { return 1; }
virtual unsigned int getLoad() { return 5; }
private:
const string groupName;
@@ -421,7 +423,7 @@ public:
priority = CommandPriority::MEDIUM;
};
virtual string getString();
virtual unsigned int getLoad() { return 4; }
virtual unsigned int getLoad() { return 5; }
private:
const Coords location;

View File

@@ -22,6 +22,7 @@ namespace DataIndex {
horizontalVelocity,
verticalVelocity,
heading,
track,
isActiveTanker,
isActiveAWACS,
onOff,

View File

@@ -1,7 +1,7 @@
#pragma once
#include "unit.h"
#define GROUND_DEST_DIST_THR 100
#define GROUND_DEST_DIST_THR 10
class GroundUnit : public Unit
{

View File

@@ -1,6 +1,8 @@
#pragma once
#include "airunit.h"
#define HELICOPTER_DEST_DIST_THR 500 // Meters
class Helicopter : public AirUnit
{
public:
@@ -11,6 +13,8 @@ public:
virtual void changeSpeed(string change);
virtual void changeAltitude(string change);
virtual double getDestinationReachedThreshold() { return HELICOPTER_DEST_DIST_THR; }
protected:
static json::value database;
};

View File

@@ -24,7 +24,7 @@ public:
void setEras(vector<string> newEras) { eras = newEras; }
void setCommandModeOptions(json::value newOptions);
int getFrameRate() { return frameRate; };
int getFrameRate() { return static_cast<int>(round(frameRate)); };
int getLoad();
bool getRestrictSpawns() { return restrictSpawns; }
bool getRestrictToCoalition() { return restrictToCoalition; }

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