Compare commits

..

5 Commits

Author SHA1 Message Date
walterroach
6f65637f6b Fix bug #353 2020-11-27 17:47:19 -06:00
Dan Albert
cceb3da693 Resurrect force multiplier option.
Fixes https://github.com/Khopa/dcs_liberation/issues/440

(cherry picked from commit 611f04ab5a)
2020-11-25 14:13:54 -08:00
walterroach
a357bf3c08 Fix bug #400 2020-11-22 17:32:05 -06:00
walterroach
3f251c38d8 Set AGL altitude on target waypoints 2020-11-22 16:42:22 -06:00
walterroach
01702f046e Add missing P-47 icons 2020-11-22 02:12:15 -06:00
2738 changed files with 43189 additions and 145518 deletions

View File

@@ -1,8 +0,0 @@
[report]
exclude_lines =
pragma: no cover
if TYPE_CHECKING:
[run]
branch = True
source = game,pydcs_extensions,qt_ui,resources/tools

View File

@@ -1,2 +0,0 @@
# Black
a47bef1f1336fd264d0b175f4421758339a30acb

92
.gitattributes vendored
View File

@@ -1,92 +0,0 @@
* text=auto
*.pxd text diff=python
*.py text diff=python
*.py3 text diff=python
*.pyw text diff=python
*.pyx text diff=python
*.pyz text diff=python
*.pyi text diff=python
*.db binary
*.p binary
*.pkl binary
*.pickle binary
*.pyc binary export-ignore
*.pyo binary export-ignore
*.pyd binary
unshipped_data/arcgis_maps/ filter=lfs diff=lfs merge=lfs -text
# https://github.com/alexkaratarakis/gitattributes/blob/master/Common.gitattributes
# Documents
*.bibtex text diff=bibtex
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
*.md text diff=markdown
*.mdx text diff=markdown
*.tex text diff=tex
*.adoc text
*.textile text
*.mustache text
*.csv text
*.tab text
*.tsv text
*.txt text
*.sql text
*.epub diff=astextplain
# Graphics
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.tif binary
*.tiff binary
*.ico binary
# SVG treated as text by default.
*.svg text
# If you want to treat it as binary,
# use the following line instead.
# *.svg binary
*.eps binary
# Scripts
*.bash text eol=lf
*.fish text eol=lf
*.sh text eol=lf
*.zsh text eol=lf
# These are explicitly windows files and should use crlf
*.bat text eol=crlf
*.cmd text eol=crlf
*.ps1 text eol=crlf
# Serialisation
*.json text
*.toml text
*.xml text
*.yaml text
*.yml text
# Archives
*.7z binary
*.gz binary
*.tar binary
*.tgz binary
*.zip binary
# Text files where line endings should be preserved
*.patch -text
#
# Exclude files from exporting
#
.gitattributes export-ignore
.gitignore export-ignore
.gitkeep export-ignore

View File

@@ -1,86 +0,0 @@
---
name: Bug report
description: >
Use for any bug that happens after campaign generation. If the New Game wizard
failed, use the "New Game wizard failed" template instead.
labels: bug
body:
- type: markdown
attributes:
value: >
Before filing, please search the issue tracker to see if the issue has
already been reported.
- type: dropdown
validations:
required: true
attributes:
label: Affected versions
multiple: true
description: >
Select all DCS Liberation versions in which you have observed this bug.
You do not need to test all of them, but the information is useful if
you have it.
If you do not see your version listed here you are on an old release
that is not supported, and the bug may already be fixed in a newer
release. Check that the bug still exists in a newer release before
filing.
If the bug was found in a development build, select "Development build"
and provide a link to the build in the field below.
options:
- 6.1.1
- Development build
- type: textarea
attributes:
label: Build information
description:
The build information from the Help -> Report an issue window.
- type: textarea
attributes:
label: Description
description: >
Describe the bug. What went wrong? What did you expect to happen
instead? What steps should we take to reproduce the error? If an error
dialog was shown, include the full text.
validations:
required: true
- type: textarea
attributes:
label: Save game and other files
description: >
Attach any files needed to reproduce the bug here. **A save game is
required.** We typically cannot help without a save game (the
`.liberation` (or `.liberation.zip`, for 7.x) file found in
`%USERPROFILE%/Saved Games/DCS/Liberation/Saves`), so most bugs filed
without saved games will be closed without investigation.
Other useful files to include are:
The Liberation log file. The log file is located at `<Liberation install
directory>/logs/liberation.log`. The log often includes data about
non-fatal errors that could be the root cause of the problem.
The `liberation_nextturn.miz` or a track file. This should always be
included for bugs where the mission was generated incorrectly or where
the in-game AI is misbehaving.
The `state.json` file for the most recently completed turn, located at
`<Liberation install directory>/state.json`. This file is essential for
investigating any issues with end-of-turn results processing. **If you
include this file, also include `last_turn.liberation`** (unless the
save is from 7.x or newer, which includes that information in the save
automatically).
You can attach files to the bug by dragging and dropping the file into
this text box. GitHub will not allow uploads of all file types, so
attach a zip of the files if needed.
validations:
required: true

View File

@@ -1,28 +0,0 @@
---
name: Campaign update submission
about: Submit an update to a campaign you maintain.
title: 'Update for <campaign name>'
labels: campaign-update-submission
assignees: ''
---
This form should only be used for submitted updated miz/json files for campaigns
distributed with Liberation. If you are _requesting_ an update to a campaign, see
https://github.com/dcs-liberation/dcs_liberation/wiki/Campaign-maintenance. If the
campaign has an owner, it will be updated before release. If it does not, you can
volunteer to own it.
If you are not the owner of the campaign listed on
https://github.com/dcs-liberation/dcs_liberation/wiki/Campaign-maintenance, please start
there.
Otherwise, delete everything above the line below and fill out the following form. Note:
GitHub does not accept .miz files. You can either rename the file to .miz.txt or add the
file to a .zip file.
---
* Campaign name:
* Files:
* Update summary (optional):

View File

@@ -1,19 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: FAQ
url: https://discord.gg/PXHA6AXw
about: Check to see if your issue is in the FAQ.
- name: Manual
url: https://github.com/dcs-liberation/dcs_liberation/wiki/
- name: Feature blocking DCS AI bugs
url: https://github.com/dcs-liberation/dcs_liberation#dcs-bugs
about: >
A list of known DCS bugs that prevent us from improving AI behavior. Check
the list before filing AI bugs here to see if it's something we know about
but cannot fix.
- name: DCS bugs
url: https://forums.eagle.ru/forum/119-dcs-world-27/
about: >
DCS bugs should be reported against DCS, not here. Occasionally we can add
workarounds for DCS bugs. Use the "Bug report" template if you can suggest
a workaround.

View File

@@ -1,21 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
Before filing, please search the issue tracker to see if this feature has already been requested.
If requesting a DCS AI feature, check If reporting a DCS AI bug, check https://github.com/dcs-liberation/dcs_liberation#dcs-bugs.
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -1,12 +0,0 @@
---
name: Mod support request
about: Request Liberation support for new mods, or updates to existing mods
title: Add/update <mod name>
labels: mod support
assignees: ''
---
* Mod name:
* Mod URL:
* Update or new mod?

View File

@@ -1,113 +0,0 @@
---
name: New Game wizard failed
description: >
Use for bugs that prevent the "New Game" wizard from completing successfully.
If the wizard completes without issue, use the normal bug report template.
labels: bug
body:
- type: markdown
attributes:
value: >
Before filing, please search the issue tracker to see if the issue has
already been reported.
If the bug is not related to campaign generation (the campaign was
created successfully and as expected), use the normal bug report
template instead, as this template will not include the information we
need. We are unable to investigate incomplete bug reports, so they will
be closed and you will be asked to refile. If you're unsure, use your
best guess. Needing to refile is not the end of the world :)
- type: dropdown
validations:
required: true
attributes:
label: Affected versions
multiple: true
description: >
Select all DCS Liberation versions in which you have observed this bug.
You do not need to test all of them, but the information is useful if
you have it.
If you do not see your version listed here you are on an old release
that is not supported, and the bug may already be fixed in a newer
release. Check that the bug still exists in a newer release before
filing.
If the bug was found in a development build, select "Development build"
and provide a link to the build in the field below.
options:
- 6.1.1
- Development build
- type: textarea
attributes:
label: Build information
description:
The build information from the Help -> Report an issue window.
- type: input
attributes:
label: Campaign name
description: >
The name of the campaign you selected. If the bug only occurs with a
custom campaign (or modifications to a stock campaign), upload the
campaign file as an attachment to the bug description field.
validations:
required: true
- type: input
attributes:
label: Blue faction
description: >
The name of the blue faction you selected. If the bug only occurs with a
custom faction (or modifications to a stock faction), upload the faction
file as an attachment to the bug description field.
validations:
required: true
- type: input
attributes:
label: Red faction
description: >
The name of the red faction you selected. If the bug only occurs with a
custom faction (or modifications to a stock faction), upload the faction
file as an attachment to the bug description field.
validations:
required: true
- type: textarea
attributes:
label: Modifications to default settings
description: >
Describe any modifications you made to the default campaign generation
settings.
- type: textarea
attributes:
label: Description
description: >
Describe the bug. What went wrong? If an error dialog was shown, include
the full text.
Attach any relevant files such as custom campaign files or factions
here. You can attach files to the bug by dragging and dropping the file
into this text box. GitHub will not allow uploads of all file types, so
attach a zip of the files if needed.
If possible, also include the save game. If the bug prevented the game
from being generated at all this will not be possible, but if the bug is
that the wizard generated something incorrectly, the save game will help
us see what went wrong.
validations:
required: true
- type: textarea
attributes:
label: Log file
description: >
Attach the Liberation log file. The log file is located at `<Liberation
install directory>/logs/liberation.log`.
You can attach files to the bug by dragging and dropping the file into
this text box.
validations:
required: true

View File

@@ -1,22 +0,0 @@
name: Build Liberation package
description: Assembles the full Liberation application.
runs:
using: composite
steps:
- name: Build client
shell: powershell
run: |
cd client
npm run build
- name: Build binaries
shell: powershell
run: |
./venv/scripts/activate
$env:PYTHONPATH=".;./pydcs"
pyinstaller pyinstaller.spec
- name: Install changelog
shell: powershell
run: |
Copy-Item .\changelog.md .\dist

View File

@@ -1,16 +0,0 @@
name: mypy
description: Type checks Python code.
runs:
using: composite
steps:
- name: mypy game
shell: powershell
run: |
./venv/scripts/activate
mypy game
- name: mypy tests
shell: powershell
run: |
./venv/scripts/activate
mypy tests

View File

@@ -1,17 +0,0 @@
name: Liberation JS set-up
description: Sets up the Liberation Javascript environment.
runs:
using: composite
steps:
- name: Set up Node
uses: actions/setup-node@v2
with:
node-version: "16"
cache: npm
cache-dependency-path: client/package-lock.json
- name: npm ci
shell: powershell
run: |
cd client
npm ci

View File

@@ -1,21 +0,0 @@
name: Liberation Python set-up
description: Sets up the Liberation Python environment.
runs:
using: composite
steps:
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: "3.11"
cache: pip
- name: Install environment
shell: powershell
run: |
python -m venv ./venv
- name: Install dependencies
shell: powershell
run: |
./venv/scripts/activate
python -m pip install -r requirements.txt

View File

@@ -1,24 +0,0 @@
Pull requests should be made against the `develop` branch. Any backports
necessary will be handled by the development team.
Pull requests should be focused on one task. Multiple bug fixes should be
multiple PRs. We cannot merge half a PR, and combined PRs are much more
difficult to review. PRs that do not adhere to this will have their review
delayed.
Prefer rebase to merge, and squash commits as needed to preserve a readable
commit history. This project maintains linear history in the develop branch, so
we will either rebase or squash your PR when merging. It is much easier for us
if your branch already has a readable commit history (ensure that your commit
subject lines are clear enough to identify the patch in the git log). An
exception to this is made for large PRs that are likely to require multiple
rounds of review; in that case it's easier if you **don't** do this (GitHub
does not preserve the history of old commits, so we cannot filter a PR for only
new changes if a branch is force pushed) and we will squash it when merging.
New features and bug fixes are usually worth mentioning in the changelog.
Exceptions are fixes for bugs that never shipped (were only present in a canary
build), and changes with no intended user observable behavior, such as a
refactor. If you're comfortable writing the note yourself, add it to
`changelog.md` in the root of the project in the section for the upcoming
release.

View File

@@ -3,39 +3,56 @@ name: Build
on: [push, pull_request]
jobs:
lint:
uses: ./.github/workflows/lint.yml
test:
uses: ./.github/workflows/test.yml
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/checkout@v2
with:
submodules: true
- name: Set up Python environment
uses: ./.github/actions/setup-liberation-python
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Set up JS environment
uses: ./.github/actions/setup-liberation-js
- name: Install environment
run: |
python -m venv ./venv
- name: Set build number
run: |
[IO.File]::WriteAllLines($pwd.path + "\resources\buildnumber", $env:GITHUB_RUN_NUMBER)
[IO.File]::WriteAllLines($pwd.path + "\resources\gitsha", $env:GITHUB_SHA)
- name: Install dependencies
run: |
./venv/scripts/activate
python -m pip install -r requirements.txt
# For some reason the shiboken2.abi3.dll is not found properly, so I copy it instead
Copy-Item .\venv\Lib\site-packages\shiboken2\shiboken2.abi3.dll .\venv\Lib\site-packages\PySide2\ -Force
- name: Build app
uses: ./.github/actions/build-app
- name: mypy game
run: |
./venv/scripts/activate
mypy game
- name: Create archive
run:
Compress-Archive -Path .\dist\dcs_liberation\ -DestinationPath
dist\dcs_liberation.zip
- name: mypy gen
run: |
./venv/scripts/activate
mypy gen
- uses: actions/upload-artifact@v2
with:
name: dcs_liberation
path: dist/dcs_liberation.zip
- name: mypy theater
run: |
./venv/scripts/activate
mypy theater
- name: update build number
run: |
[IO.File]::WriteAllLines($pwd.path + "\resources\buildnumber", $env:GITHUB_RUN_NUMBER)
- name: Build binaries
run: |
./venv/scripts/activate
$env:PYTHONPATH=".;./pydcs"
pyinstaller pyinstaller.spec
- uses: actions/upload-artifact@v2
with:
name: dcs_liberation
path: dist/

View File

@@ -1,30 +0,0 @@
name: Python lint
on: workflow_call
jobs:
black:
name: Black
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: psf/black@stable
with:
version: ~=22.12
src: "."
options: "--check"
mypy:
name: Type checking
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Set up Python environment
uses: ./.github/actions/setup-liberation-python
- name: mypy
uses: ./.github/actions/mypy

View File

@@ -2,83 +2,125 @@ name: Release Pipeline
on:
push:
tags: ["*"]
tags: [ '*' ]
jobs:
lint:
uses: ./.github/workflows/lint.yml
test:
uses: ./.github/workflows/test.yml
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: actions/checkout@v2
with:
submodules: true
- name: Set up Python environment
uses: ./.github/actions/setup-liberation-python
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Set up JS environment
uses: ./.github/actions/setup-liberation-js
- name: Install environment
run: |
python -m venv ./venv
- name: Finalize build
run: |
New-Item -ItemType file resources\final
- name: Install dependencies
run: |
./venv/scripts/activate
python -m pip install -r requirements.txt
# For some reason the shiboken2.abi3.dll is not found properly, so I copy it instead
Copy-Item .\venv\Lib\site-packages\shiboken2\shiboken2.abi3.dll .\venv\Lib\site-packages\PySide2\ -Force
- name: Build app
uses: ./.github/actions/build-app
with:
release: true
- name: Finalize version
run: |
New-Item -ItemType file resources\final
- uses: actions/upload-artifact@v2
with:
name: dcs_liberation
path: dist/
- name: mypy game
run: |
./venv/scripts/activate
mypy game
- name: mypy gen
run: |
./venv/scripts/activate
mypy gen
- name: mypy theater
run: |
./venv/scripts/activate
mypy theater
- name: Build binaries
run: |
./venv/scripts/activate
$env:PYTHONPATH=".;./pydcs"
pyinstaller pyinstaller.spec
- name: Create Installer
env:
TAG_NAME: ${{ github.ref }}
run: |
$version = ($env:TAG_NAME -split "/") | Select-Object -Last 1
(Get-Content .\installer\dcs_liberation.iss) -replace "{{version}}",$version | Out-File .\build\installer.iss
cd .\installer
iscc.exe ..\build\installer.iss
cd ..
Copy-Item .\changelog.md .\dist
- uses: actions/upload-artifact@v2
with:
name: dcs_liberation
path: dist/
release:
needs: [build]
needs: [ build ]
runs-on: windows-latest
steps:
- uses: actions/download-artifact@v2
with:
name: dcs_liberation
- uses: actions/download-artifact@v2
with:
name: dcs_liberation
- name: "Get Version"
id: version
env:
TAG_NAME: ${{ github.ref }}
run: |
Get-ChildItem -Recurse -Depth 1
$version = ($env:TAG_NAME -split "/") | Select-Object -Last 1
$prerelease = ("2.1.1-alpha3" -match '[^\.\d]').ToString().ToLower()
Write-Host $version
Write-Host $prerelease
Write-Output "::set-output name=number::$version"
Write-Output "::set-output name=prerelease::$prerelease"
$changelog = Get-Content .\changelog.md
$last_change = ($changelog | Select-String -Pattern "^#\s" | Select-Object -Skip 1 -First 1).LineNumber - 2
($changelog | Select-Object -First $last_change) -join "`n" | Out-File .\releasenotes.md
Compress-Archive -Path .\dcs_liberation -DestinationPath "dcs_liberation.$version.zip" -Compression Optimal
- name: "Get Version"
id: version
env:
TAG_NAME: ${{ github.ref }}
run: |
Get-ChildItem -Recurse -Depth 1
$version = ($env:TAG_NAME -split "/") | Select-Object -Last 1
$prerelease = ("2.1.1-alpha3" -match '[^\.\d]').ToString().ToLower()
Write-Host $version
Write-Host $prerelease
Write-Output "::set-output name=number::$version"
Write-Output "::set-output name=prerelease::$prerelease"
$changelog = Get-Content .\changelog.md
$last_change = ($changelog | Select-String -Pattern "^#\s" | Select-Object -Skip 1 -First 1).LineNumber - 2
($changelog | Select-Object -First $last_change) -join "`n" | Out-File .\releasenotes.md
Compress-Archive -Path .\dcs_liberation -DestinationPath "dcs_liberation.$version.zip" -Compression Optimal
- uses: actions/create-release@v1
id: create_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
body_path: releasenotes.md
draft: false
prerelease: ${{ steps.version.outputs.prerelease }}
- uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./dcs_liberation.exe
asset_name: dcs_liberation.${{ steps.version.outputs.number }}.exe
asset_content_type: application/exe
- uses: actions/create-release@v1
id: create_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
body_path: releasenotes.md
draft: false
prerelease: ${{ steps.version.outputs.prerelease }}
- uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./dcs_liberation.${{ steps.version.outputs.number }}.zip
asset_name: dcs_liberation.${{ steps.version.outputs.number }}.zip
asset_content_type: application/zip
- uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./dcs_liberation.${{ steps.version.outputs.number }}.zip
asset_name: dcs_liberation.${{ steps.version.outputs.number }}.zip
asset_content_type: application/zip

View File

@@ -1,38 +0,0 @@
name: Tests
on: workflow_call
jobs:
python-tests:
name: Python tests
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Set up Python environment
uses: ./.github/actions/setup-liberation-python
- name: run tests
run: |
./venv/scripts/activate
pytest --cov-report=xml tests
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
ts-tests:
name: Typescript tests
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Set up JS environment
uses: ./.github/actions/setup-liberation-js
- name: run tests
run: |
cd client
npm test -- --coverage
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3

15
.gitignore vendored
View File

@@ -1,25 +1,22 @@
*.pyc
__pycache__
build/**
# Sphinx
docs/_build
resources/payloads/*.lua
venv
logs.txt
.DS_Store
.vscode/settings.json
dist/**
/.coverage
a.py
resources/tools/a.miz
# User-specific stuff
.idea/
.env
env/
/kneeboards
/liberation_preferences.json
/state.json
/serverconfig.env
/logs/
/resources/logging.yaml
logs/
qt_ui/logs/liberation.log
*.psd

4
.gitmodules vendored Normal file
View File

@@ -0,0 +1,4 @@
[submodule "pydcs"]
path = pydcs
url = https://github.com/pydcs/dcs
branch = master

View File

@@ -1,6 +0,0 @@
repos:
- repo: https://github.com/psf/black
rev: 22.12.0
hooks:
- id: black
language_version: python3

View File

@@ -1,13 +0,0 @@
version: 2
build:
os: ubuntu-22.04
tools:
python: "3.11"
sphinx:
configuration: docs/conf.py
python:
install:
- requirements: docs/requirements.txt

39
.vscode/launch.json vendored
View File

@@ -15,32 +15,6 @@
},
"preLaunchTask": "Prepare Environment"
},
{
"name": "Python: Debug",
"type": "python",
"request": "launch",
"program": "qt_ui\\main.py",
"console": "integratedTerminal",
"env": {
"PYTHONPATH": ".;./pydcs",
"CORS_ALLOW_DEBUG_SERVER": "true"
},
"args": ["--dev"],
"preLaunchTask": "Prepare Environment"
},
{
"name": "Node: Development Server",
"type": "node",
"request": "launch",
"cwd": "${workspaceRoot}\\client",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run", "start"
],
"env": {
"BROWSER": "none"
},
},
{
"name": "Python: Make Release",
"type": "python",
@@ -51,17 +25,6 @@
"PYTHONPATH": ".;./pydcs"
},
"preLaunchTask": "Prepare Environment"
},
{
"name": "Fix Layout orientation",
"type": "python",
"request": "launch",
"program": "resources\\tools\\fix_layout_orientation.py",
"console": "integratedTerminal",
"env": {
"PYTHONPATH": ".;./pydcs"
},
"args": ["resources/layouts/anti_air/S-300_Site.miz"]
},
}
]
}

View File

@@ -1,76 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at khopa.studio@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

View File

@@ -1,26 +0,0 @@
First, note that we have a code of conduct, please follow it in all your interactions with the project.
## Contributing as a non-developer
* Report bugs by opening issues here on Github.
* Help others users on Discord by answering their questions.
* Raise awareness about the project, by making a video and/or a tutorial.
Should you report a bug, please use the search bar at the top of the page to see if it has already been reported.
Note that you may need to remove the filter for open bugs if it's something we've recently fixed.
## Making content for Liberation
You can create new campaigns : See [campaign creation wiki](https://github.com/dcs-liberation/dcs_liberation/wiki/Custom-Campaigns).
You can also improve existing campaigns.
You can then submit new campaigns on the "campaigns" channel on Discord, or by making a pull request if you are comfortable with git.
## Develop new features
If you want to develop a new feature, we recommend you first open an issue describing the new feature and discuss it with us on Discord before starting development.
However, feel free to work on any existing issue.
## Pull requests
Please submit your pull requests on the **develop** branch. We expect a description of its content, and when applicable, a reference to the issue(s) it is resolving.

165
LICENSE
View File

@@ -1,165 +0,0 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@@ -1,53 +1,29 @@
[![Logo](https://i.imgur.com/HJBT4BL.png)](https://shdwp.github.io/ukraine/)
(Github Readme Banner and Splash screen Artwork by Andriy Dankovych, CC BY-SA 4.0)
![Logo](https://i.imgur.com/c2k18E1.png)
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg?logo=paypal)](https://www.paypal.com/paypalme/KhopaDCSL)
[![Patreon](https://img.shields.io/badge/patreon-become%20a%20patron-orange?logo=patreon)](https://patreon.com/khopa)
[![Download](https://img.shields.io/github/downloads/dcs-liberation/dcs_liberation/total?label=Download)](https://github.com/dcs-liberation/dcs_liberation/releases)
[![Download](https://img.shields.io/github/downloads/khopa/dcs_liberation/total?label=Download)](https://github.com/Khopa/dcs_liberation/releases)
[![Discord](https://img.shields.io/discord/595702951800995872?label=Discord&logo=discord)](https://discord.gg/bKrtrkJ)
[![GitHub pull requests](https://img.shields.io/github/issues-pr/dcs-liberation/dcs_liberation)](https://github.com/dcs-liberation/dcs_liberation)
[![GitHub issues](https://img.shields.io/github/issues/dcs-liberation/dcs_liberation)](https://github.com/dcs-liberation/dcs_liberation/issues)
![GitHub stars](https://img.shields.io/github/stars/dcs-liberation/dcs_liberation?style=social)
[![GitHub pull requests](https://img.shields.io/github/issues-pr/khopa/dcs_liberation)](https://github.com/Khopa/dcs_liberation)
[![GitHub issues](https://img.shields.io/github/issues/khopa/dcs_liberation)](https://github.com/Khopa/dcs_liberation/issues)
![GitHub stars](https://img.shields.io/github/stars/khopa/dcs_liberation?style=social)
## About DCS Liberation
DCS Liberation is a [DCS World](https://www.digitalcombatsimulator.com/en/products/world/) turn based single-player or co-op dynamic campaign.
It is an external program that generates full and complex DCS missions and manage a persistent combat environment.
It is an external program that generates full and complex DCS missions and manage a persistent combat environment.
**Note that DCS Liberation does not support the stable release of DCS. We can
only guarantee compatibility with either the open beta or the stable release,
and more people play the open beta. DCS stable _might_ work sometimes, but it's
untested, and we will be unable to fix any bugs unique to stable DCS.**
![Screenshot](https://user-images.githubusercontent.com/315852/120939254-0b4a9f80-c6cc-11eb-82f5-ce3f8d714bfe.png)
![Logo](https://i.imgur.com/4hq0rLq.png)
## Downloads
Latest release is available here : https://github.com/dcs-liberation/dcs_liberation/releases
To download preview builds of the next version of DCS Liberation, see https://github.com/dcs-liberation/dcs_liberation/wiki/Preview-builds.
## DCS bugs
These DCS bugs prevent us from improving AI behavior. Please upvote them! (But please
_don't_ spam them with comments):
* [A2A and SEAD escorts don't escort](https://forums.eagle.ru/topic/251798-options-for-alternate-ai-escort-behavior/?tab=comments#comment-4668033)
* [DEAD can't use mixed loadouts effectively](https://forums.eagle.ru/topic/271941-ai-rtbs-after-firing-decoys-despite-full-load-of-bombs/)
## Bugs and feature requests
If you need to report a bug or want to suggest a new feature, you can do this on our [bug tracker](https://github.com/dcs-liberation/dcs_liberation/issues). In either case, please use the search bar at the top of the page to see if it has already been reported. Note that you may need to remove the filter for open bugs if it's something we've recently fixed.
## Roadmap
Our plans for future releases can be found on our [Projects page](https://github.com/dcs-liberation/dcs_liberation/projects). Each planned release has a Project, and the page for that project has columns for to do, in progress, and done. Items in the Done column are in the [preview build](https://github.com/dcs-liberation/dcs_liberation/wiki/Preview-builds) for that release. Items in the To do column are planned to be added to that release.
Latest release is available here : https://github.com/Khopa/dcs_liberation/releases
## Resources
Tutorials, contributors and developer's guides are available in the project's [Wiki](https://github.com/dcs-liberation/dcs_liberation/wiki/)
Tutorials, contributors and developer's guides are available in the project's [Wiki](https://github.com/Khopa/dcs_liberation/wiki/)
## Special Thanks

View File

@@ -1,668 +1,6 @@
# 7.0.0
Saves from 6.x are not compatible with 7.0.
## Features/Improvements
* **[Engine]** Support for DCS 2.8.5.40170.
* **[Engine]** Saved games are now a zip file of save assets for easier bug reporting. The new extension is .liberation.zip. Drag and drop that file into bug reports.
* **[Campaign]** Added options to limit squadron sizes and to begin all squadrons at maximum strength. Maximum squadron size is defined during air wing configuration with default values provided by the campaign.
* **[Campaign]** Added handling for more DCS death events. This probably does not catch any deaths that weren't previously tracked, but it should record them sooner, which will improve results for game crashes or other early exits.
* **[Campaign AI]** The campaign AI now prefers fulfilling missions with squadrons which have a matching primary task. Previously distance from target held a stronger influence than task preference. Primary tasks for squadrons are set by campaign designers but are user-configurable.
* **[Flight Planning]** Package TOT and composition can be modified after advancing time in Liberation.
* **[Mission Generation]** Units on the front line are now hidden on MFDs.
* **[Mission Generation]** Preset radio channels will now be configured for both A-10C modules.
* **[Mission Generation]** The A-10C II now uses separate radios for inter- and intra-flight comms (similar to other modern aircraft).
* **[Mission Generation]** Wind speeds no longer follow a uniform distribution. Median wind speeds are now much lower and the standard deviation has been reduced considerably at altitude but increased somewhat at MSL.
* **[Mission Generation]** Improved task generation for SEAD flights carrying TALDs.
* **[Mission Generation]** Added task timeout for SEAD flights with TALDs to prevent AI from overflying the target.
* **[Mission Generation]** Game state will automatically be checkpointed before fast-forwarding the mission, and restored on mission abort. This means that it's now possible to abort a mission and make changes without needing to manually re-load your game.
* **[Modding]** Updated Community A-4E-C mod version support to 2.1.0 release.
* **[Modding]** Add support for VSN F-4B and F-4C mod.
* **[Modding]** Added support for AI C-47 mod.
* **[Modding]** Custom factions can now be defined in YAML as well as JSON. JSON support may be removed in the future if having both formats causes confusion.
* **[Modding]** Campaigns which require custom factions can now define those factions directly in the campaign YAML. See Operation Aliied Sword for an example.
* **[Modding]** The `mission_types` field in squadron files has been removed. Squadron task capability is now determined by airframe, and the auto-assignable list has always been overridden by the campaign settings.
* **[Modding]** Aircraft task capabilities and preferred aircraft for each task are now moddable in the aircraft unit yaml files. Each aircraft has a weight per task. Higher weights are given higher preference.
* **[Modding]** Wind speed generation inputs are now moddable. See https://dcs-liberation.rtfd.io/en/latest/modding/weather.html.
* **[New Game Wizard]** Choices for some options will be remembered for the next new game. Not all settings will be preserved, as many are campaign dependent.
* **[New Game Wizard]** Lua plugins can now be set while creating a new game.
* **[New Game Wizard]** Squadrons can be directly replaced with a preset during air wing configuration rather than needing to remove and create a new squadron.
* **[New Game Wizard]** Squadron liveries can now be selected during air wing configuration.
* **[Squadrons]** Squadron-specific mission capability lists no longer restrict players from assigning missions outside the squadron's preferences.
* **[UI]** The orientation of objects like SAMs, EWRs, garrisons, and ships can now be manually adjusted.
## Fixes
* **[Campaign]** Fixed a longstanding bug where oversized airlifts could corrupt a save with empty convoys.
* **[Campaign]** Aircraft with built-in TGPs but without an external pod will no longer degrade automatic loadouts to iron bombs.
* **[Engine]** Fixed crash in startup caused by a corrupted Liberation preferences file.
* **[Flight Planning]** AEW&C missions are now plannable over FOBs and LHAs.
* **[Flight Planning]** BAI is no longer plannable against buildings.
* **[Modding]** Fixed an issue where Falklands campaigns created or edited with new versions of DCS could not be loaded.
* **[Modding]** Fixed decoding of campaign yaml files to use UTF-8 rather than the system locale's default. It's now possible to use "Bf 109 K-4 Kurfürst" as a preferred aircraft type.
* **[Mission Generation]** Planes will no longer spawn in helipads that are not also designated for fixed wing parking.
* **[Mission Generation]** Potentially an issue where ground war planning game state could become corrupted, preventing mission generation.
* **[Mission Generation]** Refueling tasks will now only be created for flights that have a tanker in their package.
* **[Mission Generation]** Fixed missing Tanker task on recovery tanker missions.
* **[UI]** Fixed error when resetting air wing configuration during game setup.
* **[UI]** Fixed flight plan recreation when changing mission type with "Recreate as" flight options.
* **[UI]** Fixed failure to launch UI when Liberation persistent preferences file was corrupt.
# 6.1.1
## Fixes
* **[Data]** Fixed unit ID for the KS-19 AAA. KS-19 would not previously generate correctly in missions. A new game is required for this fix to take effect.
* **[Flight Planning]** Automatic flight planning will no longer accidentally plan a recovery tanker instead of a theater refueling package. This fixes a potential crash during mission generation when opfor plans a refueling task at a sunk carrier. You'll need to skip the current turn to force opfor to replan their flights to get the fix.
* **[Mission Generation]** Using heliports (airports without any runways) will no longer cause mission generation to fail.
* **[Mission Generation]** Prevent helicopters from spawning into collisions at FARPs when more than one flight uses the same FARP.
# 6.1.0
Saves from 6.0.0 are compatible with 6.1.0
## Features/Improvements
* **[Engine]** Support for DCS 2.8.1.34437, including Blackshark 3.
* **[Factions]** Defaulted bluefor modern to use Georgian and Ukrainian liveries for Russian aircraft.
* **[Factions]** Added Peru.
* **[Flight Planning]** AEW&C and Refueling flights are now plannable on LHA carriers.
* **[Flight Planning]** Refueling flights planned on aircraft carriers will act as a recovery tanker for the carrier.
* **[Loadouts]** Adjusted F-15E loadouts.
* **[Mission Generation]** The previous turn will now be saved as last_turn.liberation when submitting mission results. This is often essential for debugging bug reports. **Include this file in the bug report whenever it is available.**
* **[Modding]** Added support for the HMS Ariadne, Achilles, and Castle class.
* **[Modding]** Added HMS Invincible to the game data as a helicopter carrier.
## Fixes
* **[Flight Planning]** Fixes CAS flights not having landing waypoints.
* **[Mission Generation]** Airbase and FOB capture is no longer blocked by grounded aircraft / helicopters.
* **[Squadrons]** Fixed the livery for the VF-33 F-14A squadron.
* **[Theaters]** Fixed Channel campaigns not having data for land/sea/obstacle boundaries, causing front lines to extend into forests and water. Requires a new campaign to get the fix.
* **[UI]** Fixed an issue where manual submit of mission results did not end the mission correctly.
# 6.0.0
Saves from 5.x are not compatible with 6.0.
## Features/Improvements
* **[Engine]** Support for DCS 2.8.0.33006.
* **[Factions]** Updated the Faction file structure. Older custom faction files will not work correctly and have to be updated to the new structure.
* **[Flight Planning]** Added preset formations for different flight types at hold, join, ingress, and split waypoints. Air to Air flights will tend toward line-abreast and spread-four formations. Air to ground flights will tend towards trail formation.
* **[Flight Planning]** Added the ability to plan tankers for recovery on package flights. This mission type will not be planned automatically.
* **[Flight Planning]** Air to Ground flights now have ECM enabled on lock at the join point, and SEAD/DEAD also have ECM enabled on detection and lock at ingress.
* **[Flight Planning]** AWACS flightplan changed from orbit to a racetrack to reduce data link disconnects which were caused by blind spots as a result of the bank angle.
* **[Flight Planning]** Added a new helo mission type: AirAssault which can be used to load and transport infantry troops from a pickup zone or a carrier to an enemy CP to capture it.
* **[Flight Planning]** Improved the Airlift mission type so that it now can be enforced within the unit transfer dialog and implemented CTLD support. This allows user to spawn sling loadable crates at the pickup location and fly transport flights.
* **[Mission Generation]** Added an option to fast-forward mission generation until the point of first contact (WIP).
* **[Mission Generation]** Added performance option to not cull IADS when culling would affect how mission is played at target area.
* **[Mission Generation]** Reworked the ground object generation which now uses a new layout system
* **[Mission Generation]** Added information about the modulation (AM/FM) of the assigned frequencies to the kneeboard and assign AM modulation instead of FM for JTAC.
* **[Mission Generation]** Added ice halos.
* **[Mission Generation]** Adjusted wind speeds. Wind speeds at high altitude are generally higher now.
* **[Mission Generation]** Added turbulence. Higher in Summer and Winter, also higher at day time than at nighttime.
* **[Modding]** Updated UH-60L mod version support to 1.3.1
* **[Modding]** Updated the High Digit SAMs implementation and added the HQ-2 as well as the upgraded SA-2 and SA-3 Launchers from the mod. Threat range circles will now also be displayed correctly.
* **[Modding]** Theater information such as climate properties is now moddable.
* **[Modding]** Allow campaign designers to define default values for the economy settings (starting budget and multiplier).
* **[Modding]** Campaigns can now optionally define their start time by including a time in the `recommended_start_date` field. There is not currently a way to override the start time in the UI.
* **[Plugins]** Allow full support of the SkynetIADS plugin with all advanced features (connection nodes, power sources, command centers) if campaign supports it.
* **[Plugins]** Added support for the CTLD script by ciribob with many possible customization options and updated the JTAC Autolase to the CTLD included script.
* **[UI]** Added options to the loadout editor for setting properties such as HMD choice.
* **[UI]** Added separate images for the different carrier types.
* **[UI]** Add Accept/Reset buttons to Air Wing Configurator screen.
## Fixes
* **[Engine]** Fixed issue that prevented some weapon types like torpedoes from being recognized.
* **[Flight Planning]** Fixed a miscalculation of waypoint TOTs that would require time travel.
* **[Loadouts]** Improved the range of the F-16 CAS loadout by adding bags.
* **[Mission Generation]** AAA ground units now spawn correctly at the frontline
* **[Mission Generation]** Fixed SA-13 incorrectly created as SA-8 Loading Unit which will not be spawned in the generated mission.
* **[Mission Generation]** Fixed adding additional mission types for a squadron causing error messages when the mission type is not supported by the aircraft type by default
* **[Mission Generation]** Fixed an issue where SEAD/DEAD/BAI flights fired all missiles / bombs against a single unit in a group instead of targeting the whole group.
* **[Mission Generation]** Fixed an issue which generated the helipads at FARPs incorrectly and placed the helicopters within each other.
* **[Mission Generation]** Fixed an issue with SEAD missions flown by the AI when using the Skynet Plugin and anti-radiation missiles (ARM). The AI now correctly engages the SAM when it comes alive instead of diving into it.
* **[Mission Generation]** Fixed generation issue that would cause AI helicopters to get stuck after taking off from a FARP.
* **[Mission Generation]** Fixed mission scripting error caused by control points with apostrophes in their names, such as Tha'lah.
* **[Modding]** Campaigns that used quad zones for scenery targets will no longer load. Only circular zones were ever supported, but an implementation quirk allowed them to load in a way that would misbehave. A "No white triggerzones found" message during campaign generation is the sign of a broken campaign.
* **[Modding]** Loadouts with invalid weapons (typically new DCS weapons not yet available in Liberation) will be ignored rather than causing an error.
* **[Squadrons]** Fixed issue in air wing configuration that would allow squadrons to be created with no home base if no base was available.
* **[Squadrons]** Helicopter squadrons can no longer be assigned to FOBs that are not FARPs.
* **[UI]** Add vanilla theme weather and time of day icons
* **[UI]** Disable player slots for non-flyable aircraft.
* **[UI]** Fixed and issue where the liberation main exe was still running after application close.
# 5.2.1
## Fixes
* **[Mission Generation]** Work around DCS 2.8 bug preventing the AI from leaving their hold point.
# 5.2.0
Saves from 5.1.0 are compatible with 5.2.0
## Features/Improvements
* **[Engine]** Support for DCS 2.7.11.21408, including the new Apache AH-64D and the Syria map extension
* **[Mission Generation]** Improved FARP Helipad handling and creation (now includes windsocks)
* **[Modding]** Add UH-60L mod support
* **[Modding]** Updated Community A-4E-C mod version support to 2.0.0 release. Version 1.4.2 is no longer compatible, unless the mod default loadouts are deleted/modified.
* **[Modding]** Updated JAS-39-C mod support for v1.8.0-beta
* **[Campaign]** Peace Spring, Vectron's Claw, Vegas Nerve, Scenic Route 2 campaign update
* **[Campaign]** Added Tripoint Hostility campaign by Fuzzle
* **[Campaign]** Add 3 new campaigns from Sith1144
## Fixes
* **[Mission Generation]** Fixed incorrect SA-5 and NASAMS threat range when TR destroyed. It will not count as threat anymore when the TR is dead.
* **[Mission Generation]** Fixed "Max Threat Range" error
* **[Mission Generation]** Fix unculled zones not updating when needed
* **[Mission Planner]** Now allows squadron transfers to control points where the number of free slots matches exactly the expected size of the transferring squadron next turn.
* **[Data]** Removed Fw 190 A-8 and D-9 from Germany 1940 and 1942 faction list for historical accuracy.
* **[Data]** Updated Loadouts for Tornado GR4, F-15E and F-16C
* **[Data]** Corrected some unit data
* **[UI]** Fixed various UI issues (for example Scaling and HighDPI)
* **[UI]** Typhoon GR4 and IDS images
# 5.1.0
Saves from 5.0.0 are compatible with 5.1.0
## Features/Improvements
* **[Engine]** Support for DCS 2.7.9.17830 and newer, including the HTS and ECM pod.
* **[Campaign]** Add option to manually add and remove squadrons and different aircraft type in the new game wizard / air wing configuration dialog.
* **[Mission Generation]** Add Option to enforce the Easy Communication setting for the mission
* **[Mission Generation]** Add Option to select between only night missions, day missions or any time (default).
* **[Modding]** Add F-104 mod support
## Fixes
* **[Campaign]** Fixed some minor issues in campaigns which generated error messages in the log.
* **[Campaign]** Changed the way how map object / scenery kills where tracked. This fixes issues with kill recognition after map updates from ED which change the object ids and therefore prevent correct kill recognition.
* **[Mission Generation]** Fixed incorrect radio specification for the AN/ARC-222.
* **[Mission Generation]** Fixed mission scripting error when using a dedicated server.
* **[Mission Generation]** Fixed an issue where empty convoys lead to an index error when a point capture made a pending transfer of units not completable anymore.
* **[Mission Generation]** Corrected Viggen FR22 & FR24 preset channels for the DCS 2.7.9 update
* **[Mission Generation]** Fixed the SA-5 Generator to use the P-19 FlatFace SR as a Fallback radar if the faction does not have access to the TinShield SR.
* **[UI]** Enable / Disable the settings, save and stats actions if no game is loaded to prevent an error as these functions can only be used on a valid game.
* **[UI]** Added missing icons for Tornado GR4, and Tornado IDS.
# 5.0.0
Saves from 4.x are not compatible with 5.0.
## Features/Improvements
* **[Campaign]** Weather! Theaters now experience weather that is more realistic for the region and its current season. For example, Persian Gulf will have very hot, sunny summers and Marianas will experience lots of rain during fall. These changes affect pressure, temperature, clouds and precipitation. Additionally, temperature will drop during the night, by an amount that is somewhat realistic for the region.
* **[Campaign]** Weapon data such as fallbacks and introduction years is now moddable. Due to the new architecture to support this, the old data was not automatically migrated.
* **[Campaign]** Era-restricted loadouts will now skip LGBs when no TGP is available in the loadout. This only applies to default loadouts; buddy-lasing can be coordinated with custom loadouts.
* **[Campaign]** FOBs control point can have FARP/helipad slot and host helicopters. To enable this feature on a FOB, add "Invisible FARP" statics objects near the FOB location in the campaign definition file.
* **[Campaign]** Squadrons now have a home base and will not operate out of other bases. See https://github.com/dcs-liberation/dcs_liberation/issues/1145 for status.
* **[Campaign]** Aircraft now belong to squadrons rather than bases to support squadron location transfers.
* **[Campaign]** Skipped turns are no longer counted as defeats on front lines.
* **[Campaign AI]** Overhauled campaign AI target prioritization.
* **[Campaign AI]** Player front line stances can now be automated. Improved stance selection for AI.
* **[Campaign AI]** Reworked layout of hold, join, split, and ingress points. Should result in much shorter flight plans in general while still maintaining safe join/split/hold points.
* **[Campaign AI]** Auto-planning mission range limits are now specified per-aircraft. On average this means that longer range missions will now be plannable. The limit only accounts for the direct distance to the target, not the path taken.
* **[Campaign AI]** Transport aircraft will now be bought only if necessary at control points which can produce ground units and are capable to operate transport aircraft.
* **[Campaign AI]** Aircraft will now only be automatically purchased or assigned at appropriate bases. Naval aircraft will default to only operating from carriers, Harriers will default to LHAs and shore bases, helicopters will operate from anywhere. This can be customized per-squadron.
* **[Engine]** Support for DCS 2.7.7.14727 and newer, including support for F-16 CBU-105s, SA-5s, and the Forrestal.
* **[Kneeboard]** Minimum required fuel estimates have been added to the kneeboard for aircraft with supporting data (currently only the Hornet and Viper).
* **[Kneeboard]** QNH (pressure MSL) and temperature have been added to the kneeboard.
* **[Mission Generation]** EWRs are now also headed towards the center of the conflict
* **[Mission Generation]** FACs can now use FC3 compatible laser codes. Note that this setting is global, not per FAC.
* **[Modding]** Can now install custom campaigns to <DCS saved games>/Liberation/Campaigns instead of the Liberation install directory.
* **[Modding]** Campaigns can now define a default start date.
* **[Modding]** Campaigns now specify the squadrons that are present in the campaign, their roles, and their starting bases. Players can customize this at game start but the campaign will choose the defaults.
* **[New Game Wizard]** Can now customize the player's air wing before campaign start to disable, relocate, or rename squadrons.
* **[Plugins]** Updated SkynetIADS to 2.4.0 (adds SA-5 support).
* **[UI]** Sell Button for aircraft will be disabled if there are no units available to be sold or all are already assigned to a mission
* **[UI]** Enemy aircraft inventory now viewable in the air wing menu.
## Fixes
* **[Campaign]** Naval control points will no longer claim ground objectives during campaign generation and prevent them from spawning.
* **[Campaign]** Units aboard sunk cargo ships will now have their losses tracked properly.
* **[Mission Generation]** Mission results and other files will now be opened with enforced utf-8 encoding to prevent an issue where destroyed ground units were untracked because of special characters in their names.
* **[Mission Generation]** Fixed generation of landing waypoints so that the AI obeys them.
* **[Mission Generation]** AI carrier aircraft with a start time of T+0 will now start at T+1s to avoid traffic jams.
* **[Mission Generation]** Fixed cases of unused aircraft not being spawned at airfields as soon as any airport filled up.
* **[Mission Generation]** Fixed cases with multiple client flights of the same airframe all received the same preset channels.
* **[Mission Generation]** F-14A is now generated with stored alignment.
* **[Mission Generation]** Su-33s set to cold or warm start on the Kuznetsov will always be generated as runway starts to avoid the AI getting stuck.
* **[Mission Generation]** Fixed AI not receiving anti-ship tasks against carriers and LHAs.
* **[Mods]** Fixed broken A-4 support causing no weapons to be available.
* **[UI]** Selling of Units is now visible again in the UI dialog and shows the correct amount of sold units
* **[UI]** Fixed bug where an incompatible campaign could be generated if no action is taken on the campaign selection screen.
# 4.1.1
Saves from 4.1.0 are compatible with 4.1.1.
## Fixes
* **[Campaign]** Fixed broken support for Mariana Islands map.
* **[Mission Generation]** Fix SAM sites pointing towards the center of the conflict.
* **[Flight Planning]** No longer using Su-34 for CAP missions.
# 4.1.0
Saves from 4.0.0 are compatible with 4.1.0.
## Features/Improvements
* **[Campaign]** Air defense sites now generate a fixed number of launchers per type.
* **[Campaign]** Added support for Mariana Islands map.
* **[Campaign AI]** Adjustments to aircraft selection priorities for most mission types.
* **[Engine]** Support for DCS 2.7.4.9632 and newer, including the Marianas map, F-16 JSOWs, NASAMS, and Tin Shield EWR.
* **[Flight Planning]** CAP patrol altitudes are now set per-aircraft. By default the altitude will be set based on the aircraft's maximum speed.
* **[Flight Planning]** CAP patrol speeds are now set per-aircraft to be more suitable/sensible. By default the speed will be set based on the aircraft's maximum speed.
* **[Mission Generation]** Improvements for better support of the Skynet Plugin and long range SAMs are now acting as EWR
* **[Mission Generation]** SAM sites are now headed towards the center of the conflict
* **[Mods]** Support for latest version of Gripen mod. In-progress campaigns may need to re-plan Gripen flights to pick up updated loadouts.
* **[Plugins]** Increased time JTAC Autolase messages stay visible on the UI.
* **[Plugins]** Updated SkynetIADS to 2.2.0 (adds NASAMS support).
* **[UI]** Added ability to take notes and have those notes appear as a kneeboard page.
* **[UI]** Hovering over the weather information now dispalys the cloud base (meters and feet).
* **[UI]** Google search link added to unit information when there is no information provided.
* **[UI]** Control point name displayed with ground object group name on map.
* **[UI]** Buy or Replace will now show the correct price for generated ground objects like sams.
* **[UI]** Improved logging for frontline movement to be more descriptive about what happened and why.
* **[UI]** Brought ruler map module into source, which should fix file integrity issues with the module.
## Fixes
* **[Campaign]** Fixed the Silkworm generator to include launchers and not all radars.
* **[Data]** Fixed Introduction dates for targeting pods (ATFLIR and LITENING were both a few years too early).
* **[Data]** Removed SA-10 from Syria 2011 faction.
* **[Economy]** EWRs can now be bought and sold for the correct price and can no longer be used to generate money
* **[Flight Planning]** Helicopters are now correctly identified, and will fly ingress/CAS/BAI/egress and similar at low altitude.
* **[Flight Planning]** Fixed potential issue with angles > 360° or < 0° being generated when summing two angles.
* **[Mission Generation]** The lua data for other plugins is now generated correctly
* **[Mission Generation]** Fixed problem with opfor planning missions against sold ground objects like SAMs
* **[Mission Generation]** The legacy always-available tanker option no longer prevents mission creation.
* **[Mission Generation]** Prevent the creation of a transfer order with 0 units for a rare situtation when a point was captured.
* **[Mission Generation]** Planned transfers which will be impossible after a base capture will no longer prevent the mission result submit.
* **[Mission Generation]** Fix occasional KeyError preventing mission generation when all units of the same type in a convoy were killed.
* **[Mission Generation]** Fix for AAA Flak generator using Opel Blitz preventing the mission from being generated because duplicate unit names were used.
* **[Mission Generation]** Fixed a potential bug with laser code generation where it would generate invalid codes.
* **[UI]** Statistics window tick marks are now always integers.
* **[UI]** Statistics window now shows the correct info for the turn
* **[UI]** Toggling custom loadout for an aircraft with no preset loadouts no longer breaks the flight.
# 4.0.0
Saves from 3.x are not compatible with 4.0.
## Features/Improvements
* **[Engine]** Support for DCS 2.7.2.7910.1 and newer, including Cyprus, F-16 JDAMs, and the Hind.
* **[Campaign]** Squadrons now (optionally, off by default) have a maximum size and killed pilots replenish at a limited rate.
* **[Campaign]** Added an option to disable levelling up of AI pilots.
* **[Campaign]** Added Russian Intervention 2015 campaign on Syria, for a small and somewhat realistic Russian COIN scenario.
* **[Campaign]** Added Operation Atilla campaign on Syria, for a reasonably large invasion of Cyprus scenario.
* **[Campaign AI]** AI will plan Tanker flights.
* **[Campaign AI]** Removed max distance for AEW&C auto planning.
* **[Economy]** Adjusted prices for aircraft to balance out some price inconsistencies.
* **[Factions]** Added more tankers to factions.
* **[Flight Planner]** Added ability to plan Tankers.
* **[Modding]** Campaign format version is now 7.0 to account for DCS map changes that made scenery strike targets incompatible with existing campaigns.
* **[Mods]** Added support for the Gripen mod.
* **[Mods]** Removes MB-339PAN support, as the mod is now deprecated and no longer works with DCS 2.7+.
* **[Mission Generation]** Added support for "Neutral Dot" label options.
* **[New Game Wizard]** Mods are now selected via checkboxes in the new game wizard, not as separate factions.
* **[UI]** Ctrl click and shift click now buy or sell 5 or 10 units respectively.
* **[UI]** Multiple waypoints can now be deleted simultaneously if multiple waypoints are selected.
* **[UI]** Carriers and LHAs now match the colour of airfields, and their destination icons are translucent.
* **[UI]** Updated intel box text for first turn.
* **[UI]** Base Capture Cheat is now usable at all bases and can also be used to transfer player-owned bases to OPFOR.
* **[UI]** Pass Turn button is relabled as "Begin Campaign" on Turn 0.
* **[UI]** Added a ruler to the map.
* **[UI]** Liberation now saves games to `<DCS user directory>/Liberation/Saves` by default to declutter the main directory.
## Fixes
* **[Campaign AI]** Fix procurement for factions that lack some unit types.
* **[Campaign AI]** Fix auto purchase of aircraft for factions that have no transport aircraft.
* **[Campaign AI]** Fix refunding of pending aircraft purchases when a side has no factory available.
* **[Mission Generation]** Fixed problem with mission load when control point name contained an apostrophe.
* **[Mission Generation]** Fixed EWR group names so they contribute to Skynet again.
* **[Mission Generation]** Fixed duplicate name error when generating convoys and cargo ships when creating manual transfers after loading a game.
* **[Mission Generation]** Fixed empty convoys not being disbanded when all units are killed/removed.
* **[Mission Generation]** Fixed player losing frontline progress when skipping from turn 0 to turn 1.
* **[Mission Generation]** Fixed issue where frontline would only search to the right for valid locations.
* **[UI]** Made non-interactive map elements less obstructive.
* **[UI]** Added support for Neutral Dot difficulty label
* **[UI]** Clear skies at night no longer described as "Sunny" by the weather widget.
* **[UI]** Removed ability to buy (useless) ground units at carriers and LHAs.
* **[UI]** Fixed enable/disable of buy/sell buttons.
* **[UI]** EWRs now appear in the custom waypoint list.
# 3.0.0
Saves from 2.5 are not compatible with 3.0.
## Features/Improvements
* **[Campaign]** Ground units can now be transferred by road, airlift, and cargo ship. See https://github.com/dcs-liberation/dcs_liberation/wiki/Unit-Transfers for more information.
* **[Campaign]** Ground units can no longer be sold. To move units to a new location, transfer them.
* **[Campaign]** Ground units must now be recruited at a base with a factory and transferred to their destination. When buying units in the UI, the purchase will automatically be fulfilled at the closest factory, and a transfer will be created on the next turn.
* **[Campaign]** Non-control point FOBs will no longer spawn.
* **[Campaign]** Added squadrons and pilots. See https://github.com/dcs-liberation/dcs_liberation/wiki/Squadrons-and-pilots for more information.
* **[Campaign]** Capturing a base now depopulates all of its attached objectives with units: air defenses, EWRs, ships, armor groups, etc. Buildings are captured.
* **[Campaign]** Ammunition Depots determine how many ground units can be deployed on the frontline by a control point.
* **[Campaign AI]** AI now considers Ju-88s for CAS, strike, and DEAD missions.
* **[Campaign AI]** AI planned AEW&C missions will now be scheduled ASAP.
* **[Campaign AI]** AI now considers the range to the SAM's threat zone rather than the range to the SAM itself when determining target priorities.
* **[Campaign AI]** Auto purchase of ground units will now maintain unit composition instead of buying randomly. The unit composition is predefined.
* **[Campaign AI]** Auto purchase will aim to purchase enough ground units to support the frontline, plus 30% reserve units.
* **[Campaign AI]** Auto purchase will now adjust its air/ground balance to favor whichever is under-funded.
* **[Flight Planner]** Desired mission length is now configurable (defaults to 60 minutes). A BARCAP will be planned every 30 minutes. Other packages will simply have their takeoffs spread out or compressed such that the last flight will take off around the mission end time.
* **[Flight Planner]** Flight plans now include bullseye waypoints.
* **[Flight Planner]** Differentiated SEAD and SEAD escort. SEAD is tasked with suppressing the package target, SEAD escort is tasked with protecting the package from all SAMs along its route.
* **[Flight Planner]** Planned airspeed increased to 0.85 mach for supersonic airframes and 85% of max speed for subsonic.
* **[Flight Planner]** Taxi time estimation for airfields increased from 5 minutes to 8 minutes.
* **[Flight Planner]** Reduce expected error margin for flight plans from 10% to 5%.
* **[Flight Planner]** SEAD flights are scheduled one minute ahead of the package's TOT so that they can suppress the site ahead of the strike.
* **[Flight Planner]** Automatic ATO generation for the player's coalition can now be disabled in the settings.
* **[Payloads]** AI flights for most air to ground mission types (CAS excluded) will have their guns emptied to prevent strafing fully armed and operational battle stations. Gun-reliant airframes like A-10s and warbirds will keep their bullets.
* **[Kneeboard]** ATC table overflow alleviated by wrapping long airfield names and splitting ATC frequency and channel into separate rows.
* **[UI]** Overhauled the map implementation. Now uses satellite imagery instead of low res map images. Display options have moved from the toolbar to panels in the map.
* **[UI]** Campaigns generated for an older or newer version of the game will now be marked as incompatible. They can still be played, but bugs may be present.
* **[UI]** DCS loadouts are now selectable in the loadout setup menu.
* **[UI]** Added global aircraft inventory view under Air Wing dialog.
* **[UI]** Base menu now shows information about ground unit deployment limits.
* **[Modding]** Campaigns now choose locations for factories to spawn.
* **[Modding]** Campaigns now choose locations for ammunition depots to spawn.
* **[Modding]** Campaigns now use map structures as strike targets.
* **[Modding]** Campaigns may now set *any* objective type to be a required spawn rather than random chance. Support for random objective generation was removed.
* **[Modding]** Campaigns may now place AAA objectives.
* **[Modding]** Can now install custom factions to <DCS saved games>/Liberation/Factions instead of the Liberation install directory.
* **[Performance Settings]** Added a settings to lower the number of smoke effects generated on frontlines. Lowered default settings for frontline smoke generators, so less smoke should be generated by default.
* **[Configuration]** Liberation preferences (DCS install and save game location) are now saved to `%LOCALAPPDATA%/DCSLiberation` to prevent needing to reconfigure each new install.
* **[Skynet]** Updated to 2.1.0.
## Fixes
* **[Campaign AI]** Fix purchase of aircraft by priority (the faction's list was being used as the priority list rather than the game's).
* **[Campaign AI]** Fixed bug causing AI to over-purchase cheap aircraft.
* **[Campaign AI]** Auto planner will no longer attempt to plan missions for which the faction has no compatible aircraft.
* **[Campaign AI]** Stop purchasing aircraft after the first unaffordable package to attempt to complete more packages rather than filling airfields with cheap escorts that will never be used.
* **[Campaign]** Fixed bug where offshore strike locations were being used to spawn ship objectives.
* **[Campaign]** EWR sites are now purchasable.
* **[Flight Planner]** AI strike flight plans now include the correct target actions for building groups.
* **[Flight Planner]** AI BAI/DEAD/SEAD flights now have tasks to attack all groups at the target location, not just the primary group (for multi-group SAM sites).
* **[Flight Planner]** Fixed some contexts where damaged runways would be used. Destroying a carrier will no longer break the game.
# 2.5.1
## Features/Improvements
* **[UI]** Engagement ranges are now displayed by default.
* **[UI]** Engagement range display generalized to work for all patrolling flight plans (BARCAP, TARCAP, and CAS).
* **[Flight Planner]** Front lines no longer project threat zones to avoid pushing BARCAPs back so much. TARCAPs will be forcibly planned but strike packages will not route around front lines even if it is reasonable to do so.
## Fixes
* **[Campaigns]** EWRs associated with a base will now only be generated near the base.
* **[Flight Planner]** Fixed error when generating AEW&C flight plans in campaigns with no front lines.
# 2.5.0
Saves from 2.4 are not compatible with 2.5.
## Features/Improvements
* **[Engine]** DCS 2.7 Support
* **[UI]** Improved FOB menu, added a custom banner, and do not display aircraft recruitment menu
* **[Flight Planner]** Added AEW&C missions. (by siKruger)
* **[Kneeboard]** Added dark kneeboard option (by GvonH)
* **[Campaigns]** Multiple EWR sites may now be generated, and EWR sites may be generated outside bases (by SnappyComebacks)
* **[Mission Generation]** Cloudy and rainy (but not thunderstorm) weather will use the cloud presets from DCS 2.7.
* **[Plugins]** Added LotATC export plugin (by drsoran)
* **[Plugins]** Added Splash Damage Plugin (by Wheelijoe)
* **[Loadouts]** Replaced Litening with ATFLIR for all default F/A-18C loadouts.
## Fixes
* **[Flight Planner]** Front lines now project threat zones, so TARCAP/escorts will not be pruned for flights near the front. Packages may also route around the front line when practical.
* **[Flight Planner]** Fixed error when planning BAI at SAMs with dead subgroups.
* **[Flight Planner]** Mig-19 was not allowed for CAS roles fixed
* **[Flight Planner]** Increased size of navigation planning area to avoid plannign failures with distant waypoints.
* **[Flight Planner]** Fixed UI refresh when unchecking the "default loadout" box in the loadout editor.
* **[Objective names]** Fixed typos in objective name : ARMADILLLO -> ARMADILLO (by SnappyComebacks)
* **[Payloads]** F-86 Sabre was missing a custom payload
* **[Payloads]** Added GAR-8 period restrictions (by Mustang-25)
* **[Campaign]** Date now progresses.
* **[Campaign]** Added game over message when a coalition runs out of functioning airbases.
* **[Mission Generation]** Fixed "invalid face handle" error in kneeboard generation that occurred on some machines.
## Regressions
* **[Mod Support]** Stopped support for 2.5.5 Rafale Mode, and removed factions that were using it
* **[Mod Support]** Su-57 mod support might be out of date
# 2.4.3
## Features/Improvements
* **[New Game Wizard]** Added the possibility to setup custom start date
## Fixes
* **[Mods]** Updated C-130J mod data to version 6.4
* **[Mods]** Updated F-22A mod to latest version
# 2.4.2
## Features/Improvements
* **[Factions]** Introduction dates and fallback weapons added for US, Russian, UK, and French weapons. Huge thanks to @TheCandianVendingMachine for the massive amount of data entry!
* **[Campaigns]** Added 1995 start dates.
## Fixes
* **[Economy]** Pending ground unit purchases will also be transferred when a connected base is captured.
* **[UI]** Fixed rounding of budget in recruitment menu.
# 2.4.1
## Fixes
* **[Units]** Fixed syntax error with the SH-60B payload file.
* **[Culling]** Missile sites generate reasonably sized non-cull zones rather than 100km ones.
* **[UI]** Budget display is also now rounded to 2 decimal places.
* **[UI]** Fixed some areas where the old, non-pretty name was displayed to users.
# 2.4.0
Saves from 2.3 are not compatible with 2.4.
## Highlights
* Improved flight plan generation to avoid loitering in or traveling through threatened areas when practical.
* Improved AI aircraft purchasing behavior.
* Era-restricted weapons (work in progress).
* Tons of UI polish.
* Rebalanced economy to keep opfor competitive over the course of the game.
## Features/Improvements
* **[Flight Planner]** Air-to-air and SEAD escorts will no longer be automatically planned for packages that are not in range of threats.
* **[Flight Planner]** Non-custom flight plans will now navigate around threat areas en route to the target area when practical.
* **[Flight Planner]** Flight plans along front lines now ensure that the race track start is closer to the departure airfield than the race track end.
* **[Campaign AI]** Auto-purchase now prefers airfields that are not within range of the enemy.
* **[Campaign AI]** Auto-purchase now prefers the best aircraft for the task, but will attempt to maintain some variety.
* **[Campaign AI]** Opfor now sells off odd aircraft since they're unlikely to be used.
* **[Campaign AI]** Multiple rounds of CAP will be planned (roughly 90 minutes of coverage). Default starting budget has increased to account for the increased need for aircraft.
* **[Mission Generator]** Multiple groups are created for complex SAM sites (SAMs with additional point defense or SHORADS), improving Skynet behavior.
* **[Mission Generator]** Default start type can now be chosen in the settings. This replaces the non-functional "AI Parking Start" option. **Selecting any type other than cold will break OCA/Aircraft missions.**
* **[Cheat Menu]** Added ability to toggle base capture and frontline advance/retreat cheats.
* **[Skynet]** Updated to 2.0.1.
* **[Skynet]** Point defenses are now configured to remain on to protect the site they accompany.
* **[Hercules]** Updated the Hercules Cargo list file.
* **[Balance]** Opfor now gains income using the same rules as the player, significantly increasing their income relative to the player for most campaigns.
* **[Balance]** Units now retreat from captured bases when able. Units with no retreat path will be captured and sold.
* **[Economy]** FOBs generate only $10M per turn (previously $20M like airbases).
* **[Economy]** Carriers and off-map spawns generate no income (previously $20M like airbases).
* **[Economy]** Sales of aircraft and ground vehicles can now be cancelled before the next turn begins.
* **[UI]** Multi-SAM objectives now show threat and detection rings per group.
* **[UI]** New icon for AA sites with no active threat.
* **[UI]** Unit names are now prettier and more accurate, and can now be set per-country for added historical flavour.
* **[UI]** Default loadout is now shown for flights with no custom loadout selected.
* **[UI]** Aircraft for a new flight are now only selectable if they match the task type for that flight.
* **[UI]** WIP - There is now a unit info button for each unit in the recruitment list, that should help newer players learn what each unit does.
* **[UI]** Docs for time-on-target and creating new theaters/factions/loadouts are now linked in the UI at the appropriate places.
* **[UI]** ASAP is now a checkbox rather than a button. Enabling this will disable the TOT selector but changes to the package structure will automatically re-ASAP the package.
* **[UI]** Arrival airfield is now shown in the flight list if it differs from the departure airfield.
* **[UI]** Start type can now be selected when creating a flight.
* **[UI]** Arrival and divert airfields can be edited after the flight is created.
* **[Factions]** Added option for date-based loadout restriction. Active radar homing missiles are handled, patches welcome for the other thousand weapons.
* **[Factions]** Added Poland 2010 faction.
* **[Factions]** Added Greece 2005 faction.
* **[Factions]** Added Iran 1988 faction.
* **[Units]** Support for E-2 Hawkeye, SH-60B Seahawk, S-3B Viking (thanks to awinterquest) and SpGH Dana - these are now being used by appropriate factions.
* **[Culling]** Missile sites are no longer culled.
* **[Campaigns]** Added campaign "Black Sea Lite" by Starfire
* **[Campaigns]** Added campaign "Exercise Vegas Nerve" by Starfire
* **[New game Wizard]** The theater page is now the first page of the campaign wizard, recommended factions will be selected automatically on the faction selection page
* **[New game Wizard]** Added information text about the selected campaign performance.
* **[Mod Support]** Added support for High Digit SAMs mod 1.4.0
* **[Mod Support]** Added SAMs sites generator : KS19Generator, SA10BGenerator, SA12Generator, SA17Generator, SA20Generator, SA20BGenerator, SA23Generator
## Fixes
* **[Hercules]** Updated the default Hercules radio frequency.
* **[Economy]** Pending unit orders at captured bases will be refunded.
* **[UI]** Carrier group SAM threat rings now move with the carrier.
* **[UI]** Base intel menu no longer compresses text, and is now scrollable.
* **[UI]** Edit Flight window is now dynamically sized to adapt to the width of waypoint names, so they no longer get truncated.
* **[UI]** Budget income display is now rounded to 2 decimal places.
* **[UI]** Fixed incorrect income per turn displayed for strike target tooltip.
* **[Factions]** USA with C-130 faction now links to the required mod.
* **[Campaign]** Fixed issue where destroyed buildings would sometimes not count as destroyed and thus respawn.
* **[Campaign]** Fixed issue where destroyed runways were not registered.
* **[Units]** J-11A is no longer spawned with empty loadout.
* **[Units]** F-14B is no longer spawned with empty loadout for fighter sweep tasks.
* **[Units]** Pyotr Velikiy cruiser has been removed for now as it's nearly unkillable.
* **[Units]** Submarines have been removed for now as they aren't wholly functional.
* **[Units]** Fixed "FACTION ERROR : Unable to find OliverHazardPerryGroupGenerator in pydcs" error at startup.
* **[Mission Generator]** Fixed a bug where units set to Aggressive stance sometimes did not move.
* **[Mission Generator]** Flyover points for OCA/Aircraft missions are now generated correctly.
* **[Flight Planner]** Fixed not being able to create custom waypoints for buildings.
* **[Flight Planner]** Strike missions will no longer be automatically planned against SAMs.
* **[Flight Planner]** Strike missions will no longer be automatically planned against FOB structures.
# 2.3.4
## Fixes:
[Mission Generator] Mission generator would crash when generating fire missions for destroyed SCUD sites - fixed
# 2.3.3
## Features/Improvements
* **[Campaigns]** Reworked Golan Heights campaign on Syria, (Added FOB and preset locations for SAMS)
* **[Campaigns]** Added a lite version of the Golan Heights campaign
* **[Campaigns]** Reworked Syrian Civil War campaign (Added FOB and preset locations for SAMS)
* **[Campaigns]** Reworked Emirates campaign
* **[Campaigns]** AA units added to frontlines and updated all factions to include some frontline AA units.
* **[Mission Generator]** Infantry will only be generated for APC and IFV groups
* **[Mission Generator]** Infantry squads size is not randomized anymore
* **[Mission Generator]** Infantry squads can have a mortar.
* **[Mission Generator]** SCUD missiles sites will now fire on enemy controls points in range when possible
* **[Factions]** Updated Nato Desert Storm to include F-14A
* **[Factions]** Updated Iraq 1991 factions to include Zsu-57 and Mig-29A
* **[Factions]** Germany 1944, added Stug III and Stug IV
* **[Factions]** Added factions Insurgents (Hard) with better and more weapons
* **[Plugins]** [The EWRS plugin](https://github.com/Bob7heBuilder/EWRS) is now included.
* **[UI]** Added enemy intelligence summary and details window.
## Fixes:
* **[Factions]** AI would never buy artillery units for the frontline - fixed
* **[Factions]** Removed the F-111 unit from the NATO desert storm faction. (Recruiting it would cause crashes in DCS, since it is not a valid unit)
* **[Campaign]** Automatic redeployment of ground units would sometimes fail - fixed
* **[Mission Generator]** Artillery groups would retreat in the wrong direction - fixed
* **[Units]** Fixed SPG_Stryker_M1128_MGS not being in db
* **[UI]** Fixed and added many missing ground units icons
* **[UI]** Ship groups could be replaced by SAM sites in the UI, which would lead to broken mission being generated - fixed
* **[New Game Wizard]** Removed the "mid game" campaign generator option which is currently broken
* **[Mission Generator]** Empty navy groups will no longer be generated
* **[Mission Generator]** Fixed BAI, SEAD, and DEAD flights ocassionally being assigned the wrong targets.
* **[Flight Planner]** Fixed not being able to plan packages against opfor carriers
* **[UI]** Repaired SAMs no longer show as dead.
* **[UI]** Fixed not being able to manage a disbanded site after disbanding and closing the base menu.
# 2.3.2
## Features/Improvements
* **[Units]** Support for newly added BTR-82A, T-72B3
* **[Units]** Added ZSU-57 AAA sites
* **[Culling]** BARCAP missions no longer create culling exclusion zones.
* **[Flight Planner]** Improved TOT planning. Negative start times no longer occur with TARCAPs and hold times no longer affect planning for flight plans without hold points.
* **[Factions]** Added Iraq 1991 faction (thanks again to Hawkmoon!)
## Fixes:
* **[Mission Generator]** Fix mission generation error when there are too many radio frequency to setup for the Mig-21
* **[Mission Generator]** Fix ground units not moving forward
* **[Mission Generator]** Fixed assigned radio channels overlapping with beacons.
* **[Flight Planner]** Fix creation of custom waypoints.
* **[Campaigns]** Fixed many cases of SAMs spawning on the runways/taxiways in Syria Full.
# 2.3.1
## Features/Improvements
* **[UX]** Added a warning message when the player is attempting to buy more planes at an already full airbase.
* **[Campaigns]** Migrated Syria full map to new format. (Thanks to Hawkmoon)
* **[Faction]** Added NATO desert Storm faction (Thanks to Hawkmoon)
## Fixes:
* **[AI]** CAP flights will engage enemies again.
* **[Campaigns]** Fixed a missing path on the Caucasus Full Map campaign
# 2.3.0
## Features/Improvements
* **[Campaign Map]** Overhauled the campaign model
* **[Campaign Map]** Possible to add FOB as control points
* **[Campaign Map]** Added off-map spawn locations
* **[Campaign AI]** Overhauled AI recruiting behaviour
* **[Campaign AI]** Added AI procurement for Blue
* **[Campaign]** New Campaign: "Black Sea"
* **[Mission Planner]** Possible to move carrier and tarawa on the campaign map
* **[Mission Generator]** Infantry squads on frontline can have manpads
* **[Mission Generator]** Unused aircraft now spawned to allow for OCA strikes
* **[Mission Generator]** Opfor now obeys parking limits
* **[Mission Generator]** Support for Anubis C-130 Hercules mod
* **[Flight Planner]** Added fighter sweep missions.
* **[Flight Planner]** Added BAI missions.
* **[Flight Planner]** Added anti-ship missions.
* **[Flight Planner]** Differentiated BARCAP and TARCAP. TARCAP is now for hostile areas and will arrive before the package.
* **[Flight Planner]** Added OCA missions
* **[Flight Planner]** Added Alternate/divert airfields
* **[Culling]** Added possibility to include/exclude carriers from culling zones
* **[QOL]** On liberation startup, your latest save game is loaded automatically
* **[Units]** Reduced starting fuel load for C101
* **[UI]** Inform the user of the weather
* **[UI]** Added toolbar buttons to change map display settings
* **[Game]** Added new Economy options for adjusting income multipliers and starting budgets.
## Fixes :
* **[Map]** Missiles sites now have a proper icon and will not re-use the SAM sites icon
* **[Mission Generator]** Ground unit waypoints improperly set to "On Road" - fixed
* **[Mission Generator]** Target waypoints not at ground level - fixed
* **[Mission Generator]** Selected skill not applied to Helicopters - fixed
* **[Mission Generator]** Ground units do not always spawn - fixed
* **[Kneeboard]** Briefing waypoints off by one - fixed
* **[Game]** Destroyed buildings still granting budget - fixed
# 2.2.1
## Features/Improvements
# Features/Improvements
* **[Factions]** Added factions : Georgia 2008, USN 1985, France 2005 Frenchpack by HerrTom
* **[Factions]** Added map Persian Gulf full by Plob
* **[Flight Planner]** Player flights with start delays under ten minutes will spawn immediately.

26
client/.gitignore vendored
View File

@@ -1,26 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.vscode/settings.json
.vscode/tasks.json

View File

@@ -1,10 +0,0 @@
{
"configurations": [
{
"type": "pwa-chrome",
"name": "http://localhost:3000",
"request": "launch",
"url": "http://localhost:3000"
}
]
}

View File

@@ -1,76 +0,0 @@
# DCS Liberation Client
This is a React app for the front-end of DCS Liberation. It is a work in
progress that just barely implements the map. This is not useful for players
yet.
For development, set the following environment variables when launching DCS
Liberation (the Qt UI):
- `CORS_ALLOW_DEBUG_SERVER=true`
This will allow the front-end to make requests to the server, as long as the
front-end is running on http://localhost:3000.
Then, run `npm start` to start the development server. Launch the Qt UI with
`--new-map --dev` to connect the webview to the development server, or navigate
to http://localhost:3000 in your browser.
## Regenerating the API stubs
The backend uses FastAPI which exposes `/openapi.json`. This is consumed by
`@rtk-query/codegen-openapi` to automatically generate the API stubs in
`src/api/liberationApi.ts`.
If you make a change to the API surface the typescript API will need to be
regenerated. To do this, first launch Liberation (to start the backend) and run
```powershell
npm run regenerate-api
```
See https://redux-toolkit.js.org/rtk-query/usage/code-generation for more
information.
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.<br />
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br />
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.<br />
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.<br />
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br />
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).

View File

@@ -1,57 +0,0 @@
const path = require("path");
const { app, BrowserWindow } = require("electron");
const isDev = require("electron-is-dev");
const windowStateKeeper = require("electron-window-state");
function createWindow() {
let mainWindowState = windowStateKeeper({
defaultWidth: 1000,
defaultHeight: 800,
});
// Create the browser window.
const win = new BrowserWindow({
x: mainWindowState.x,
y: mainWindowState.y,
width: mainWindowState.width,
height: mainWindowState.height,
show: false,
webPreferences: {
nodeIntegration: true,
},
});
mainWindowState.manage(win);
// and load the index.html of the app.
// win.loadFile("index.html");
win.loadURL(
isDev
? "http://localhost:3000"
: `file://${path.join(__dirname, "../build/index.html")}`
);
// Open the DevTools.
if (isDev) {
win.webContents.openDevTools({ mode: "detach" });
}
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(createWindow);
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});

View File

@@ -1,12 +0,0 @@
import { ConfigFile } from "@rtk-query/codegen-openapi";
const config: ConfigFile = {
schemaFile: "http://[::1]:16880/openapi.json",
apiFile: "./src/api/baseApi.ts",
apiImport: "baseApi",
outputFile: "./src/api/_liberationApi.ts",
exportName: "_liberationApi",
hooks: true,
};
export default config;

36432
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,86 +0,0 @@
{
"name": "liberation-client",
"version": "0.1.0",
"private": true,
"main": "main.js",
"license": "LGPL-3.0-or-later",
"homepage": ".",
"dependencies": {
"@reduxjs/toolkit": "^1.8.5",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
"@types/jest": "^29.1.2",
"@types/node": "^18.8.3",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"@types/react-redux": "^7.1.24",
"axios": "^1.1.2",
"electron-window-state": "^5.0.3",
"esri-leaflet": "^3.0.8",
"leaflet": "^1.9.2",
"leaflet-ruler": "^1.0.0",
"milsymbol": "^2.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-esri-leaflet": "^2.0.1",
"react-leaflet": "^4.1.0",
"react-redux": "^8.0.4",
"redux-logger": "^3.0.6",
"typescript": "~4.8.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build && generate-license-file --input package.json --output build/NOTICE",
"regenerate-api": "rtk-query-codegen-openapi ./openapi-config.ts",
"lint": "eslint src",
"prepare": "eslint src && license-checker --onlyAllow \"MIT;Apache-2.0;CC0-1.0;BSD-3-Clause;ISC;Custom: https://github.com/tmcw/jsonlint;BSD-2-Clause;Hippocratic-2.1;BSD*;WTFPL\" --excludePrivatePackages --production",
"test": "react-scripts test",
"eject": "react-scripts eject",
"electron": "wait-on tcp:3000 && electron ."
},
"eslintConfig": {
"extends": "react-app"
},
"eslintIgnore": [
"leaflet-ruler.d.ts"
],
"prettier": {
"endOfLine": "auto"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@rtk-query/codegen-openapi": "^1.0.0",
"@trivago/prettier-plugin-sort-imports": "^3.3.0",
"@types/leaflet": "^1.8.0",
"@types/redux-logger": "^3.0.9",
"@types/websocket": "^1.0.5",
"electron": "^21.1.0",
"electron-is-dev": "^2.0.0",
"generate-license-file": "^2.0.0",
"identity-obj-proxy": "^3.0.0",
"license-checker": "^25.0.1",
"react-scripts": "5.0.1",
"ts-node": "^10.9.1",
"wait-on": "^6.0.1"
},
"jest": {
"transformIgnorePatterns": [
"node_modules/(?!(@?react-leaflet|axios)/)"
],
"moduleNameMapper": {
".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": "identity-obj-proxy"
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -1,43 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React Redux App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,25 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@@ -1,12 +0,0 @@
import App from "./App";
import { store } from "./app/store";
import { render } from "@testing-library/react";
import { Provider } from "react-redux";
test("app renders", () => {
render(
<Provider store={store}>
<App />
</Provider>
);
});

View File

@@ -1,16 +0,0 @@
import LiberationMap from "./components/liberationmap";
import useEventStream from "./hooks/useEventSteam";
import useInitialGameState from "./hooks/useInitialGameState";
function App() {
useInitialGameState();
useEventStream();
return (
<div className="App">
<LiberationMap />
</div>
);
}
export default App;

View File

@@ -1,524 +0,0 @@
import { baseApi as api } from "./baseApi";
const injectedRtkApi = api.injectEndpoints({
endpoints: (build) => ({
listControlPoints: build.query<
ListControlPointsApiResponse,
ListControlPointsApiArg
>({
query: () => ({ url: `/control-points/` }),
}),
getControlPointById: build.query<
GetControlPointByIdApiResponse,
GetControlPointByIdApiArg
>({
query: (queryArg) => ({ url: `/control-points/${queryArg.cpId}` }),
}),
controlPointDestinationInRange: build.query<
ControlPointDestinationInRangeApiResponse,
ControlPointDestinationInRangeApiArg
>({
query: (queryArg) => ({
url: `/control-points/${queryArg.cpId}/destination-in-range`,
params: { lat: queryArg.lat, lng: queryArg.lng },
}),
}),
setControlPointDestination: build.mutation<
SetControlPointDestinationApiResponse,
SetControlPointDestinationApiArg
>({
query: (queryArg) => ({
url: `/control-points/${queryArg.cpId}/destination`,
method: "PUT",
body: queryArg.body,
}),
}),
clearControlPointDestination: build.mutation<
ClearControlPointDestinationApiResponse,
ClearControlPointDestinationApiArg
>({
query: (queryArg) => ({
url: `/control-points/${queryArg.cpId}/cancel-travel`,
method: "PUT",
}),
}),
getDebugHoldZones: build.query<
GetDebugHoldZonesApiResponse,
GetDebugHoldZonesApiArg
>({
query: (queryArg) => ({
url: `/debug/waypoint-geometries/hold/${queryArg.flightId}`,
}),
}),
getDebugIpZones: build.query<
GetDebugIpZonesApiResponse,
GetDebugIpZonesApiArg
>({
query: (queryArg) => ({
url: `/debug/waypoint-geometries/ip/${queryArg.flightId}`,
}),
}),
getDebugJoinZones: build.query<
GetDebugJoinZonesApiResponse,
GetDebugJoinZonesApiArg
>({
query: (queryArg) => ({
url: `/debug/waypoint-geometries/join/${queryArg.flightId}`,
}),
}),
listFlights: build.query<ListFlightsApiResponse, ListFlightsApiArg>({
query: (queryArg) => ({
url: `/flights/`,
params: { with_waypoints: queryArg.withWaypoints },
}),
}),
getFlightById: build.query<GetFlightByIdApiResponse, GetFlightByIdApiArg>({
query: (queryArg) => ({
url: `/flights/${queryArg.flightId}`,
params: { with_waypoints: queryArg.withWaypoints },
}),
}),
getCommitBoundaryForFlight: build.query<
GetCommitBoundaryForFlightApiResponse,
GetCommitBoundaryForFlightApiArg
>({
query: (queryArg) => ({
url: `/flights/${queryArg.flightId}/commit-boundary`,
}),
}),
listFrontLines: build.query<
ListFrontLinesApiResponse,
ListFrontLinesApiArg
>({
query: () => ({ url: `/front-lines/` }),
}),
getFrontLineById: build.query<
GetFrontLineByIdApiResponse,
GetFrontLineByIdApiArg
>({
query: (queryArg) => ({ url: `/front-lines/${queryArg.frontLineId}` }),
}),
getGameState: build.query<GetGameStateApiResponse, GetGameStateApiArg>({
query: () => ({ url: `/game/` }),
}),
getTerrainZones: build.query<
GetTerrainZonesApiResponse,
GetTerrainZonesApiArg
>({
query: () => ({ url: `/map-zones/terrain` }),
}),
listUnculledZones: build.query<
ListUnculledZonesApiResponse,
ListUnculledZonesApiArg
>({
query: () => ({ url: `/map-zones/unculled` }),
}),
getThreatZones: build.query<
GetThreatZonesApiResponse,
GetThreatZonesApiArg
>({
query: () => ({ url: `/map-zones/threats` }),
}),
getNavmesh: build.query<GetNavmeshApiResponse, GetNavmeshApiArg>({
query: (queryArg) => ({
url: `/navmesh/`,
params: { for_player: queryArg.forPlayer },
}),
}),
openNewFrontLinePackageDialog: build.mutation<
OpenNewFrontLinePackageDialogApiResponse,
OpenNewFrontLinePackageDialogApiArg
>({
query: (queryArg) => ({
url: `/qt/create-package/front-line/${queryArg.frontLineId}`,
method: "POST",
}),
}),
openNewTgoPackageDialog: build.mutation<
OpenNewTgoPackageDialogApiResponse,
OpenNewTgoPackageDialogApiArg
>({
query: (queryArg) => ({
url: `/qt/create-package/tgo/${queryArg.tgoId}`,
method: "POST",
}),
}),
openTgoInfoDialog: build.mutation<
OpenTgoInfoDialogApiResponse,
OpenTgoInfoDialogApiArg
>({
query: (queryArg) => ({
url: `/qt/info/tgo/${queryArg.tgoId}`,
method: "POST",
}),
}),
openNewControlPointPackageDialog: build.mutation<
OpenNewControlPointPackageDialogApiResponse,
OpenNewControlPointPackageDialogApiArg
>({
query: (queryArg) => ({
url: `/qt/create-package/control-point/${queryArg.cpId}`,
method: "POST",
}),
}),
openControlPointInfoDialog: build.mutation<
OpenControlPointInfoDialogApiResponse,
OpenControlPointInfoDialogApiArg
>({
query: (queryArg) => ({
url: `/qt/info/control-point/${queryArg.cpId}`,
method: "POST",
}),
}),
listSupplyRoutes: build.query<
ListSupplyRoutesApiResponse,
ListSupplyRoutesApiArg
>({
query: () => ({ url: `/supply-routes/` }),
}),
listTgos: build.query<ListTgosApiResponse, ListTgosApiArg>({
query: () => ({ url: `/tgos/` }),
}),
getTgoById: build.query<GetTgoByIdApiResponse, GetTgoByIdApiArg>({
query: (queryArg) => ({ url: `/tgos/${queryArg.tgoId}` }),
}),
listAllWaypointsForFlight: build.query<
ListAllWaypointsForFlightApiResponse,
ListAllWaypointsForFlightApiArg
>({
query: (queryArg) => ({ url: `/waypoints/${queryArg.flightId}` }),
}),
setWaypointPosition: build.mutation<
SetWaypointPositionApiResponse,
SetWaypointPositionApiArg
>({
query: (queryArg) => ({
url: `/waypoints/${queryArg.flightId}/${queryArg.waypointIdx}/position`,
method: "POST",
body: queryArg.leafletPoint,
}),
}),
getIadsNetwork: build.query<
GetIadsNetworkApiResponse,
GetIadsNetworkApiArg
>({
query: () => ({ url: `/iads-network/` }),
}),
getIadsConnectionsForTgo: build.query<
GetIadsConnectionsForTgoApiResponse,
GetIadsConnectionsForTgoApiArg
>({
query: (queryArg) => ({ url: `/iads-network/for-tgo/${queryArg.tgoId}` }),
}),
}),
overrideExisting: false,
});
export { injectedRtkApi as _liberationApi };
export type ListControlPointsApiResponse =
/** status 200 Successful Response */ ControlPoint[];
export type ListControlPointsApiArg = void;
export type GetControlPointByIdApiResponse =
/** status 200 Successful Response */ ControlPoint;
export type GetControlPointByIdApiArg = {
cpId: string;
};
export type ControlPointDestinationInRangeApiResponse =
/** status 200 Successful Response */ boolean;
export type ControlPointDestinationInRangeApiArg = {
cpId: string;
lat: number;
lng: number;
};
export type SetControlPointDestinationApiResponse =
/** status 204 Successful Response */ undefined;
export type SetControlPointDestinationApiArg = {
cpId: string;
body: LatLng;
};
export type ClearControlPointDestinationApiResponse =
/** status 204 Successful Response */ undefined;
export type ClearControlPointDestinationApiArg = {
cpId: string;
};
export type GetDebugHoldZonesApiResponse =
/** status 200 Successful Response */ HoldZones;
export type GetDebugHoldZonesApiArg = {
flightId: string;
};
export type GetDebugIpZonesApiResponse =
/** status 200 Successful Response */ IpZones;
export type GetDebugIpZonesApiArg = {
flightId: string;
};
export type GetDebugJoinZonesApiResponse =
/** status 200 Successful Response */ JoinZones;
export type GetDebugJoinZonesApiArg = {
flightId: string;
};
export type ListFlightsApiResponse =
/** status 200 Successful Response */ Flight[];
export type ListFlightsApiArg = {
withWaypoints?: boolean;
};
export type GetFlightByIdApiResponse =
/** status 200 Successful Response */ Flight;
export type GetFlightByIdApiArg = {
flightId: string;
withWaypoints?: boolean;
};
export type GetCommitBoundaryForFlightApiResponse =
/** status 200 Successful Response */ LatLng[][];
export type GetCommitBoundaryForFlightApiArg = {
flightId: string;
};
export type ListFrontLinesApiResponse =
/** status 200 Successful Response */ FrontLine[];
export type ListFrontLinesApiArg = void;
export type GetFrontLineByIdApiResponse =
/** status 200 Successful Response */ FrontLine;
export type GetFrontLineByIdApiArg = {
frontLineId: string;
};
export type GetGameStateApiResponse =
/** status 200 Successful Response */ Game;
export type GetGameStateApiArg = void;
export type GetTerrainZonesApiResponse =
/** status 200 Successful Response */ MapZones;
export type GetTerrainZonesApiArg = void;
export type ListUnculledZonesApiResponse =
/** status 200 Successful Response */ UnculledZone[];
export type ListUnculledZonesApiArg = void;
export type GetThreatZonesApiResponse =
/** status 200 Successful Response */ ThreatZoneContainer;
export type GetThreatZonesApiArg = void;
export type GetNavmeshApiResponse =
/** status 200 Successful Response */ NavMesh;
export type GetNavmeshApiArg = {
forPlayer: boolean;
};
export type OpenNewFrontLinePackageDialogApiResponse =
/** status 200 Successful Response */ any;
export type OpenNewFrontLinePackageDialogApiArg = {
frontLineId: string;
};
export type OpenNewTgoPackageDialogApiResponse =
/** status 200 Successful Response */ any;
export type OpenNewTgoPackageDialogApiArg = {
tgoId: string;
};
export type OpenTgoInfoDialogApiResponse =
/** status 200 Successful Response */ any;
export type OpenTgoInfoDialogApiArg = {
tgoId: string;
};
export type OpenNewControlPointPackageDialogApiResponse =
/** status 200 Successful Response */ any;
export type OpenNewControlPointPackageDialogApiArg = {
cpId: string;
};
export type OpenControlPointInfoDialogApiResponse =
/** status 200 Successful Response */ any;
export type OpenControlPointInfoDialogApiArg = {
cpId: string;
};
export type ListSupplyRoutesApiResponse =
/** status 200 Successful Response */ SupplyRoute[];
export type ListSupplyRoutesApiArg = void;
export type ListTgosApiResponse = /** status 200 Successful Response */ Tgo[];
export type ListTgosApiArg = void;
export type GetTgoByIdApiResponse = /** status 200 Successful Response */ Tgo;
export type GetTgoByIdApiArg = {
tgoId: string;
};
export type ListAllWaypointsForFlightApiResponse =
/** status 200 Successful Response */ Waypoint[];
export type ListAllWaypointsForFlightApiArg = {
flightId: string;
};
export type SetWaypointPositionApiResponse =
/** status 204 Successful Response */ undefined;
export type SetWaypointPositionApiArg = {
flightId: string;
waypointIdx: number;
leafletPoint: LatLng;
};
export type GetIadsNetworkApiResponse =
/** status 200 Successful Response */ IadsNetwork;
export type GetIadsNetworkApiArg = void;
export type GetIadsConnectionsForTgoApiResponse =
/** status 200 Successful Response */ IadsConnection[];
export type GetIadsConnectionsForTgoApiArg = {
tgoId: string;
};
export type LatLng = {
lat: number;
lng: number;
};
export type ControlPoint = {
id: string;
name: string;
blue: boolean;
position: LatLng;
mobile: boolean;
destination?: LatLng;
sidc: string;
};
export type ValidationError = {
loc: (string | number)[];
msg: string;
type: string;
};
export type HttpValidationError = {
detail?: ValidationError[];
};
export type HoldZones = {
homeBubble: LatLng[][];
targetBubble: LatLng[][];
joinBubble: LatLng[][];
excludedZones: LatLng[][][];
permissibleZones: LatLng[][][];
preferredLines: LatLng[][];
};
export type IpZones = {
homeBubble: LatLng[][];
ipBubble: LatLng[][];
permissibleZone: LatLng[][];
safeZones: LatLng[][][];
};
export type JoinZones = {
homeBubble: LatLng[][];
targetBubble: LatLng[][];
ipBubble: LatLng[][];
excludedZones: LatLng[][][];
permissibleZones: LatLng[][][];
preferredLines: LatLng[][];
};
export type Waypoint = {
name: string;
position: LatLng;
altitude_ft: number;
altitude_reference: string;
is_movable: boolean;
should_mark: boolean;
include_in_path: boolean;
timing: string;
};
export type Flight = {
id: string;
blue: boolean;
position?: LatLng;
sidc: string;
waypoints?: Waypoint[];
};
export type FrontLine = {
id: string;
extents: LatLng[];
};
export type Tgo = {
id: string;
name: string;
control_point_name: string;
category: string;
blue: boolean;
position: LatLng;
units: string[];
threat_ranges: number[];
detection_ranges: number[];
dead: boolean;
sidc: string;
};
export type SupplyRoute = {
id: string;
points: LatLng[];
front_active: boolean;
is_sea: boolean;
blue: boolean;
active_transports: string[];
};
export type IadsConnection = {
id: string;
points: LatLng[];
node: string;
connected: string;
active: boolean;
blue: boolean;
is_power: boolean;
};
export type IadsNetwork = {
advanced: boolean;
connections: IadsConnection[];
};
export type ThreatZones = {
full: LatLng[][][];
aircraft: LatLng[][][];
air_defenses: LatLng[][][];
radar_sams: LatLng[][][];
};
export type ThreatZoneContainer = {
blue: ThreatZones;
red: ThreatZones;
};
export type NavMeshPoly = {
poly: LatLng[][];
threatened: boolean;
};
export type NavMesh = {
polys: NavMeshPoly[];
};
export type NavMeshes = {
blue: NavMesh;
red: NavMesh;
};
export type UnculledZone = {
position: LatLng;
radius: number;
};
export type Game = {
control_points: ControlPoint[];
tgos: Tgo[];
supply_routes: SupplyRoute[];
front_lines: FrontLine[];
flights: Flight[];
iads_network: IadsNetwork;
threat_zones: ThreatZoneContainer;
navmeshes: NavMeshes;
map_center?: LatLng;
unculled_zones: UnculledZone[];
};
export type MapZones = {
inclusion: LatLng[][][];
exclusion: LatLng[][][];
sea: LatLng[][][];
};
export const {
useListControlPointsQuery,
useGetControlPointByIdQuery,
useControlPointDestinationInRangeQuery,
useSetControlPointDestinationMutation,
useClearControlPointDestinationMutation,
useGetDebugHoldZonesQuery,
useGetDebugIpZonesQuery,
useGetDebugJoinZonesQuery,
useListFlightsQuery,
useGetFlightByIdQuery,
useGetCommitBoundaryForFlightQuery,
useListFrontLinesQuery,
useGetFrontLineByIdQuery,
useGetGameStateQuery,
useGetTerrainZonesQuery,
useListUnculledZonesQuery,
useGetThreatZonesQuery,
useGetNavmeshQuery,
useOpenNewFrontLinePackageDialogMutation,
useOpenNewTgoPackageDialogMutation,
useOpenTgoInfoDialogMutation,
useOpenNewControlPointPackageDialogMutation,
useOpenControlPointInfoDialogMutation,
useListSupplyRoutesQuery,
useListTgosQuery,
useGetTgoByIdQuery,
useListAllWaypointsForFlightQuery,
useSetWaypointPositionMutation,
useGetIadsNetworkQuery,
useGetIadsConnectionsForTgoQuery,
} = injectedRtkApi;

View File

@@ -1,5 +0,0 @@
import { Game } from "./liberationApi";
import { createAction } from "@reduxjs/toolkit";
export const gameLoaded = createAction<Game>("game/loaded");
export const gameUnloaded = createAction("game/unloaded");

View File

@@ -1,15 +0,0 @@
import axios from "axios";
const backendAddr =
new URL(window.location.toString()).searchParams.get("server") ??
"[::1]:16880";
export const HTTP_URL = `http://${backendAddr}/`;
export const backend = axios.create({
baseURL: HTTP_URL,
});
export const WEBSOCKET_URL = `ws://${backendAddr}/eventstream`;
export default backend;

View File

@@ -1,7 +0,0 @@
import { HTTP_URL } from "./backend";
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
export const baseApi = createApi({
baseQuery: fetchBaseQuery({ baseUrl: HTTP_URL }),
endpoints: () => ({}),
});

View File

@@ -1,8 +0,0 @@
import { LatLng } from "leaflet";
export default interface Combat {
id: string;
flight_position: LatLng | null;
target_positions: LatLng[] | null;
footprint: LatLng[][] | null;
}

View File

@@ -1,49 +0,0 @@
import { RootState } from "../app/store";
import { gameLoaded, gameUnloaded } from "./actions";
import Combat from "./combat";
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
interface CombatState {
combat: { [key: string]: Combat };
}
const initialState: CombatState = {
combat: {},
};
export const combatSlice = createSlice({
name: "combat",
initialState,
reducers: {
newCombats: (state, action: PayloadAction<Combat[]>) => {
for (const combat of action.payload) {
state.combat[combat.id] = combat;
}
},
updateCombats: (state, action: PayloadAction<Combat[]>) => {
for (const combat of action.payload) {
state.combat[combat.id] = combat;
}
},
endCombats: (state, action: PayloadAction<string[]>) => {
for (const cID of action.payload) {
delete state.combat[cID];
}
},
},
extraReducers: (builder) => {
builder.addCase(gameLoaded, (state, action) => {
state.combat = {};
});
builder.addCase(gameUnloaded, (state) => {
state.combat = {};
});
},
});
export const { newCombats, updateCombats, endCombats } =
combatSlice.actions;
export const selectCombat = (state: RootState) => state.combat;
export default combatSlice.reducer;

View File

@@ -1,44 +0,0 @@
import { RootState } from "../app/store";
import { gameLoaded, gameUnloaded } from "./actions";
import { ControlPoint } from "./liberationApi";
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
interface ControlPointsState {
controlPoints: { [key: string]: ControlPoint };
}
const initialState: ControlPointsState = {
controlPoints: {},
};
export const controlPointsSlice = createSlice({
name: "controlPoints",
initialState,
reducers: {
updateControlPoint: (state, action: PayloadAction<ControlPoint[]>) => {
for (const cp of action.payload) {
state.controlPoints[cp.id] = cp;
}
},
},
extraReducers: (builder) => {
builder.addCase(gameLoaded, (state, action) => {
state.controlPoints = action.payload.control_points.reduce(
(acc: { [key: string]: ControlPoint }, curr) => {
acc[curr.id] = curr;
return acc;
},
{}
);
});
builder.addCase(gameUnloaded, (state) => {
state.controlPoints = {};
});
},
});
export const { updateControlPoint } = controlPointsSlice.actions;
export const selectControlPoints = (state: RootState) => state.controlPoints;
export default controlPointsSlice.reducer;

View File

@@ -1,149 +0,0 @@
import { AppDispatch } from "../app/store";
import { gameUnloaded } from "./actions";
import Combat from "./combat";
import { endCombats, newCombats, updateCombats } from "./combatSlice";
import { updateControlPoint } from "./controlPointsSlice";
import {
deselectFlight,
registerFlights,
selectFlight,
unregisterFlights,
updateFlights,
updateFlightPositions,
} from "./flightsSlice";
import {
deleteFrontLine,
updateFrontLine,
} from "./frontLinesSlice";
import reloadGameState from "./gamestate";
import {
ControlPoint,
Flight,
FrontLine,
IadsConnection,
NavMesh,
Tgo,
ThreatZones,
UnculledZone,
} from "./liberationApi";
import { navMeshUpdated } from "./navMeshSlice";
import { updateTgo } from "./tgosSlice";
import { threatZonesUpdated } from "./threatZonesSlice";
import { unculledZonesUpdated } from "./unculledZonesSlice";
import { LatLng } from "leaflet";
import { updateIadsConnection, removeIadsConnection } from "./iadsNetworkSlice";
interface GameUpdateEvents {
updated_flight_positions: { [id: string]: LatLng };
new_combats: Combat[];
updated_combats: Combat[];
ended_combats: string[];
navmesh_updates: {blue: boolean, mesh: NavMesh}[];
updated_unculled_zones: UnculledZone[];
threat_zones_updated: {blue: boolean, zones: ThreatZones}[];
new_flights: Flight[];
updated_flights: Flight[];
deleted_flights: string[];
selected_flight: string | null;
deselected_flight: boolean;
updated_front_lines: FrontLine[];
deleted_front_lines: string[];
updated_tgos: Tgo[];
updated_control_points: ControlPoint[];
updated_iads: IadsConnection[];
deleted_iads: string[];
reset_on_map_center: LatLng | null;
game_unloaded: boolean;
new_turn: boolean;
}
export const handleStreamedEvents = (
dispatch: AppDispatch,
events: GameUpdateEvents
) => {
if (Object.keys(events.updated_flight_positions).length) {
dispatch(
updateFlightPositions(Object.entries(events.updated_flight_positions))
);
}
if (events.new_combats.length > 0) {
dispatch(newCombats(events.new_combats));
}
if (events.updated_combats.length > 0) {
dispatch(updateCombats(events.updated_combats));
}
if (events.ended_combats.length > 0) {
dispatch(endCombats(events.ended_combats));
}
if (Object.keys(events.navmesh_updates).length > 0) {
dispatch(navMeshUpdated(events.navmesh_updates));
}
if (events.updated_unculled_zones.length > 0) {
dispatch(unculledZonesUpdated(events.updated_unculled_zones));
}
if (Object.keys(events.threat_zones_updated).length > 0) {
dispatch(threatZonesUpdated(events.threat_zones_updated));
}
if (events.new_flights.length > 0) {
dispatch(registerFlights(events.new_flights));
}
if (events.updated_flights.length > 0) {
dispatch(updateFlights(events.updated_flights));
}
if (events.deleted_flights.length > 0) {
dispatch(unregisterFlights(events.deleted_flights));
}
if (events.deselected_flight) {
dispatch(deselectFlight());
}
if (events.selected_flight != null) {
dispatch(selectFlight(events.selected_flight));
}
if (events.updated_front_lines.length > 0) {
dispatch(updateFrontLine(events.updated_front_lines));
}
if (events.deleted_front_lines.length > 0) {
dispatch(deleteFrontLine(events.deleted_front_lines));
}
if (events.updated_tgos.length > 0) {
dispatch(updateTgo(events.updated_tgos));
}
if (events.updated_control_points.length > 0) {
dispatch(updateControlPoint(events.updated_control_points));
}
if (events.deleted_iads.length > 0) {
dispatch(removeIadsConnection(events.deleted_iads));
}
if (events.updated_iads.length > 0) {
dispatch(updateIadsConnection(events.updated_iads));
}
if (events.reset_on_map_center != null) {
reloadGameState(dispatch);
}
if (events.game_unloaded) {
dispatch(gameUnloaded());
}
if (events.new_turn) {
reloadGameState(dispatch, true);
}
};

View File

@@ -1,89 +0,0 @@
import { RootState } from "../app/store";
import { gameLoaded, gameUnloaded } from "./actions";
import { Flight } from "./liberationApi";
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
import { LatLng } from "leaflet";
interface FlightsState {
flights: { [id: string]: Flight };
selected: string | null;
}
const initialState: FlightsState = {
flights: {},
selected: null,
};
export const flightsSlice = createSlice({
name: "flights",
initialState,
reducers: {
registerFlights: (state, action: PayloadAction<Flight[]>) => {
for (const flight of action.payload) {
if (flight.id in state.flights) {
console.log(`Overriding flight with ID: ${flight.id}`);
}
state.flights[flight.id] = flight;
}
},
unregisterFlights: (state, action: PayloadAction<string[]>) => {
for (const id of action.payload) {
delete state.flights[id];
}
},
updateFlights: (state, action: PayloadAction<Flight[]>) => {
for (const flight of action.payload) {
state.flights[flight.id] = flight;
}
},
deselectFlight: (state) => {
state.selected = null;
},
selectFlight: (state, action: PayloadAction<string>) => {
state.selected = action.payload;
},
updateFlightPositions: (
state,
action: PayloadAction<[string, LatLng][]>
) => {
for (const [id, position] of action.payload) {
state.flights[id].position = position;
}
},
},
extraReducers: (builder) => {
builder.addCase(gameLoaded, (state, action) => {
state.selected = null;
state.flights = action.payload.flights.reduce(
(acc: { [key: string]: Flight }, curr) => {
acc[curr.id] = curr;
return acc;
},
{}
);
});
builder.addCase(gameUnloaded, (state) => {
state.selected = null;
state.flights = {};
});
},
});
export const {
registerFlights,
unregisterFlights,
updateFlights,
deselectFlight,
selectFlight,
updateFlightPositions,
} = flightsSlice.actions;
export const selectFlights = (state: RootState) => state.flights;
export const selectSelectedFlightId = (state: RootState) =>
state.flights.selected;
export const selectSelectedFlight = (state: RootState) => {
const id = state.flights.selected;
return id ? state.flights.flights[id] : null;
};
export default flightsSlice.reducer;

View File

@@ -1,50 +0,0 @@
import { RootState } from "../app/store";
import { gameLoaded, gameUnloaded } from "./actions";
import { FrontLine } from "./liberationApi";
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
interface FrontLinesState {
fronts: { [key: string]: FrontLine };
}
const initialState: FrontLinesState = {
fronts: {},
};
export const frontLinesSlice = createSlice({
name: "frontLines",
initialState,
reducers: {
updateFrontLine: (state, action: PayloadAction<FrontLine[]>) => {
for (const front of action.payload) {
state.fronts[front.id] = front;
}
},
deleteFrontLine: (state, action: PayloadAction<string[]>) => {
for (const uid of action.payload) {
delete state.fronts[uid];
}
},
},
extraReducers: (builder) => {
builder.addCase(gameLoaded, (state, action) => {
state.fronts = action.payload.front_lines.reduce(
(acc: { [key: string]: FrontLine }, curr) => {
acc[curr.id] = curr;
return acc;
},
{}
);
});
builder.addCase(gameUnloaded, (state) => {
state.fronts = {};
});
},
});
export const { updateFrontLine, deleteFrontLine } =
frontLinesSlice.actions;
export const selectFrontLines = (state: RootState) => state.frontLines;
export default frontLinesSlice.reducer;

View File

@@ -1,24 +0,0 @@
import { AppDispatch } from "../app/store";
import { gameLoaded, gameUnloaded } from "./actions";
import backend from "./backend";
import { Game } from "./liberationApi";
export default function reloadGameState(
dispatch: AppDispatch,
ignoreRecenter: boolean = false
) {
backend
.get("/game")
.catch((error) => console.log(`Error fetching game state: ${error}`))
.then((response) => {
if (response == null || response.data == null) {
dispatch(gameUnloaded());
return;
}
const game = response.data as Game;
if (ignoreRecenter) {
game.map_center = undefined;
}
dispatch(gameLoaded(game));
});
}

View File

@@ -1,49 +0,0 @@
import { RootState } from "../app/store";
import { gameLoaded, gameUnloaded } from "./actions";
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { IadsConnection} from "./_liberationApi";
interface IadsNetworkState {
connections: {[key: string]: IadsConnection}
}
const initialState: IadsNetworkState = {
connections: {},
};
export const IadsNetworkSlice = createSlice({
name: "iadsNetwork",
initialState,
reducers: {
updateIadsConnection: (state, action: PayloadAction<IadsConnection[]>) => {
for (const connection of action.payload) {
state.connections[connection.id] = connection
}
},
removeIadsConnection: (state, action: PayloadAction<string[]>) => {
for (const cID of action.payload) {
delete state.connections[cID];
}
}
},
extraReducers: (builder) => {
builder.addCase(gameLoaded, (state, action) => {
state.connections = action.payload.iads_network.connections.reduce(
(acc: { [key: string]: IadsConnection }, curr) => {
acc[curr.id] = curr;
return acc;
},
{}
);
});
builder.addCase(gameUnloaded, (state) => {
state.connections = {};
});
},
});
export const { updateIadsConnection, removeIadsConnection } = IadsNetworkSlice.actions;
export const selectIadsNetwork = (state: RootState) => state.iadsNetwork;
export default IadsNetworkSlice.reducer;

View File

@@ -1,71 +0,0 @@
import { _liberationApi } from "./_liberationApi";
// See https://redux-toolkit.js.org/rtk-query/usage/automated-refetching for an
// explanation of tag behavior.
export enum Tags {
FLIGHT_PLAN = "FlightPlan",
}
const LIST_ID = "LIST";
function providesList<R extends { id: string | number }[], T extends string>(
resultsWithIds: R | undefined,
tagType: T
) {
return resultsWithIds
? [
{ type: tagType, id: LIST_ID },
...resultsWithIds.map(({ id }) => ({ type: tagType, id })),
]
: [{ type: tagType, id: LIST_ID }];
}
export const liberationApi = _liberationApi.enhanceEndpoints({
addTagTypes: Object.values(Tags),
endpoints: {
// /debug/waypoint-geometries
getDebugHoldZones: {
providesTags: (result, error, arg) => [
{ type: Tags.FLIGHT_PLAN, id: arg.flightId },
],
},
getDebugIpZones: {
providesTags: (result, error, arg) => [
{ type: Tags.FLIGHT_PLAN, id: arg.flightId },
],
},
getDebugJoinZones: {
providesTags: (result, error, arg) => [
{ type: Tags.FLIGHT_PLAN, id: arg.flightId },
],
},
// /flights/
getCommitBoundaryForFlight: {
providesTags: (result, error, arg) => [
{ type: Tags.FLIGHT_PLAN, id: arg.flightId },
],
},
getFlightById: {
providesTags: (result, error, arg) => [
{ type: Tags.FLIGHT_PLAN, id: arg.flightId },
],
},
listFlights: {
providesTags: (result) => providesList(result, Tags.FLIGHT_PLAN),
},
// /waypoints/
listAllWaypointsForFlight: {
providesTags: (result, error, arg) => [
{ type: Tags.FLIGHT_PLAN, id: arg.flightId },
],
},
setWaypointPosition: {
invalidatesTags: (result, error, arg) => [
{ type: Tags.FLIGHT_PLAN, id: arg.flightId },
],
},
},
});
export * from "./_liberationApi";

View File

@@ -1,32 +0,0 @@
import { RootState } from "../app/store";
import { gameLoaded, gameUnloaded } from "./actions";
import { createSlice } from "@reduxjs/toolkit";
import { LatLngLiteral } from "leaflet";
interface MapState {
center: LatLngLiteral;
}
const initialState: MapState = {
center: { lat: 0, lng: 0 },
};
const mapSlice = createSlice({
name: "map",
initialState: initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(gameLoaded, (state, action) => {
if (action.payload.map_center != null) {
state.center = action.payload.map_center;
}
});
builder.addCase(gameUnloaded, (state) => {
state.center = { lat: 0, lng: 0 };
});
},
});
export const selectMapCenter = (state: RootState) => state.map.center;
export default mapSlice.reducer;

View File

@@ -1,53 +0,0 @@
import { RootState } from "../app/store";
import { gameLoaded, gameUnloaded } from "./actions";
import { NavMesh, NavMeshPoly } from "./liberationApi";
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface NavMeshState {
blue: NavMeshPoly[];
red: NavMeshPoly[];
}
const initialState: NavMeshState = {
blue: [],
red: [],
};
export interface INavMeshUpdate {
blue: boolean;
mesh: NavMesh;
}
const navMeshSlice = createSlice({
name: "navmesh",
initialState: initialState,
reducers: {
updated: (state, action: PayloadAction<INavMeshUpdate[]>) => {
for (const [blue, navmesh] of Object.entries(action.payload)) {
const data = {blue: (blue === "true"), mesh: navmesh} as unknown as INavMeshUpdate
const polys = data.mesh.polys;
if (data.blue) {
state.blue = polys;
} else {
state.red = polys;
}
}
},
},
extraReducers: (builder) => {
builder.addCase(gameLoaded, (state, action) => {
state.blue = action.payload.navmeshes.blue.polys;
state.red = action.payload.navmeshes.red.polys;
});
builder.addCase(gameUnloaded, (state) => {
state.blue = [];
state.red = [];
});
},
});
export const { updated: navMeshUpdated } = navMeshSlice.actions;
export const selectNavMeshes = (state: RootState) => state.navmeshes;
export default navMeshSlice.reducer;

View File

@@ -1,30 +0,0 @@
import { RootState } from "../app/store";
import { gameLoaded, gameUnloaded } from "./actions";
import { SupplyRoute } from "./liberationApi";
import { createSlice } from "@reduxjs/toolkit";
interface SupplyRoutesState {
routes: SupplyRoute[];
}
const initialState: SupplyRoutesState = {
routes: [],
};
export const supplyRoutesSlice = createSlice({
name: "supplyRoutes",
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(gameLoaded, (state, action) => {
state.routes = action.payload.supply_routes;
});
builder.addCase(gameUnloaded, (state) => {
state.routes = [];
});
},
});
export const selectSupplyRoutes = (state: RootState) => state.supplyRoutes;
export default supplyRoutesSlice.reducer;

View File

@@ -1,44 +0,0 @@
import { RootState } from "../app/store";
import { gameLoaded, gameUnloaded } from "./actions";
import { Tgo } from "./liberationApi";
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
interface TgosState {
tgos: { [key: string]: Tgo };
}
const initialState: TgosState = {
tgos: {},
};
export const tgosSlice = createSlice({
name: "tgos",
initialState,
reducers: {
updateTgo: (state, action: PayloadAction<Tgo[]>) => {
for (const tgo of action.payload) {
state.tgos[tgo.id] = tgo;
}
},
},
extraReducers: (builder) => {
builder.addCase(gameLoaded, (state, action) => {
state.tgos = action.payload.tgos.reduce(
(acc: { [key: string]: Tgo }, curr) => {
acc[curr.id] = curr;
return acc;
},
{}
);
});
builder.addCase(gameUnloaded, (state) => {
state.tgos = {};
});
},
});
export const { updateTgo } = tgosSlice.actions;
export const selectTgos = (state: RootState) => state.tgos;
export default tgosSlice.reducer;

View File

@@ -1,61 +0,0 @@
import { RootState } from "../app/store";
import { gameLoaded, gameUnloaded } from "./actions";
import { ThreatZoneContainer, ThreatZones } from "./liberationApi";
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
interface ThreatZonesState {
zones: ThreatZoneContainer;
}
const initialState: ThreatZonesState = {
zones: {
blue: {
full: [],
aircraft: [],
air_defenses: [],
radar_sams: [],
},
red: {
full: [],
aircraft: [],
air_defenses: [],
radar_sams: [],
},
},
};
export interface IThreatZoneUpdate {
blue: boolean;
zones: ThreatZones;
}
export const threatZonesSlice = createSlice({
name: "threatZonesState",
initialState,
reducers: {
updated: (state, action: PayloadAction<IThreatZoneUpdate[]>) => {
for (const [blue, zones] of Object.entries(action.payload)) {
const data = {blue: (blue === "true"), zones: zones} as unknown as IThreatZoneUpdate
if (data.blue) {
state.zones.blue = data.zones;
} else {
state.zones.red = data.zones;
}
}
},
},
extraReducers: (builder) => {
builder.addCase(gameLoaded, (state, action) => {
state.zones = action.payload.threat_zones;
});
builder.addCase(gameUnloaded, (state) => {
state.zones = initialState.zones;
});
},
});
export const { updated: threatZonesUpdated } = threatZonesSlice.actions;
export const selectThreatZones = (state: RootState) => state.threatZones;
export default threatZonesSlice.reducer;

View File

@@ -1,36 +0,0 @@
import { RootState } from "../app/store";
import { gameLoaded, gameUnloaded } from "./actions";
import { UnculledZone } from "./liberationApi";
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
interface UnculledZonesState {
zones: UnculledZone[];
}
const initialState: UnculledZonesState = {
zones: [],
};
export const unculledZonesSlice = createSlice({
name: "unculledZonesState",
initialState,
reducers: {
updated: (state, action: PayloadAction<UnculledZone[]>) => {
state.zones = action.payload;
},
},
extraReducers: (builder) => {
builder.addCase(gameLoaded, (state, action) => {
state.zones = action.payload.unculled_zones;
});
builder.addCase(gameUnloaded, (state) => {
state.zones = initialState.zones;
});
},
});
export const { updated: unculledZonesUpdated } = unculledZonesSlice.actions;
export const selectUnculledZones = (state: RootState) => state.unculledZones;
export default unculledZonesSlice.reducer;

View File

@@ -1,6 +0,0 @@
import type { RootState, AppDispatch } from "./store";
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

View File

@@ -1,41 +0,0 @@
import { baseApi } from "../api/baseApi";
import combatReducer from "../api/combatSlice";
import controlPointsReducer from "../api/controlPointsSlice";
import flightsReducer from "../api/flightsSlice";
import frontLinesReducer from "../api/frontLinesSlice";
import mapReducer from "../api/mapSlice";
import navMeshReducer from "../api/navMeshSlice";
import supplyRoutesReducer from "../api/supplyRoutesSlice";
import tgosReducer from "../api/tgosSlice";
import iadsNetworkReducer from "../api/iadsNetworkSlice";
import threatZonesReducer from "../api/threatZonesSlice";
import unculledZonesReducer from "../api/unculledZonesSlice";
import { Action, ThunkAction, configureStore } from "@reduxjs/toolkit";
export const store = configureStore({
reducer: {
combat: combatReducer,
controlPoints: controlPointsReducer,
flights: flightsReducer,
frontLines: frontLinesReducer,
map: mapReducer,
navmeshes: navMeshReducer,
supplyRoutes: supplyRoutesReducer,
iadsNetwork: iadsNetworkReducer,
tgos: tgosReducer,
threatZones: threatZonesReducer,
[baseApi.reducerPath]: baseApi.reducer,
unculledZones: unculledZonesReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(baseApi.middleware),
});
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;

View File

@@ -1,53 +0,0 @@
import Aircraft from "./Aircraft";
import { render } from "@testing-library/react";
import { Icon } from "leaflet";
const mockMarker = jest.fn();
jest.mock("react-leaflet", () => ({
Marker: (props: any) => {
mockMarker(props);
},
}));
test("grounded aircraft do not render", async () => {
const { container } = render(
<Aircraft
flight={{
id: "",
blue: true,
position: undefined,
sidc: "",
waypoints: [],
}}
/>
);
expect(container).toBeEmptyDOMElement();
});
test("in-flight aircraft render", async () => {
render(
<Aircraft
flight={{
id: "",
blue: true,
position: {
lat: 10,
lng: 20,
},
sidc: "foobar",
waypoints: [],
}}
/>
);
expect(mockMarker).toHaveBeenCalledWith(
expect.objectContaining({
position: {
lat: 10,
lng: 20,
},
icon: expect.any(Icon),
})
);
});

View File

@@ -1,32 +0,0 @@
import { Flight } from "../../api/liberationApi";
import { Icon, Point } from "leaflet";
import { Symbol } from "milsymbol";
import { Marker } from "react-leaflet";
function iconForFlight(flight: Flight) {
const symbol = new Symbol(flight.sidc, {
size: 20,
});
return new Icon({
iconUrl: symbol.toDataURL(),
iconAnchor: new Point(symbol.getAnchor().x, symbol.getAnchor().y),
});
}
interface AircraftProps {
flight: Flight;
}
export default function Aircraft(props: AircraftProps) {
if (!props.flight.position) {
return <></>;
}
return (
<Marker
position={props.flight.position}
icon={iconForFlight(props.flight)}
/>
);
}

View File

@@ -1 +0,0 @@
export { default } from "./Aircraft";

View File

@@ -1,15 +0,0 @@
import { selectFlights } from "../../api/flightsSlice";
import { useAppSelector } from "../../app/hooks";
import Aircraft from "../aircraft";
import { LayerGroup } from "react-leaflet";
export default function AircraftLayer() {
const flights = useAppSelector(selectFlights).flights;
return (
<LayerGroup>
{Object.values(flights).map((flight) => {
return <Aircraft key={flight.id} flight={flight} />;
})}
</LayerGroup>
);
}

View File

@@ -1 +0,0 @@
export { default } from "./AircraftLayer";

View File

@@ -1,65 +0,0 @@
import { Tgo } from "../../api/liberationApi";
import { selectTgos } from "../../api/tgosSlice";
import { useAppSelector } from "../../app/hooks";
import { Circle, LayerGroup } from "react-leaflet";
interface TgoRangeCirclesProps {
tgo: Tgo;
blue: boolean;
detection?: boolean;
}
function colorFor(blue: boolean, detection: boolean) {
if (blue) {
return detection ? "#bb89ff" : "#0084ff";
}
return detection ? "#eee17b" : "#c85050";
}
const TgoRangeCircles = (props: TgoRangeCirclesProps) => {
const radii = props.detection
? props.tgo.detection_ranges
: props.tgo.threat_ranges;
const color = colorFor(props.blue, props.detection === true);
const weight = props.detection ? 1 : 2;
return (
<>
{radii.map((radius, idx) => {
return (
<Circle
key={idx}
center={props.tgo.position}
radius={radius}
color={color}
fill={false}
weight={weight}
interactive={false}
/>
);
})}
</>
);
};
interface AirDefenseRangeLayerProps {
blue: boolean;
detection?: boolean;
}
export const AirDefenseRangeLayer = (props: AirDefenseRangeLayerProps) => {
const tgos = Object.values(useAppSelector(selectTgos).tgos);
var tgosForSide = tgos.filter((tgo) => tgo.blue === props.blue);
return (
<LayerGroup>
{tgosForSide.map((tgo) => {
return (
<TgoRangeCircles key={tgo.id} tgo={tgo} {...props}></TgoRangeCircles>
);
})}
</LayerGroup>
);
};
export default AirDefenseRangeLayer;

View File

@@ -1 +0,0 @@
export { default } from "./AirDefenseRangeLayer";

View File

@@ -1,53 +0,0 @@
import CombatModel from "../../api/combat";
import { LatLng } from "leaflet";
import { Polygon, Polyline } from "react-leaflet";
interface CombatProps {
combat: CombatModel;
}
function CombatFootprint(props: CombatProps) {
if (!props.combat.footprint) {
return <></>;
}
return (
<Polygon
positions={props.combat.footprint}
color="#c85050"
interactive={false}
fillOpacity={0.2}
/>
);
}
function CombatLines(props: CombatProps) {
if (!props.combat.flight_position || !props.combat.target_positions) {
return <></>;
}
const flightPosition: LatLng = props.combat.flight_position;
return (
<>
{props.combat.target_positions.map((position, idx) => {
return (
<Polyline
key={idx}
positions={[flightPosition, position]}
color="#c85050"
interactive={false}
/>
);
})}
</>
);
}
export default function Combat(props: CombatProps) {
return (
<>
<CombatFootprint {...props} />
<CombatLines {...props} />
</>
);
}

View File

@@ -1 +0,0 @@
export { default } from "./Combat";

View File

@@ -1,16 +0,0 @@
import { selectCombat } from "../../api/combatSlice";
import { useAppSelector } from "../../app/hooks";
import Combat from "../combat/Combat";
import { LayerGroup } from "react-leaflet";
export default function CombatLayer() {
const combats = useAppSelector(selectCombat);
return (
<LayerGroup>
{Object.values(combats.combat).map((combat) => {
return <Combat key={combat.id} combat={combat} />;
})}
(
</LayerGroup>
);
}

View File

@@ -1 +0,0 @@
export { default } from "./CombatLayer";

View File

@@ -1,15 +0,0 @@
import { ControlPoint as ControlPointModel } from "../../api/liberationApi";
import { MobileControlPoint } from "./MobileControlPoint";
import { StaticControlPoint } from "./StaticControlPoint";
interface ControlPointProps {
controlPoint: ControlPointModel;
}
export default function ControlPoint(props: ControlPointProps) {
if (props.controlPoint.mobile) {
return <MobileControlPoint {...props} />;
} else {
return <StaticControlPoint {...props} />;
}
}

View File

@@ -1,22 +0,0 @@
import { ControlPoint } from "../../api/_liberationApi";
import backend from "../../api/backend";
function openInfoDialog(controlPoint: ControlPoint) {
backend.post(`/qt/info/control-point/${controlPoint.id}`);
}
function openNewPackageDialog(controlPoint: ControlPoint) {
backend.post(`/qt/create-package/control-point/${controlPoint.id}`);
}
export const makeLocationMarkerEventHandlers = (controlPoint: ControlPoint) => {
return {
click: () => {
openInfoDialog(controlPoint);
},
contextmenu: () => {
openNewPackageDialog(controlPoint);
},
};
};

View File

@@ -1,15 +0,0 @@
import { ControlPoint } from "../../api/_liberationApi";
import { Icon, Point } from "leaflet";
import { Symbol } from "milsymbol";
export const iconForControlPoint = (cp: ControlPoint) => {
const symbol = new Symbol(cp.sidc, {
size: 24,
colorMode: "Dark",
});
return new Icon({
iconUrl: symbol.toDataURL(),
iconAnchor: new Point(symbol.getAnchor().x, symbol.getAnchor().y),
});
};

View File

@@ -1,9 +0,0 @@
interface LocationTooltipTextProps {
name: string;
}
export const LocationTooltipText = (props: LocationTooltipTextProps) => {
return <h3 style={{ margin: 0 }}>{props.name}</h3>;
};
export default LocationTooltipText;

View File

@@ -1,228 +0,0 @@
import { ControlPoint } from "../../api/_liberationApi";
import backend from "../../api/backend";
import {
useClearControlPointDestinationMutation,
useSetControlPointDestinationMutation,
} from "../../api/liberationApi";
import { makeLocationMarkerEventHandlers } from "./EventHandlers";
import { iconForControlPoint } from "./Icons";
import LocationTooltipText from "./LocationTooltipText";
import { MovementPath, MovementPathHandle } from "./MovementPath";
import { StaticControlPoint } from "./StaticControlPoint";
import { LatLng, Marker as LMarker, LatLngLiteral } from "leaflet";
import { useCallback, useEffect, useRef, useState } from "react";
import ReactDOMServer from "react-dom/server";
import { Marker, Tooltip } from "react-leaflet";
function metersToNauticalMiles(meters: number) {
return meters * 0.000539957;
}
function formatLatLng(latLng: LatLng) {
const lat = latLng.lat.toFixed(2);
const lng = latLng.lng.toFixed(2);
const ns = latLng.lat >= 0 ? "N" : "S";
const ew = latLng.lng >= 0 ? "E" : "W";
return `${lat}&deg;${ns} ${lng}&deg;${ew}`;
}
function destinationTooltipText(
cp: ControlPoint,
destinationish: LatLngLiteral,
inRange: boolean
) {
const destination = new LatLng(destinationish.lat, destinationish.lng);
const distance = metersToNauticalMiles(
destination.distanceTo(cp.position)
).toFixed(1);
if (!inRange) {
return `Out of range (${distance}nm away)`;
}
const dest = formatLatLng(destination);
return `${cp.name} moving ${distance}nm to ${dest} next turn`;
}
interface PrimaryMarkerProps {
controlPoint: ControlPoint;
}
/**
* The primary control point marker. For non-mobile control points, this has
* fairly simple behavior: it's a marker in a fixed location that can manage
* units and can have missions planned against it.
*
* For mobile control points, this is a draggable marker. If the control point
* has a destination (either because it was dragged after render, or because it
* had a destination in the game that was loaded), the unit management and
* mission planning behaviors are delegated to SecondaryMarker, and the primary
* marker becomes only a destination marker. It can be dragged to change the
* destination, and can be right clicked to cancel movement.
*/
function PrimaryMarker(props: PrimaryMarkerProps) {
// We can't use normal state to update the marker tooltip or the line points
// because if we set any state in the drag event it will re-render this
// component and all children, interrupting dragging. Instead, keep refs to
// the objects and mutate them directly.
//
// For the same reason, the path is owned by this component, because updating
// sibling state would be messy. Lifting the state into the parent would still
// cause this component to redraw.
const markerRef = useRef<LMarker | null>(null);
const pathRef = useRef<MovementPathHandle | null>(null);
const [hasDestination, setHasDestination] = useState<boolean>(
props.controlPoint.destination != null
);
const [position, setPosition] = useState<LatLngLiteral>(
props.controlPoint.destination
? props.controlPoint.destination
: props.controlPoint.position
);
const setDestination = useCallback((destination: LatLng) => {
setPosition(destination);
setHasDestination(true);
}, []);
const resetDestination = useCallback(() => {
setPosition(props.controlPoint.position);
setHasDestination(false);
}, [props]);
const [putDestination, { isLoading }] =
useSetControlPointDestinationMutation();
const [cancelTravel] = useClearControlPointDestinationMutation();
useEffect(() => {
markerRef.current?.setTooltipContent(
props.controlPoint.destination
? destinationTooltipText(
props.controlPoint,
props.controlPoint.destination,
true
)
: ReactDOMServer.renderToString(
<LocationTooltipText name={props.controlPoint.name} />
)
);
});
const locationClickHandlers = makeLocationMarkerEventHandlers(
props.controlPoint
);
return (
<>
<Marker
position={position}
icon={iconForControlPoint(props.controlPoint)}
draggable={!isLoading}
autoPan
// We might draw other markers on top of the CP. The tooltips from the
// other markers are helpful so we want to keep them, but make sure the CP
// is always the clickable thing.
zIndexOffset={1000}
opacity={props.controlPoint.destination ? 0.5 : 1}
ref={(ref) => {
if (ref != null) {
markerRef.current = ref;
}
}}
eventHandlers={{
click: () => {
if (!hasDestination) {
locationClickHandlers.click();
}
},
contextmenu: () => {
if (props.controlPoint.destination) {
cancelTravel({ cpId: props.controlPoint.id }).then(() => {
resetDestination();
});
} else {
locationClickHandlers.contextmenu();
}
},
drag: (event) => {
const destination = event.target.getLatLng();
backend
.get(
`/control-points/${props.controlPoint.id}/destination-in-range?lat=${destination.lat}&lng=${destination.lng}`
)
.then((inRange) => {
markerRef.current?.setTooltipContent(
destinationTooltipText(
props.controlPoint,
destination,
inRange.data
)
);
});
pathRef.current?.setDestination(destination);
},
dragend: async (event) => {
const currentPosition = new LatLng(position.lat, position.lng);
const destination = event.target.getLatLng();
setDestination(destination);
try {
await putDestination({
cpId: props.controlPoint.id,
body: { lat: destination.lat, lng: destination.lng },
}).unwrap();
} catch (error) {
console.error("setDestination failed", error);
setDestination(currentPosition);
}
},
}}
>
<Tooltip />
</Marker>
<MovementPath
source={props.controlPoint.position}
destination={position}
ref={pathRef}
/>
</>
);
}
interface SecondaryMarkerProps {
controlPoint: ControlPoint;
destination: LatLngLiteral | undefined;
}
/**
* The secondary marker for a control point. The secondary marker will only be
* shown when the control point has a destination set. For mobile control
* points, the primary marker is draggable, and the secondary marker will be
* shown at the current location iff the control point has been dragged. The
* secondary marker is also the marker that has the normal control point
* interaction options (mission planning and unit management).
*/
function SecondaryMarker(props: SecondaryMarkerProps) {
if (!props.destination) {
return <></>;
}
return <StaticControlPoint controlPoint={props.controlPoint} />;
}
interface MobileControlPointProps {
controlPoint: ControlPoint;
}
export const MobileControlPoint = (props: MobileControlPointProps) => {
return (
<>
<PrimaryMarker
controlPoint={props.controlPoint}
key={props.controlPoint.destination ? 0 : 1}
/>
<SecondaryMarker
controlPoint={props.controlPoint}
destination={props.controlPoint.destination}
/>
</>
);
};

View File

@@ -1,35 +0,0 @@
import { LatLngLiteral, Polyline as LPolyline } from "leaflet";
import { forwardRef, useImperativeHandle, useRef } from "react";
import { Polyline } from "react-leaflet";
interface MovementPathProps {
source: LatLngLiteral;
destination: LatLngLiteral;
}
export interface MovementPathHandle {
setDestination: (destination: LatLngLiteral) => void;
}
export const MovementPath = forwardRef<MovementPathHandle, MovementPathProps>(
(props: MovementPathProps, ref) => {
const lineRef = useRef<LPolyline | null>(null);
useImperativeHandle(
ref,
() => ({
setDestination: (destination: LatLngLiteral) => {
lineRef.current?.setLatLngs([props.source, destination]);
},
}),
[props]
);
return (
<Polyline
positions={[props.source, props.destination]}
weight={1}
color="#80BA80"
ref={lineRef}
/>
);
}
);

View File

@@ -1,27 +0,0 @@
import { ControlPoint } from "../../api/_liberationApi";
import { makeLocationMarkerEventHandlers } from "./EventHandlers";
import { iconForControlPoint } from "./Icons";
import LocationTooltipText from "./LocationTooltipText";
import { Marker, Tooltip } from "react-leaflet";
interface StaticControlPointProps {
controlPoint: ControlPoint;
}
export const StaticControlPoint = (props: StaticControlPointProps) => {
return (
<Marker
position={props.controlPoint.position}
icon={iconForControlPoint(props.controlPoint)}
// We might draw other markers on top of the CP. The tooltips from the
// other markers are helpful so we want to keep them, but make sure the CP
// is always the clickable thing.
zIndexOffset={1000}
eventHandlers={makeLocationMarkerEventHandlers(props.controlPoint)}
>
<Tooltip>
<LocationTooltipText name={props.controlPoint.name} />
</Tooltip>
</Marker>
);
};

View File

@@ -1 +0,0 @@
export { default } from "./ControlPoint";

View File

@@ -1,17 +0,0 @@
import { selectControlPoints } from "../../api/controlPointsSlice";
import { useAppSelector } from "../../app/hooks";
import ControlPoint from "../controlpoints";
import { LayerGroup } from "react-leaflet";
export default function ControlPointsLayer() {
const controlPoints = useAppSelector(selectControlPoints);
return (
<LayerGroup>
{Object.values(controlPoints.controlPoints).map((controlPoint) => {
return (
<ControlPoint key={controlPoint.id} controlPoint={controlPoint} />
);
})}
</LayerGroup>
);
}

View File

@@ -1 +0,0 @@
export { default } from "./ControlPointsLayer";

View File

@@ -1,47 +0,0 @@
import { UnculledZone } from "../../api/liberationApi";
import { selectUnculledZones } from "../../api/unculledZonesSlice";
import { useAppSelector } from "../../app/hooks";
import { LayerGroup, LayersControl, Circle } from "react-leaflet";
interface CullingExclusionCirclesProps {
zones: UnculledZone[];
}
const CullingExclusionCircles = (props: CullingExclusionCirclesProps) => {
return (
<>
<LayerGroup>
{props.zones.map((zone, idx) => {
return (
<Circle
key={idx}
center={zone.position}
radius={zone.radius}
color="#b4ff8c"
fill={false}
interactive={false}
/>
);
})}
</LayerGroup>
</>
);
};
export default function CullingExclusionZones() {
const data = useAppSelector(selectUnculledZones).zones;
var cez = <></>;
if (!data) {
console.log("Empty response when loading culling exclusion zones");
} else {
cez = (
<CullingExclusionCircles zones={data}></CullingExclusionCircles>
);
}
return (
<LayersControl.Overlay name="Culling exclusion zones">
{cez}
</LayersControl.Overlay>
);
}

View File

@@ -1 +0,0 @@
export { default } from "./CullingExclusionZones";

View File

@@ -1,120 +0,0 @@
import { Flight } from "../../api/liberationApi";
import { useGetCommitBoundaryForFlightQuery } from "../../api/liberationApi";
import WaypointMarker from "../waypointmarker";
import { ReactElement } from "react";
import { Polyline } from "react-leaflet";
const BLUE_PATH = "#0084ff";
const RED_PATH = "#c85050";
const SELECTED_PATH = "#ffff00";
interface FlightPlanProps {
flight: Flight;
selected: boolean;
highlight?: boolean;
}
const pathColor = (props: FlightPlanProps) => {
if (props.selected && props.highlight) {
return SELECTED_PATH;
} else if (props.flight.blue) {
return BLUE_PATH;
} else {
return RED_PATH;
}
};
function FlightPlanPath(props: FlightPlanProps) {
const color = pathColor(props);
const waypoints = props.flight.waypoints;
if (waypoints == null) {
return <></>;
}
const points = waypoints
.filter((waypoint) => waypoint.include_in_path)
.map((waypoint) => waypoint.position);
return (
<Polyline
positions={points}
pathOptions={{ color: color, interactive: false }}
/>
);
}
const WaypointMarkers = (props: FlightPlanProps) => {
if (!props.selected || props.flight.waypoints == null) {
return <></>;
}
var markers: ReactElement[] = [];
props.flight.waypoints?.forEach((p, idx) => {
if (p.should_mark) {
markers.push(
<WaypointMarker
key={idx}
number={idx}
waypoint={p}
flight={props.flight}
/>
);
}
});
return <>{markers}</>;
};
interface CommitBoundaryProps {
flightId: string;
selected: boolean;
}
function CommitBoundary(props: CommitBoundaryProps) {
const { data, error, isLoading } = useGetCommitBoundaryForFlightQuery(
{
flightId: props.flightId,
},
// RTK Query doesn't seem to allow us to invalidate the cache from anything
// but a mutation, but this data can be invalidated by events from the
// websocket. Just disable the cache for this.
//
// This isn't perfect. It won't redraw until the component remounts. There
// doesn't appear to be a better way.
{ refetchOnMountOrArgChange: true }
);
if (isLoading) {
return <></>;
}
if (error) {
console.error(`Error loading commit boundary for ${props.flightId}`, error);
return <></>;
}
if (!data) {
console.log(
`Null response data when loading commit boundary for ${props.flightId}`
);
return <></>;
}
return (
<Polyline positions={data} color="#ffff00" weight={1} interactive={false} />
);
}
function CommitBoundaryIfSelected(props: CommitBoundaryProps) {
if (!props.selected) {
return <></>;
}
return <CommitBoundary {...props} />;
}
export default function FlightPlan(props: FlightPlanProps) {
return (
<>
<FlightPlanPath {...props} />
<WaypointMarkers {...props} />
<CommitBoundaryIfSelected
flightId={props.flight.id}
selected={props.selected}
/>
</>
);
}

View File

@@ -1 +0,0 @@
export { default } from "./FlightPlan";

View File

@@ -1,67 +0,0 @@
import { selectFlights, selectSelectedFlight } from "../../api/flightsSlice";
import { Flight } from "../../api/liberationApi";
import { useAppSelector } from "../../app/hooks";
import FlightPlan from "../flightplan";
import { LayerGroup } from "react-leaflet";
interface FlightPlansLayerProps {
blue: boolean;
selectedOnly?: true;
}
function SelectedFlightPlan(props: FlightPlansLayerProps) {
const flight = useAppSelector(selectSelectedFlight);
if (!flight) {
return <></>;
}
if (!props.blue) {
// We don't currently support playing as red, so nothing to draw.
return <></>;
}
return (
<FlightPlan
key={flight.id}
flight={flight}
selected={true}
highlight={!props.selectedOnly}
/>
);
}
function UnselectedFlightPlans(props: FlightPlansLayerProps) {
const flightData = useAppSelector(selectFlights);
const isNotSelected = (flight: Flight) => {
if (flightData.selected == null) {
return true;
}
return flightData.selected !== flight.id;
};
if (props.selectedOnly) {
return <></>;
}
return (
<>
{Object.values(flightData.flights)
.filter(isNotSelected)
.filter((flight) => props.blue === flight.blue)
.map((flight) => {
return (
<FlightPlan key={flight.id} flight={flight} selected={false} />
);
})}
</>
);
}
export default function FlightPlansLayer(props: FlightPlansLayerProps) {
return (
<LayerGroup>
<UnselectedFlightPlans {...props} />
<SelectedFlightPlan {...props} />
</LayerGroup>
);
}

View File

@@ -1 +0,0 @@
export { default } from "./FlightPlansLayer";

View File

@@ -1,27 +0,0 @@
import {
FrontLine as FrontLineModel,
useOpenNewFrontLinePackageDialogMutation,
} from "../../api/liberationApi";
import { Polyline } from "react-leaflet";
interface FrontLineProps {
front: FrontLineModel;
}
function FrontLine(props: FrontLineProps) {
const [openNewPackageDialog] = useOpenNewFrontLinePackageDialogMutation();
return (
<Polyline
positions={props.front.extents}
weight={16}
color={"#fe7d0a"}
eventHandlers={{
contextmenu: () => {
openNewPackageDialog({ frontLineId: props.front.id });
},
}}
/>
);
}
export default FrontLine;

View File

@@ -1 +0,0 @@
export { default } from "./FrontLine";

View File

@@ -1,15 +0,0 @@
import { selectFrontLines } from "../../api/frontLinesSlice";
import { useAppSelector } from "../../app/hooks";
import FrontLine from "../frontline";
import { LayerGroup } from "react-leaflet";
export default function SupplyRoutesLayer() {
const fronts = useAppSelector(selectFrontLines).fronts;
return (
<LayerGroup>
{Object.values(fronts).map((front, idx) => {
return <FrontLine key={idx} front={front} />;
})}
</LayerGroup>
);
}

View File

@@ -1 +0,0 @@
export { default } from "./FrontLinesLayer";

View File

@@ -1,39 +0,0 @@
import { IadsConnection as IadsConnectionModel } from "../../api/liberationApi";
import { Polyline as LPolyline } from "leaflet";
import { useRef } from "react";
import { Polyline, Tooltip } from "react-leaflet";
interface IadsConnectionProps {
iads_connection: IadsConnectionModel;
}
function IadsConnectionTooltip(props: IadsConnectionProps) {
var status = props.iads_connection.active ? "Active" : "Inactive";
if (props.iads_connection.is_power) {
return <Tooltip>Power Connection ({status})</Tooltip>;
} else {
return <Tooltip>Communication Connection ({status})</Tooltip>;
}
}
export default function IadsConnection(props: IadsConnectionProps) {
const color = props.iads_connection.is_power ? "#FFD580" : "#87CEEB";
const path = useRef<LPolyline | null>();
const weight = 1
var opacity = props.iads_connection.active ? 1.0 : 0.5
var dashArray = props.iads_connection.active ? "" : "20"
return (
<Polyline
positions={props.iads_connection.points}
color={color}
weight={weight}
opacity={opacity}
dashArray={dashArray}
ref={(ref) => (path.current = ref)}
>
<IadsConnectionTooltip {...props} />
</Polyline>
);
}

View File

@@ -1 +0,0 @@
export { default } from "./IadsNetwork";

View File

@@ -1,26 +0,0 @@
import { useAppSelector } from "../../app/hooks";
import { LayerGroup } from "react-leaflet";
import IadsConnection from "../iadsnetwork/IadsNetwork";
import { selectIadsNetwork } from "../../api/iadsNetworkSlice";
interface IadsNetworkLayerProps {
blue: boolean;
}
export const IadsNetworkLayer = (props: IadsNetworkLayerProps) => {
const connections = Object.values(useAppSelector(selectIadsNetwork).connections);
var iadsConnectionsForSide = connections.filter((connection) => connection.blue === props.blue);
return (
<LayerGroup>
{iadsConnectionsForSide.map((connection) => {
return (
<IadsConnection key={connection.id} iads_connection={connection} />
);
})}
</LayerGroup>
);
};
export default IadsNetworkLayer;

View File

@@ -1 +0,0 @@
export { default } from "./IadsNetworkLayer";

View File

@@ -1,7 +0,0 @@
@import "~leaflet/dist/leaflet.css";
.leaflet-container {
width: 100%;
height: 100%;
min-height: 100vh;
}

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