Compare commits

...

60 Commits

Author SHA1 Message Date
Dan Albert
e8e41c0bc1 Advance develop-7.1 to 7.1.1. 2023-06-03 22:27:12 +00:00
Dan Albert
9bd99f9cde Configure squadron sizes for Abu Dhabi.
(cherry picked from commit ec49a10135)
2023-06-03 22:07:59 +00:00
Dan Albert
d2cee713d8 Fix Black Sea LHA parking limits.
Everything else was within the limits, but I had forgotten to check the
LHAs.

(cherry picked from commit 23e3630169)
2023-06-03 22:07:59 +00:00
Dan Albert
1e0ef288be Ack campaign versions for new squadron limits.
I haven't tested all of them, but I know these are compatible, so
advertise them as such.

(cherry picked from commit e20ab5fbc0)
2023-06-03 22:07:59 +00:00
Dan Albert
12a0186246 Warn for new squadron rules with old campaigns.
It's not feasible to actually check the parking limits because we can't
identify parking limits for carriers until the theater is populated.
Doing so is expensive (and depends on other NGW inputs). Instead,
compare against the version of the campaign and guess.

A new (minor) campaign version has been introduced which makes this
required to improve the UI hint. Campaigns that are compatible with the
new rules should update their version to advertise support.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2909.

(cherry picked from commit 4fd2bb131b)
2023-06-03 22:07:59 +00:00
Dan Albert
2f1a9d3dfd Disallow air wing generation with overfull bases.
This also changes the window close button of the air wing configuration
dialog to cancel rather than revert and continue, because otherwise
there's no way for the user to back out of the dialog without fixing all
the overfull bases first.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2910.

(cherry picked from commit 42a7102948)
2023-06-03 22:07:59 +00:00
Dan Albert
7c690227d6 Show overfull airbase details in air wing config.
https://github.com/dcs-liberation/dcs_liberation/issues/2910
(cherry picked from commit d271ff17c2)
2023-06-03 22:07:59 +00:00
Dan Albert
8172461db4 Show parking capacities in air wing config.
This does show the theoretical parking use of full squadrons even when
the new rules are not enabled. Since limits can be enabled manually
later in the game, it's still useful information, even if it's a bit
misleading.

https://github.com/dcs-liberation/dcs_liberation/issues/2910
(cherry picked from commit cb61dfccc4)
2023-06-03 22:07:59 +00:00
Dan Albert
87ed02deb0 Add new-game option to show air wing config.
Working on this UI was a huge pain because it required manually creating
a game before the UI could be used.

(cherry picked from commit 56f93c76eb)
2023-06-03 22:07:59 +00:00
Dan Albert
59676fb0fe Move CLI game generation after UI init.
(cherry picked from commit 36cb3a386c)
2023-06-03 22:07:59 +00:00
Dan Albert
7cfd6381fb Factor out game creation parameters in main.
Want to move this deeper into the launch process so that it can use the
UI, but don't want to pass the loosely typed argparse namespace any
more than we have to.

(cherry picked from commit c25e830e6c)
2023-06-03 22:07:59 +00:00
Dan Albert
9f7aa7b75b Fix line endings.
(cherry picked from commit 5d08990cd0)
2023-06-01 23:00:48 -07:00
Starfire13
5f5422b579 Add Final Countdown II campaign.
Designed for Normandy 2.0

(cherry picked from commit 2a45cd8899)
2023-06-01 23:00:48 -07:00
ColonelAkirNakesh
16ad43f260 Updates china_2010.yaml
Replaces T-55 with Type 59 MBT, adds Type 093 attack sub from China Assets pack

(cherry picked from commit 90b880ec3c)
2023-06-01 23:00:48 -07:00
ColonelAkirNakesh
9cd1a06651 Update russia_2010.yaml
Adds Ropucha landing ship, Improved Kilo sub

(cherry picked from commit 5f0c570d65)
2023-06-01 23:00:48 -07:00
ColonelAkirNakesh
2bf1ba6d12 Update allies_1944.yaml
Adds 105mm field howitzer to allies

(cherry picked from commit ce102fcc50)
2023-06-01 23:00:48 -07:00
ColonelAkirNakesh
e705a2ddbf Enforces Topgun: Maverick Rogue Nation livery for Iranian Tomcat
(cherry picked from commit 30c792c15a)
2023-06-01 23:00:48 -07:00
ColonelAkirNakesh
60dd8f3245 Adds support for Chinese sub Type_093.yaml
(cherry picked from commit 2f45b856d6)
2023-06-01 23:00:48 -07:00
ColonelAkirNakesh
0ecc53ef27 Create TYPE-59.yaml
(cherry picked from commit 31d2b756ab)
2023-06-01 23:00:48 -07:00
ColonelAkirNakesh
2f715a1427 Create Horch_901_typ_40_kfz_21.yaml
(cherry picked from commit b5cf889c09)
2023-06-01 23:00:48 -07:00
ColonelAkirNakesh
e28ffe97ac Create Pak40.yaml
(cherry picked from commit 19958f91ca)
2023-06-01 23:00:48 -07:00
ColonelAkirNakesh
54bd4189bd Create Wespe124.yaml
(cherry picked from commit c775a898a4)
2023-06-01 23:00:48 -07:00
ColonelAkirNakesh
a762970469 Create LeFH_18-40-105.yaml
(cherry picked from commit 535244f6f3)
2023-06-01 23:00:48 -07:00
ColonelAkirNakesh
c5ebde3cd3 Create Higgins_boat.yaml
(cherry picked from commit 9d1d3bdcfa)
2023-06-01 23:00:48 -07:00
ColonelAkirNakesh
38ce82f3f9 Create M2A1-105.yaml
(cherry picked from commit 36eef2b1b9)
2023-06-01 23:00:48 -07:00
ColonelAkirNakesh
16ef182a8d Create IMPROVED_KILO.yaml
(cherry picked from commit 7788425c5c)
2023-06-01 23:00:48 -07:00
ColonelAkirNakesh
8ac582b9a8 Create BDK-775.yaml
(cherry picked from commit ee0c21b3e5)
2023-06-01 23:00:48 -07:00
ColonelAkirNakesh
11d77c0fe6 Create santafe.yaml
(cherry picked from commit 54cd619f75)
2023-06-01 23:00:48 -07:00
ColonelAkirNakesh
c1eab6715b Create leander-gun-condell.yaml
(cherry picked from commit 051940e23c)
2023-06-01 23:00:48 -07:00
ColonelAkirNakesh
4be77472e7 Create leander-gun-lynch.yaml
(cherry picked from commit 4fbd7defa3)
2023-06-01 23:00:48 -07:00
Dan Albert
af65254db5 Add missing note about 7.0.0 -> 7.1.0 save compat.
(cherry picked from commit 90bda9383d)
2023-06-01 23:00:48 -07:00
Dan Albert
b523b03e3c Minor campaign version bump for Normandy 2.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2804.

(cherry picked from commit 7798e2970c)
2023-05-30 23:59:35 -07:00
Dan Albert
aae53ffc63 Update beacon data.
Did this for Normandy 2... but unsurprisingly there aren't a whole lot
of beacons in a WW2 map.

(cherry picked from commit 410c25b331)
2023-05-30 23:59:35 -07:00
Dan Albert
c6916d8da2 Update pydcs.
Normandy 2 support.

https://github.com/dcs-liberation/dcs_liberation/issues/2804
(cherry picked from commit cff74525d6)
2023-05-30 23:59:35 -07:00
Dan Albert
8ae64f57b5 Update Normandy landmap for Normandy 2.
https://github.com/dcs-liberation/dcs_liberation/issues/2804
(cherry picked from commit 8b7f107044)
2023-05-30 23:59:35 -07:00
Dan Albert
b132543b7e Add Normandy 2 landmap inputs.
https://github.com/dcs-liberation/dcs_liberation/issues/2804
(cherry picked from commit c365a0d739)
2023-05-30 23:59:35 -07:00
Dan Albert
1836b0bd98 Force polygons into validity during GIS import.
Not sure why, but some polygons become invalid (which usually means a
self-intersecting "polygon", such as two triangles that meet at a point)
during this transformation. Shapely includes a tool to reshape polygons
into validity, so use that.

(cherry picked from commit 1f4fd0fd04)
2023-05-30 23:59:35 -07:00
Dan Albert
29a05fa0e7 Tolerate empty settings files.
(cherry picked from commit 4bb60cb500)
2023-05-30 23:59:35 -07:00
Dan Albert
da1f84a8f5 Add settings for battlefield commander slots.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2235.

(cherry picked from commit fe96a415be)
2023-05-30 23:59:35 -07:00
Dan Albert
5d22d4f43c Add performance option to prevent missile tasks.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2508.

(cherry picked from commit 6699289bf7)
2023-05-30 23:59:35 -07:00
Dan Albert
6adde1cb3e Add changelog note for BAI fix.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2922.

(cherry picked from commit a85d3243fb)
2023-05-30 21:22:27 -07:00
zhexu14
81a00981eb issue 2922: make BAI plannable against missile and costal sites
(cherry picked from commit e024013093)
2023-05-30 21:22:27 -07:00
Dan Albert
ed17fc97d9 Replace more Patriot STRs with real EWRs.
Not all of these nations actually field this radar (according to
Wikipedia), but at least it's a real EWR, and it's the only blue one
we've got.

(cherry picked from commit 7f2607cf08)
2023-05-30 21:22:27 -07:00
ColonelAkirNakesh
75ee0de23f Replaces Patriot STR with AN/FPS-117 EWR, adds USS Harry Truman
(cherry picked from commit 29ffb526f2)
2023-05-30 21:22:27 -07:00
Dan Albert
a4d7c66621 Fix formatting of takeoff time.
(cherry picked from commit 257dabe4fa)
2023-05-25 22:44:35 -07:00
Dan Albert
998864797d Add UI for TOT offset adjustment.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2912.

(cherry picked from commit 406fb61fa4)
2023-05-25 22:44:35 -07:00
Dan Albert
5cca4eb051 Save the TOT offset in the flight plan.
Prep work for exposing this to the UI.

(cherry picked from commit 49dfa95c61)
2023-05-25 22:44:35 -07:00
Dan Albert
d5c335c698 Allow save compat to exist for two versions.
We want to clean up eventually, but allowing it to exist in both develop
and the release branch makes cherry picks easier.

(cherry picked from commit c80e5b259f)
2023-05-25 22:44:35 -07:00
Dan Albert
7fea15ee07 Make the flight details menu modal.
Prevents players from accidentally deleting flights they're currently
viewing, which would cause an error.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2911.

(cherry picked from commit 64e2213f28)
2023-05-25 22:19:40 -07:00
Dan Albert
a31296cbc0 Add changelog section for 7.1.0.
(cherry picked from commit ced93afd49)
2023-05-25 22:19:40 -07:00
Dan Albert
fe60757891 Branch 7.1.0. 2023-05-25 21:16:02 -07:00
Dan Albert
7614017828 Bump version to 7.0.1. 2023-05-23 01:41:13 -07:00
Dan Albert
61879aeafa Fix line endings.
These get broken whenever someone uses the GitHub file upload editor,
since that doesn't understand .gitattributes.

(cherry picked from commit 6f4ac1dc39)
2023-05-23 00:50:38 -07:00
Starfire13
f5b9052257 Update Golan Heights and Caen to Evreux campaigns.
I had asked Khopa for permission to edit two of his campaigns to fix
some issues. Only the YAMLs have been edited, .miz files did not need
changes. I have tested both YAMLs to make sure campaigns will generate.
Also tested generating turn 1 .miz and ran it in DCS.

Golan Heights:
1. Removed the 2 problematic squadrons from Marj Ruhayyil that were
causing aircraft losses due to larger aircraft sizes not fitting at that
airfield (which is intended for helicopters).
2. Implemented squadron limits.

Caen to Evreux:
1. Re-arranged squadrons for better force distribution and revised
primary and secondary mission types for better default play experience.
2. Implemented squadron limits.

(cherry picked from commit f831c8efdd)
2023-05-23 00:50:38 -07:00
Starfire13
b27d2be0d1 Update Apache loadouts.
BAI loadout updated to use the new radar guided hellfires. Aux tanks
removed in favour of extra cannon ammo.

(cherry picked from commit e3c6b03603)
2023-05-20 02:34:02 -07:00
Dan Albert
e1a1eca5da Fix syntax error in bluefor_modern.yaml.
(cherry picked from commit 7a2e8279cd)
2023-05-19 18:00:32 -07:00
Dan Albert
c695db0f98 Checkpoint game before sim, auto-revert on abort.
An alternative to
https://github.com/dcs-liberation/dcs_liberation/pull/2891 that I ended
up liking much better (I had assumed some part of the UI would fail or
at least look terrible with this approach, but it seems to work quite
well).

On by default now since it's far less frightening than the previous
thing.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2735.

(cherry picked from commit 24e72475b4)
2023-05-19 17:53:34 -07:00
Starfire13
ff20f16109 Update scenic_inland.yaml
A formatting fix for scenic route 2 that was preventing new campaign start. Fixing at Fuzzle's request as he doesn't have the time for it right now.

(cherry picked from commit f10350dac4)
2023-05-19 17:43:59 -07:00
Dan Albert
8af3dc6965 Fuzzle campaign updates.
https://github.com/dcs-liberation/dcs_liberation/issues/2889
(cherry picked from commit f068976749)
2023-05-19 01:28:08 -07:00
Dan Albert
e6cf253e45 Attempt to reset the simulation on abort.
This is optional because I really don't know if I trust it. I don't see
much wrong with it (aside from the warning about not using it with auto-
resolve, because it won't restore lost aircraft), but it's really not
something I'd built for since it's not going to be possible as the RTS
features grow.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2735.

(cherry picked from commit 4b4c45e90f)
2023-05-19 01:19:49 -07:00
98 changed files with 1309 additions and 446 deletions

View File

@@ -1,3 +1,32 @@
# 7.1.1
Saves from 7.1.0 are compatible with 7.1.1
## Features/Improvements
## Fixes
# 7.1.0
Saves from 7.0.0 are compatible with 7.1.0
## Features/Improvements
* **[Engine]** Support for Normandy 2 airfields.
* **[Factions]** Replaced Patriot STRs "EWRs" with AN/FPS-117 for blue factions 1980 or newer.
* **[Mission Generation]** Added option to prevent scud and V2 sites from firing at the start of the mission.
* **[Mission Generation]** Added settings for controlling number of tactical commander, observer, JTAC, and game master slots.
* **[Mission Planning]** Per-flight TOT offsets can now be set in the flight details UI. This allows individual flights to be scheduled ahead of or behind the rest of the package.
* **[New Game Wizard]** The air wing configuration dialog will check for and reject overfull airbases before continuing when the new squadron rules are used.
* **[New Game Wizard]** Closing the air wing configuration dialog will now cancel and return to the new game wizard rather than reverting changes and continuing.
* **[New Game Wizard]** A warning will be displayed next to the new squadron rules button if the campaign predates the new rules and will likely require user intervention before continuing.
* **[UI]** Parking capacity of each squadron's base is now shown during air wing configuration to avoid overcrowding bases when beginning the game with full squadrons.
## Fixes
* **[Mission Planning]** BAI is once again plannable against missile sites and coastal defense batteries.
* **[UI]** Fixed formatting of departure time in flight details dialog.
# 7.0.0
Saves from 6.x are not compatible with 7.0.
@@ -16,6 +45,7 @@ Saves from 6.x are not compatible with 7.0.
* **[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.

View File

@@ -9,7 +9,7 @@
project = "DCS Liberation"
copyright = "2023, DCS Liberation Team"
author = "DCS Liberation Team"
release = "7.0.0"
release = "7.1.1"
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

View File

@@ -21,6 +21,7 @@ from .planningerror import PlanningError
from ..flightwaypointtype import FlightWaypointType
from ..starttype import StartType
from ..traveltime import GroundSpeed, TravelTime
from ...savecompat import has_save_compat_for
if TYPE_CHECKING:
from game.dcs.aircrafttype import FuelConsumption
@@ -62,6 +63,13 @@ class FlightPlan(ABC, Generic[LayoutT]):
def __init__(self, flight: Flight, layout: LayoutT) -> None:
self.flight = flight
self.layout = layout
self.tot_offset = self.default_tot_offset()
@has_save_compat_for(7)
def __setstate__(self, state: dict[str, Any]) -> None:
if "tot_offset" not in state:
state["tot_offset"] = self.default_tot_offset()
self.__dict__.update(state)
@property
def package(self) -> Package:
@@ -195,8 +203,7 @@ class FlightPlan(ABC, Generic[LayoutT]):
[meters(cp.position.distance_to_point(w.position)) for w in self.waypoints]
)
@property
def tot_offset(self) -> timedelta:
def default_tot_offset(self) -> timedelta:
"""This flight's offset from the package's TOT.
Positive values represent later TOTs. An offset of -2 minutes is used

View File

@@ -25,10 +25,6 @@ if TYPE_CHECKING:
class FormationAttackFlightPlan(FormationFlightPlan, ABC):
@property
def lead_time(self) -> timedelta:
return timedelta()
@property
def package_speed_waypoints(self) -> set[FlightWaypoint]:
return {
@@ -50,13 +46,6 @@ class FormationAttackFlightPlan(FormationFlightPlan, ABC):
def tot_waypoint(self) -> FlightWaypoint:
return self.layout.targets[0]
@property
def tot_offset(self) -> timedelta:
try:
return -self.lead_time
except AttributeError:
return timedelta()
@property
def target_area_waypoint(self) -> FlightWaypoint:
return FlightWaypoint(

View File

@@ -16,9 +16,8 @@ class SeadFlightPlan(FormationAttackFlightPlan):
def builder_type() -> Type[Builder]:
return Builder
@property
def lead_time(self) -> timedelta:
return timedelta(minutes=1)
def default_tot_offset(self) -> timedelta:
return -timedelta(minutes=1)
class Builder(FormationAttackBuilder[SeadFlightPlan, FormationAttackLayout]):

View File

@@ -38,10 +38,6 @@ class SweepLayout(LoiterLayout):
class SweepFlightPlan(LoiterFlightPlan):
@property
def lead_time(self) -> timedelta:
return timedelta(minutes=5)
@staticmethod
def builder_type() -> Type[Builder]:
return Builder
@@ -54,9 +50,8 @@ class SweepFlightPlan(LoiterFlightPlan):
def tot_waypoint(self) -> FlightWaypoint:
return self.layout.sweep_end
@property
def tot_offset(self) -> timedelta:
return -self.lead_time
def default_tot_offset(self) -> timedelta:
return -timedelta(minutes=5)
@property
def sweep_start_time(self) -> datetime:

View File

@@ -34,10 +34,6 @@ class TarCapLayout(PatrollingLayout):
class TarCapFlightPlan(PatrollingFlightPlan[TarCapLayout]):
@property
def lead_time(self) -> timedelta:
return timedelta(minutes=2)
@property
def patrol_duration(self) -> timedelta:
# Note that this duration only has an effect if there are no
@@ -64,9 +60,8 @@ class TarCapFlightPlan(PatrollingFlightPlan[TarCapLayout]):
def combat_speed_waypoints(self) -> set[FlightWaypoint]:
return {self.layout.patrol_start, self.layout.patrol_end}
@property
def tot_offset(self) -> timedelta:
return -self.lead_time
def default_tot_offset(self) -> timedelta:
return -timedelta(minutes=2)
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> datetime | None:
if waypoint == self.layout.patrol_end:

View File

@@ -353,6 +353,14 @@ class MissionGenerator:
gen.generate()
def setup_combined_arms(self) -> None:
self.mission.groundControl.pilot_can_control_vehicles = COMBINED_ARMS_SLOTS > 0
self.mission.groundControl.blue_tactical_commander = COMBINED_ARMS_SLOTS
self.mission.groundControl.blue_observer = 1
self.mission.groundControl.blue_game_masters = (
self.game.settings.game_master_slots
)
self.mission.groundControl.blue_tactical_commander = (
self.game.settings.tactical_commander_slots
)
self.mission.groundControl.pilot_can_control_vehicles = (
self.mission.groundControl.blue_tactical_commander > 0
)
self.mission.groundControl.blue_jtac = self.game.settings.jtac_operator_slots
self.mission.groundControl.blue_observer = self.game.settings.observer_slots

View File

@@ -315,6 +315,10 @@ class MissileSiteGenerator(GroundObjectGenerator):
def generate(self) -> None:
super(MissileSiteGenerator, self).generate()
if not self.game.settings.generate_fire_tasks_for_missile_sites:
return
# Note : Only the SCUD missiles group can fire (V1 site cannot fire in game right now)
# TODO : Should be pre-planned ?
# TODO : Add delay to task to spread fire task over mission duration ?

View File

@@ -25,6 +25,7 @@ class SaveGameBundle:
MANUAL_SAVE_NAME = "player.liberation"
LAST_TURN_SAVE_NAME = "last_turn.liberation"
START_OF_TURN_SAVE_NAME = "start_of_turn.liberation"
PRE_SIM_CHECKPOINT_SAVE_NAME = "pre_sim_checkpoint.liberation"
def __init__(self, bundle_path: Path) -> None:
self.bundle_path = bundle_path
@@ -58,6 +59,19 @@ class SaveGameBundle:
game, self.START_OF_TURN_SAVE_NAME, copy_from=self
)
def save_pre_sim_checkpoint(self, game: Game) -> None:
"""Writes the save file for the state before beginning simulation.
This save is the state of the game after the player presses "TAKE OFF", but
before the fast-forward simulation begins. It is not practical to rewind, but
players commonly will want to cancel and continue planning after pressing that
button, so we make a checkpoint that we can reload on abort.
"""
with logged_duration("Saving pre-sim checkpoint"):
self._update_bundle_member(
game, self.PRE_SIM_CHECKPOINT_SAVE_NAME, copy_from=self
)
def load_player(self) -> Game:
"""Loads the save manually created by the player via save/save-as."""
return self._load_from(self.MANUAL_SAVE_NAME)
@@ -70,6 +84,10 @@ class SaveGameBundle:
"""Loads the save automatically created at the end of the last turn."""
return self._load_from(self.LAST_TURN_SAVE_NAME)
def load_pre_sim_checkpoint(self) -> Game:
"""Loads the save automatically created before the simulation began."""
return self._load_from(self.PRE_SIM_CHECKPOINT_SAVE_NAME)
def _load_from(self, name: str) -> Game:
with ZipFile(self.bundle_path) as zip_bundle:
with zip_bundle.open(name, "r") as save:

View File

@@ -51,6 +51,10 @@ class SaveManager:
with self._save_bundle_context() as bundle:
bundle.save_start_of_turn(self.game)
def save_pre_sim_checkpoint(self) -> None:
with self._save_bundle_context() as bundle:
bundle.save_pre_sim_checkpoint(self.game)
def set_loaded_from(self, bundle: SaveGameBundle) -> None:
"""Reconfigures this save manager based on the loaded game.
@@ -81,6 +85,9 @@ class SaveManager:
self.last_saved_bundle = previous_saved_bundle
raise
def load_pre_sim_checkpoint(self) -> Game:
return self.default_save_bundle.load_pre_sim_checkpoint()
@staticmethod
def load_last_turn(bundle_path: Path) -> Game:
return SaveGameBundle(bundle_path).load_last_turn()

View File

@@ -41,7 +41,8 @@ def has_save_compat_for(
"""
def decorator(func: Callable[..., ReturnT]) -> Callable[..., ReturnT]:
if major != MAJOR_VERSION:
# Allow current and previous version to ease cherry-picking.
if major not in {MAJOR_VERSION - 1, MAJOR_VERSION}:
raise DeprecatedSaveCompatError(func.__name__)
return func

View File

@@ -42,6 +42,8 @@ HQ_AUTOMATION_SECTION = "HQ Automation"
MISSION_GENERATOR_PAGE = "Mission Generator"
COMMANDERS_SECTION = "Battlefield Commanders"
GAMEPLAY_SECTION = "Gameplay"
# TODO: Make sections a type and add headers.
@@ -310,6 +312,41 @@ class Settings:
reserves_procurement_target: int = 10
# Mission Generator
# Commanders
game_master_slots: int = bounded_int_option(
"Game master",
page=MISSION_GENERATOR_PAGE,
section=COMMANDERS_SECTION,
default=0,
min=0,
max=100,
)
tactical_commander_slots: int = bounded_int_option(
"Tactical commander",
page=MISSION_GENERATOR_PAGE,
section=COMMANDERS_SECTION,
default=1,
min=0,
max=100,
)
jtac_operator_slots: int = bounded_int_option(
"JTAC/Operator",
page=MISSION_GENERATOR_PAGE,
section=COMMANDERS_SECTION,
default=0,
min=0,
max=100,
)
observer_slots: int = bounded_int_option(
"Observer",
page=MISSION_GENERATOR_PAGE,
section=COMMANDERS_SECTION,
default=1,
min=0,
max=100,
)
# Gameplay
fast_forward_to_first_contact: bool = boolean_option(
"Fast forward mission to first contact (WIP)",
@@ -324,6 +361,17 @@ class Settings:
"modifications."
),
)
reload_pre_sim_checkpoint_on_abort: bool = boolean_option(
"Reset mission to pre-take off conditions on abort",
page=MISSION_GENERATOR_PAGE,
section=GAMEPLAY_SECTION,
default=True,
detail=(
"If enabled, the game will automatically reload a pre-take off save when "
"aborting take off. If this is disabled, you will need to manually re-load "
"your game after aborting take off."
),
)
player_mission_interrupts_sim_at: Optional[StartType] = choices_option(
"Player missions interrupt fast forward",
page=MISSION_GENERATOR_PAGE,
@@ -443,6 +491,16 @@ class Settings:
section=PERFORMANCE_SECTION,
default=True,
)
generate_fire_tasks_for_missile_sites: bool = boolean_option(
"Generate fire tasks for missile sites",
page=MISSION_GENERATOR_PAGE,
section=PERFORMANCE_SECTION,
detail=(
"If enabled, missile sites like V2s and Scuds will fire on random targets "
"at the start of the mission."
),
default=True,
)
perf_moving_units: bool = boolean_option(
"Moving ground units",
page=MISSION_GENERATOR_PAGE,
@@ -523,6 +581,10 @@ class Settings:
with settings_path.open(encoding="utf-8") as settings_file:
data = yaml.safe_load(settings_file)
if data is None:
logging.warning("Saved settings file %s is empty", settings_path)
return
expected_types = get_type_hints(Settings)
for key, value in data.items():
if key not in self.__dict__:

View File

@@ -11,12 +11,12 @@ from game.ato.flightstate import (
Uninitialized,
)
from .combat import CombatInitiator, FrozenCombat
from .gameupdateevents import GameUpdateEvents
from .simulationresults import SimulationResults
if TYPE_CHECKING:
from game import Game
from game.ato import Flight
from .gameupdateevents import GameUpdateEvents
class AircraftSimulation:
@@ -72,6 +72,7 @@ class AircraftSimulation:
def reset(self) -> None:
for flight in self.iter_flights():
flight.set_state(Uninitialized(flight, self.game.settings))
self.combats = []
def iter_flights(self) -> Iterator[Flight]:
packages = itertools.chain(

View File

@@ -39,6 +39,7 @@ class GameLoop:
def start(self) -> None:
if self.started:
raise RuntimeError("Cannot start game loop because it has already started")
self.game.save_manager.save_pre_sim_checkpoint()
self.started = True
self.sim.begin_simulation()

View File

@@ -5,7 +5,8 @@ import random
from collections.abc import Iterable
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional, Sequence, TYPE_CHECKING
from typing import Optional, Sequence, TYPE_CHECKING, Any
from uuid import uuid4, UUID
from faker import Faker
@@ -13,6 +14,7 @@ from game.ato import Flight, FlightType, Package
from game.settings import AutoAtoBehavior, Settings
from .pilot import Pilot, PilotStatus
from ..db.database import Database
from ..savecompat import has_save_compat_for
from ..utils import meters
if TYPE_CHECKING:
@@ -26,6 +28,8 @@ if TYPE_CHECKING:
@dataclass
class Squadron:
id: UUID = field(init=False, default_factory=uuid4)
name: str
nickname: Optional[str]
country: str
@@ -61,21 +65,24 @@ class Squadron:
untasked_aircraft: int = field(init=False, hash=False, compare=False, default=0)
pending_deliveries: int = field(init=False, hash=False, compare=False, default=0)
@has_save_compat_for(7)
def __setstate__(self, state: dict[str, Any]) -> None:
if "id" not in state:
state["id"] = uuid4()
self.__dict__.update(state)
def __str__(self) -> str:
if self.nickname is None:
return self.name
return f'{self.name} "{self.nickname}"'
def __hash__(self) -> int:
return hash(
(
self.name,
self.nickname,
self.country,
self.role,
self.aircraft,
)
)
return hash(self.id)
def __eq__(self, other: object) -> bool:
if not isinstance(other, Squadron):
return False
return self.id == other.id
@property
def player(self) -> bool:

View File

@@ -434,6 +434,14 @@ class MissileSiteGroundObject(TheaterGroundObject):
def should_head_to_conflict(self) -> bool:
return True
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from game.ato import FlightType
if not self.is_friendly(for_player):
yield FlightType.BAI
for mission_type in super().mission_types(for_player):
yield mission_type
class CoastalSiteGroundObject(TheaterGroundObject):
def __init__(
@@ -466,6 +474,14 @@ class CoastalSiteGroundObject(TheaterGroundObject):
def should_head_to_conflict(self) -> bool:
return True
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from game.ato import FlightType
if not self.is_friendly(for_player):
yield FlightType.BAI
for mission_type in super().mission_types(for_player):
yield mission_type
class IadsGroundObject(TheaterGroundObject, ABC):
def __init__(

View File

@@ -2,8 +2,8 @@ from pathlib import Path
MAJOR_VERSION = 7
MINOR_VERSION = 0
MICRO_VERSION = 0
MINOR_VERSION = 1
MICRO_VERSION = 1
VERSION_NUMBER = ".".join(str(v) for v in (MAJOR_VERSION, MINOR_VERSION, MICRO_VERSION))
@@ -175,4 +175,11 @@ VERSION = _build_version_string()
#:
#: Version 10.7
#: * Support for defining squadron sizes.
CAMPAIGN_FORMAT_VERSION = (10, 7)
#:
#: Version 10.8
#: * Support for Normandy 2.
#:
#: Version 10.9
#: * Campaign is compatible with new squadron rules. The default air wing configuration
#: has enough parking available at each base when squadrons begin at full strength.
CAMPAIGN_FORMAT_VERSION = (10, 9)

View File

@@ -1,17 +1,19 @@
from __future__ import annotations
import argparse
import logging
import ntpath
import os
import sys
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Optional
import yaml
from PySide6 import QtWidgets
from PySide6.QtCore import Qt
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import QApplication, QCheckBox, QSplashScreen
from PySide6.QtWidgets import QApplication, QCheckBox, QSplashScreen, QDialog
from dcs.payloads import PayloadDirectories
from game import Game, VERSION, logging_config, persistence
@@ -34,6 +36,7 @@ from qt_ui import (
uiconstants,
)
from qt_ui.uiflags import UiFlags
from qt_ui.windows.AirWingConfigurationDialog import AirWingConfigurationDialog
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.QLiberationWindow import QLiberationWindow
from qt_ui.windows.preferences.QLiberationFirstStartWindow import (
@@ -67,7 +70,7 @@ def on_game_load(game: Game | None) -> None:
EventStream.put_nowait(GameUpdateEvents().game_loaded(game))
def run_ui(game: Game | None, ui_flags: UiFlags) -> None:
def run_ui(create_game_params: CreateGameParams | None, ui_flags: UiFlags) -> None:
os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "1" # Potential fix for 4K screens
QApplication.setHighDpiScaleFactorRoundingPolicy(
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
@@ -151,6 +154,11 @@ def run_ui(game: Game | None, ui_flags: UiFlags) -> None:
GameUpdateSignal()
GameUpdateSignal.get_instance().game_loaded.connect(on_game_load)
game: Game | None = None
if create_game_params is not None:
with logged_duration("New game creation"):
game = create_game(create_game_params)
# Start window
window = QLiberationWindow(game, ui_flags)
window.showMaximized()
@@ -253,6 +261,12 @@ def parse_args() -> argparse.Namespace:
"--advanced-iads", action="store_true", help="Enable advanced IADS."
)
new_game.add_argument(
"--show-air-wing-config",
action="store_true",
help="Show the air wing configuration dialog after generating the game.",
)
lint_weapons = subparsers.add_parser("lint-weapons")
lint_weapons.add_argument("aircraft", help="Name of the aircraft variant to lint.")
@@ -261,60 +275,68 @@ def parse_args() -> argparse.Namespace:
return parser.parse_args()
def create_game(
campaign_path: Path,
blue: str,
red: str,
supercarrier: bool,
auto_procurement: bool,
inverted: bool,
cheats: bool,
start_date: datetime,
restrict_weapons_by_date: bool,
advanced_iads: bool,
use_new_squadron_rules: bool,
) -> Game:
first_start = liberation_install.init()
if first_start:
sys.exit(
"Cannot generate campaign without configuring DCS Liberation. Start the UI "
"for the first run configuration."
@dataclass(frozen=True)
class CreateGameParams:
campaign_path: Path
blue: str
red: str
supercarrier: bool
auto_procurement: bool
inverted: bool
cheats: bool
start_date: datetime
restrict_weapons_by_date: bool
advanced_iads: bool
use_new_squadron_rules: bool
show_air_wing_config: bool
@staticmethod
def from_args(args: argparse.Namespace) -> CreateGameParams | None:
if args.subcommand != "new-game":
return None
return CreateGameParams(
args.campaign,
args.blue,
args.red,
args.supercarrier,
args.auto_procurement,
args.inverted,
args.cheats,
args.date,
args.restrict_weapons_by_date,
args.advanced_iads,
args.use_new_squadron_rules,
args.show_air_wing_config,
)
# This needs to run before the pydcs payload cache is created, which happens
# extremely early. It's not a problem that we inject these paths twice because we'll
# get the same answers each time.
#
# Without this, it is not possible to use next turn (or anything that needs to check
# for loadouts) without saving the generated campaign and reloading it the normal
# way.
inject_custom_payloads(Path(persistence.base_path()))
campaign = Campaign.from_file(campaign_path)
theater = campaign.load_theater(advanced_iads)
def create_game(params: CreateGameParams) -> Game:
campaign = Campaign.from_file(params.campaign_path)
theater = campaign.load_theater(params.advanced_iads)
faction_loader = Factions.load()
lua_plugin_manager = LuaPluginManager.load()
lua_plugin_manager.merge_player_settings()
generator = GameGenerator(
faction_loader.get_by_name(blue),
faction_loader.get_by_name(red),
faction_loader.get_by_name(params.blue),
faction_loader.get_by_name(params.red),
theater,
campaign.load_air_wing_config(theater),
Settings(
supercarrier=supercarrier,
automate_runway_repair=auto_procurement,
automate_front_line_reinforcements=auto_procurement,
automate_aircraft_reinforcements=auto_procurement,
enable_frontline_cheats=cheats,
enable_base_capture_cheat=cheats,
restrict_weapons_by_date=restrict_weapons_by_date,
enable_squadron_aircraft_limits=use_new_squadron_rules,
supercarrier=params.supercarrier,
automate_runway_repair=params.auto_procurement,
automate_front_line_reinforcements=params.auto_procurement,
automate_aircraft_reinforcements=params.auto_procurement,
enable_frontline_cheats=params.cheats,
enable_base_capture_cheat=params.cheats,
restrict_weapons_by_date=params.restrict_weapons_by_date,
enable_squadron_aircraft_limits=params.use_new_squadron_rules,
),
GeneratorSettings(
start_date=start_date,
start_date=params.start_date,
start_time=campaign.recommended_start_time,
player_budget=DEFAULT_BUDGET,
enemy_budget=DEFAULT_BUDGET,
inverted=inverted,
inverted=params.inverted,
advanced_iads=theater.iads_network.advanced_iads,
no_carrier=False,
no_lha=False,
@@ -334,7 +356,10 @@ def create_game(
lua_plugin_manager,
)
game = generator.generate()
game.begin_turn_0(squadrons_start_full=use_new_squadron_rules)
if params.show_air_wing_config:
if AirWingConfigurationDialog(game, None).exec() == QDialog.DialogCode.Rejected:
sys.exit("Aborted air wing configuration")
game.begin_turn_0(squadrons_start_full=params.use_new_squadron_rules)
return game
@@ -405,8 +430,6 @@ def main():
"Installation path contains non-ASCII characters. This is known to cause problems."
)
game: Optional[Game] = None
args = parse_args()
# TODO: Flesh out data and then make unconditional.
@@ -415,21 +438,6 @@ def main():
load_mods()
if args.subcommand == "new-game":
with logged_duration("New game creation"):
game = create_game(
args.campaign,
args.blue,
args.red,
args.supercarrier,
args.auto_procurement,
args.inverted,
args.cheats,
args.date,
args.restrict_weapons_by_date,
args.advanced_iads,
args.use_new_squadron_rules,
)
if args.subcommand == "lint-weapons":
lint_weapon_data_for_aircraft(AircraftType.named(args.aircraft))
return
@@ -438,7 +446,10 @@ def main():
return
with Server().run_in_thread():
run_ui(game, UiFlags(args.dev, args.show_sim_speed_controls))
run_ui(
CreateGameParams.from_args(args),
UiFlags(args.dev, args.show_sim_speed_controls),
)
if __name__ == "__main__":

View File

@@ -1,5 +1,5 @@
from datetime import datetime
from typing import List, Optional
from typing import List, Optional, Callable
from PySide6.QtWidgets import (
QDialog,
@@ -33,11 +33,16 @@ from qt_ui.windows.QWaitingForMissionResultWindow import QWaitingForMissionResul
class QTopPanel(QFrame):
def __init__(
self, game_model: GameModel, sim_controller: SimController, ui_flags: UiFlags
self,
game_model: GameModel,
sim_controller: SimController,
ui_flags: UiFlags,
reset_to_pre_sim_checkpoint: Callable[[], None],
) -> None:
super(QTopPanel, self).__init__()
self.game_model = game_model
self.sim_controller = sim_controller
self.reset_to_pre_sim_checkpoint = reset_to_pre_sim_checkpoint
self.dialog: Optional[QDialog] = None
self.setMaximumHeight(70)
@@ -293,7 +298,9 @@ class QTopPanel(QFrame):
persistence.mission_path_for("liberation_nextturn.miz")
)
waiting = QWaitingForMissionResultWindow(self.game, self.sim_controller, self)
waiting = QWaitingForMissionResultWindow(
self.game, self.sim_controller, self.reset_to_pre_sim_checkpoint, self
)
waiting.exec_()
def budget_update(self, game: Game):

View File

@@ -1,3 +1,5 @@
import textwrap
from collections import defaultdict
from typing import Iterable, Iterator, Optional
from PySide6.QtCore import (
@@ -150,6 +152,49 @@ class SquadronSizeSpinner(QSpinBox):
# return size
class AirWingConfigParkingTracker(QWidget):
allocation_changed = Signal()
def __init__(self, game: Game) -> None:
super().__init__()
self.theater = game.theater
self.by_cp: dict[ControlPoint, set[Squadron]] = defaultdict(set)
for coalition in game.coalitions:
for squadron in coalition.air_wing.iter_squadrons():
self.add_squadron(squadron)
def add_squadron(self, squadron: Squadron) -> None:
self.by_cp[squadron.location].add(squadron)
self.signal_change()
def remove_squadron(self, squadron: Squadron) -> None:
self.by_cp[squadron.location].remove(squadron)
self.signal_change()
def relocate_squadron(
self,
squadron: Squadron,
prior_location: ControlPoint,
new_location: ControlPoint,
) -> None:
self.by_cp[prior_location].remove(squadron)
self.by_cp[new_location].add(squadron)
squadron.relocate_to(new_location)
self.signal_change()
def used_parking_at(self, control_point: ControlPoint) -> int:
return sum(s.max_size for s in self.by_cp[control_point])
def iter_overfull(self) -> Iterator[tuple[ControlPoint, int, list[Squadron]]]:
for control_point in self.theater.controlpoints:
used = self.used_parking_at(control_point)
if used > control_point.total_aircraft_parking:
yield control_point, used, list(self.by_cp[control_point])
def signal_change(self) -> None:
self.allocation_changed.emit()
class SquadronConfigurationBox(QGroupBox):
remove_squadron_signal = Signal(Squadron)
@@ -158,11 +203,13 @@ class SquadronConfigurationBox(QGroupBox):
game: Game,
coalition: Coalition,
squadron: Squadron,
parking_tracker: AirWingConfigParkingTracker,
) -> None:
super().__init__()
self.game = game
self.coalition = coalition
self.squadron = squadron
self.parking_tracker = parking_tracker
columns = QHBoxLayout()
self.setLayout(columns)
@@ -200,6 +247,7 @@ class SquadronConfigurationBox(QGroupBox):
left_column.addLayout(size_column)
size_column.addWidget(QLabel("Max size:"))
self.max_size_selector = SquadronSizeSpinner(self.squadron.max_size, self)
self.max_size_selector.valueChanged.connect(self.update_max_size)
size_column.addWidget(self.max_size_selector)
task_column = QVBoxLayout()
@@ -214,8 +262,14 @@ class SquadronConfigurationBox(QGroupBox):
squadron.location,
squadron.aircraft,
)
self.base_selector.currentIndexChanged.connect(self.relocate_squadron)
left_column.addWidget(self.base_selector)
self.parking_label = QLabel()
self.update_parking_label()
self.parking_tracker.allocation_changed.connect(self.update_parking_label)
left_column.addWidget(self.parking_label)
if not squadron.player and squadron.aircraft.flyable:
player_label = QLabel("Player slots not available for opfor")
elif not squadron.aircraft.flyable:
@@ -266,9 +320,26 @@ class SquadronConfigurationBox(QGroupBox):
self.player_list.setText(
"<br />".join(p.name for p in self.claim_players_from_squadron())
)
self.update_parking_label()
finally:
self.blockSignals(old_state)
def update_parking_label(self) -> None:
self.parking_label.setText(
f"{self.parking_tracker.used_parking_at(self.squadron.location)}/"
f"{self.squadron.location.total_aircraft_parking}"
)
def update_max_size(self) -> None:
self.squadron.max_size = self.max_size_selector.value()
self.parking_tracker.signal_change()
def relocate_squadron(self) -> None:
location = self.base_selector.currentData()
self.parking_tracker.relocate_squadron(
self.squadron, self.squadron.location, location
)
def remove_from_squadron_config(self) -> None:
self.remove_squadron_signal.emit(self.squadron)
@@ -321,6 +392,7 @@ class SquadronConfigurationBox(QGroupBox):
self.squadron = new_squadron
self.bind_data()
self.mission_types.replace_squadron(self.squadron)
self.parking_tracker.signal_change()
def reset_title(self) -> None:
self.setTitle(f"{self.name_edit.text()} - {self.squadron.aircraft}")
@@ -361,11 +433,13 @@ class SquadronConfigurationLayout(QVBoxLayout):
game: Game,
coalition: Coalition,
squadrons: list[Squadron],
parking_tracker: AirWingConfigParkingTracker,
) -> None:
super().__init__()
self.game = game
self.coalition = coalition
self.squadron_configs = []
self.parking_tracker = parking_tracker
for squadron in squadrons:
self.add_squadron(squadron)
@@ -376,6 +450,7 @@ class SquadronConfigurationLayout(QVBoxLayout):
return keep_squadrons
def remove_squadron(self, squadron: Squadron) -> None:
self.parking_tracker.remove_squadron(squadron)
for squadron_config in self.squadron_configs:
if squadron_config.squadron == squadron:
squadron_config.deleteLater()
@@ -386,23 +461,32 @@ class SquadronConfigurationLayout(QVBoxLayout):
return
def add_squadron(self, squadron: Squadron) -> None:
squadron_config = SquadronConfigurationBox(self.game, self.coalition, squadron)
squadron_config = SquadronConfigurationBox(
self.game, self.coalition, squadron, self.parking_tracker
)
squadron_config.remove_squadron_signal.connect(self.remove_squadron)
self.squadron_configs.append(squadron_config)
self.addWidget(squadron_config)
self.parking_tracker.add_squadron(squadron)
class AircraftSquadronsPage(QWidget):
remove_squadron_page = Signal(AircraftType)
def __init__(
self, game: Game, coalition: Coalition, squadrons: list[Squadron]
self,
game: Game,
coalition: Coalition,
squadrons: list[Squadron],
parking_tracker: AirWingConfigParkingTracker,
) -> None:
super().__init__()
layout = QVBoxLayout()
self.setLayout(layout)
self.squadrons_config = SquadronConfigurationLayout(game, coalition, squadrons)
self.squadrons_config = SquadronConfigurationLayout(
game, coalition, squadrons, parking_tracker
)
self.squadrons_config.config_changed.connect(self.on_squadron_config_changed)
scrolling_widget = QWidget()
@@ -430,10 +514,16 @@ class AircraftSquadronsPage(QWidget):
class AircraftSquadronsPanel(QStackedLayout):
page_removed = Signal(AircraftType)
def __init__(self, game: Game, coalition: Coalition) -> None:
def __init__(
self,
game: Game,
coalition: Coalition,
parking_tracker: AirWingConfigParkingTracker,
) -> None:
super().__init__()
self.game = game
self.coalition = coalition
self.parking_tracker = parking_tracker
self.squadrons_pages: dict[AircraftType, AircraftSquadronsPage] = {}
for aircraft, squadrons in self.air_wing.squadrons.items():
self.new_page_for_type(aircraft, squadrons)
@@ -453,7 +543,9 @@ class AircraftSquadronsPanel(QStackedLayout):
def new_page_for_type(
self, aircraft_type: AircraftType, squadrons: list[Squadron]
) -> None:
page = AircraftSquadronsPage(self.game, self.coalition, squadrons)
page = AircraftSquadronsPage(
self.game, self.coalition, squadrons, self.parking_tracker
)
page.remove_squadron_page.connect(self.remove_page_for_type)
self.addWidget(page)
self.squadrons_pages[aircraft_type] = page
@@ -539,14 +631,70 @@ class AircraftTypeList(QListView):
self.update(self.selectionModel().currentIndex())
def describe_overfull_airbases(
overfull: Iterable[tuple[ControlPoint, int, list[Squadron]]]
) -> str:
string_builder = []
for (
control_point,
used_parking,
squadrons,
) in overfull:
capacity = control_point.total_aircraft_parking
base_description = f"{control_point.name} {used_parking}/{capacity}"
string_builder.append(f"<p><strong>{base_description}</strong></p>")
squadron_descriptions = []
for squadron in squadrons:
squadron_details = (
f"{squadron.aircraft} {squadron.name} {squadron.max_size} aircraft"
)
squadron_descriptions.append(f"<li>{squadron_details}</li>")
string_builder.append(f"<ul>{''.join(squadron_descriptions)}</ul>")
if not string_builder:
string_builder.append("All airbases are within parking limits.")
return "".join(string_builder)
class OverfullAirbasesDisplay(QGroupBox):
def __init__(
self,
parking_tracker: AirWingConfigParkingTracker,
parent: QWidget | None = None,
) -> None:
super().__init__("Overfull airbases", parent)
self.parking_tracker = parking_tracker
self.parking_tracker.allocation_changed.connect(self.on_allocation_changed)
layout = QVBoxLayout()
self.setLayout(layout)
self.label = QLabel()
layout.addWidget(self.label)
self.on_allocation_changed()
def on_allocation_changed(self) -> None:
self.label.setText(
describe_overfull_airbases(self.parking_tracker.iter_overfull())
)
class AirWingConfigurationTab(QWidget):
def __init__(self, coalition: Coalition, game: Game) -> None:
def __init__(
self,
coalition: Coalition,
game: Game,
parking_tracker: AirWingConfigParkingTracker,
) -> None:
super().__init__()
layout = QGridLayout()
self.setLayout(layout)
self.game = game
self.coalition = coalition
self.parking_tracker = parking_tracker
self.type_list = AircraftTypeList(coalition.air_wing)
@@ -556,7 +704,9 @@ class AirWingConfigurationTab(QWidget):
add_button.clicked.connect(lambda state: self.add_squadron())
layout.addWidget(add_button, 2, 1, 1, 1)
self.squadrons_panel = AircraftSquadronsPanel(game, coalition)
self.squadrons_panel = AircraftSquadronsPanel(
game, coalition, self.parking_tracker
)
self.squadrons_panel.page_removed.connect(self.type_list.remove_aircraft_type)
layout.addLayout(self.squadrons_panel, 1, 3, 2, 1)
@@ -630,6 +780,9 @@ class AirWingConfigurationDialog(QDialog):
def __init__(self, game: Game, parent) -> None:
super().__init__(parent)
self.game = game
self.parking_tracker = AirWingConfigParkingTracker(game)
self.setMinimumSize(1024, 768)
self.setWindowTitle(f"Air Wing Configuration")
# TODO: self.setWindowIcon()
@@ -651,11 +804,18 @@ class AirWingConfigurationDialog(QDialog):
self.tabs = []
for coalition in game.coalitions:
coalition_tab = AirWingConfigurationTab(coalition, game)
coalition_tab = AirWingConfigurationTab(
coalition, game, self.parking_tracker
)
name = "Blue" if coalition.player else "Red"
self.tab_widget.addTab(coalition_tab, name)
self.tabs.append(coalition_tab)
self.overfull_airbases_display = OverfullAirbasesDisplay(
self.parking_tracker, self
)
layout.addWidget(self.overfull_airbases_display)
buttons_layout = QHBoxLayout()
apply_button = QPushButton("Accept Changes && Start Campaign")
apply_button.setProperty("style", "btn-accept")
@@ -671,7 +831,29 @@ class AirWingConfigurationDialog(QDialog):
for tab in self.tabs:
tab.revert()
def can_continue(self) -> bool:
if not self.game.settings.enable_squadron_aircraft_limits:
return True
overfull = list(self.parking_tracker.iter_overfull())
if not overfull:
return True
description = (
"<p>The following airbases are over capacity:</p>"
f"{describe_overfull_airbases(overfull)}"
)
QMessageBox().critical(
self,
"Cannot continue with overfull bases",
description,
QMessageBox.Ok,
)
return False
def accept(self) -> None:
if not self.can_continue():
return
for tab in self.tabs:
tab.apply()
super().accept()
@@ -679,8 +861,16 @@ class AirWingConfigurationDialog(QDialog):
def reject(self) -> None:
result = QMessageBox.information(
None,
"Discard changes?",
"Are you sure you want to discard your changes and start the campaign?",
"Abort new game?",
"<br />".join(
textwrap.wrap(
"Are you sure you want to cancel air wing configuration and "
"return to the new game wizard? If you instead want to revert your "
"air wing changes and continue, use the revert and accept buttons "
"below.",
width=55,
)
),
QMessageBox.Yes,
QMessageBox.No,
)

View File

@@ -133,7 +133,14 @@ class QLiberationWindow(QMainWindow):
vbox = QVBoxLayout()
vbox.setContentsMargins(0, 0, 0, 0)
vbox.addWidget(QTopPanel(self.game_model, self.sim_controller, ui_flags))
vbox.addWidget(
QTopPanel(
self.game_model,
self.sim_controller,
ui_flags,
self.reset_to_pre_sim_checkpoint,
)
)
vbox.addWidget(hbox)
central_widget = QWidget()
@@ -340,6 +347,23 @@ class QLiberationWindow(QMainWindow):
except Exception:
logging.exception("Error loading save game %s", file[0])
def reset_to_pre_sim_checkpoint(self) -> None:
"""Loads the game that was saved before pressing the take-off button.
A checkpoint will be saved when the player presses take-off to save their state
before the mission simulation begins. If the mission is aborted, we usually want
to reset to the pre-simulation state to allow players to effectively "rewind",
since they probably aborted so that they could make changes. Implementing rewind
for real is impractical, but checkpoints are easy.
"""
if self.game is None:
raise RuntimeError(
"Cannot reset to pre-sim checkpoint when no game is loaded"
)
GameUpdateSignal.get_instance().game_loaded.emit(
self.game.save_manager.load_pre_sim_checkpoint()
)
def saveGame(self):
logging.info("Saving game")

View File

@@ -2,7 +2,7 @@ from __future__ import annotations
import logging
from pathlib import Path
from typing import Optional
from typing import Optional, Callable
from PySide6 import QtCore
from PySide6.QtCore import QObject, Signal
@@ -52,12 +52,14 @@ class QWaitingForMissionResultWindow(QDialog):
self,
game: Game,
sim_controller: SimController,
reset_to_pre_sim_checkpoint: Callable[[], None],
parent: Optional[QWidget] = None,
) -> None:
super(QWaitingForMissionResultWindow, self).__init__(parent=parent)
self.setWindowModality(QtCore.Qt.WindowModal)
self.game = game
self.sim_controller = sim_controller
self.reset_to_pre_sim_checkpoint = reset_to_pre_sim_checkpoint
self.setWindowTitle("Waiting for mission completion.")
self.setWindowIcon(QIcon("./resources/icon.png"))
self.setMinimumHeight(570)
@@ -111,7 +113,7 @@ class QWaitingForMissionResultWindow(QDialog):
self.manually_submit.clicked.connect(self.submit_manually)
self.actions_layout.addWidget(self.manually_submit)
self.cancel = QPushButton("Abort mission")
self.cancel.clicked.connect(self.close)
self.cancel.clicked.connect(self.reject)
self.actions_layout.addWidget(self.cancel)
self.gridLayout.addWidget(self.actions, 2, 0)
@@ -122,7 +124,7 @@ class QWaitingForMissionResultWindow(QDialog):
self.manually_submit2.clicked.connect(self.submit_manually)
self.actions2_layout.addWidget(self.manually_submit2)
self.cancel2 = QPushButton("Abort mission")
self.cancel2.clicked.connect(self.close)
self.cancel2.clicked.connect(self.reject)
self.actions2_layout.addWidget(self.cancel2)
self.proceed = QPushButton("Accept results")
self.proceed.setProperty("style", "btn-success")
@@ -133,6 +135,11 @@ class QWaitingForMissionResultWindow(QDialog):
self.layout.addLayout(self.gridLayout, 1, 0)
self.setLayout(self.layout)
def reject(self) -> None:
if self.game.settings.reload_pre_sim_checkpoint_on_abort:
self.reset_to_pre_sim_checkpoint()
super().reject()
@staticmethod
def add_update_row(description: str, count: int, layout: QGridLayout) -> None:
row = layout.rowCount()
@@ -217,7 +224,7 @@ class QWaitingForMissionResultWindow(QDialog):
GameUpdateSignal.get_instance().sendDebriefing(self.debriefing)
GameUpdateSignal.get_instance().updateGame(self.game)
self.close()
self.accept()
def closeEvent(self, evt):
super(QWaitingForMissionResultWindow, self).closeEvent(evt)

View File

@@ -29,6 +29,7 @@ class QEditFlightDialog(QDialog):
self.setWindowTitle("Edit flight")
self.setWindowIcon(EVENT_ICONS["strike"])
self.setModal(True)
layout = QVBoxLayout()

View File

@@ -1,6 +1,16 @@
import logging
from datetime import timedelta
from PySide6.QtWidgets import QGroupBox, QLabel, QMessageBox, QVBoxLayout
from PySide6.QtCore import QTime
from PySide6.QtWidgets import (
QGroupBox,
QLabel,
QMessageBox,
QVBoxLayout,
QTimeEdit,
QHBoxLayout,
QCheckBox,
)
from game import Game
from game.ato.flight import Flight
@@ -10,9 +20,9 @@ from qt_ui.widgets.QLabeledWidget import QLabeledWidget
from qt_ui.widgets.combos.QArrivalAirfieldSelector import QArrivalAirfieldSelector
class FlightAirfieldDisplay(QGroupBox):
class FlightPlanPropertiesGroup(QGroupBox):
def __init__(self, game: Game, package_model: PackageModel, flight: Flight) -> None:
super().__init__("Departure/Arrival")
super().__init__("Flight plan properties")
self.game = game
self.package_model = package_model
self.flight = flight
@@ -28,6 +38,31 @@ class FlightAirfieldDisplay(QGroupBox):
self.package_model.tot_changed.connect(self.update_departure_time)
self.update_departure_time()
tot_offset_layout = QHBoxLayout()
layout.addLayout(tot_offset_layout)
delay = int(self.flight.flight_plan.tot_offset.total_seconds())
negative = delay < 0
if negative:
delay = -delay
hours = delay // 3600
minutes = delay // 60 % 60
seconds = delay % 60
tot_offset_layout.addWidget(QLabel("TOT Offset (minutes:seconds)"))
tot_offset_layout.addStretch()
negative_offset_checkbox = QCheckBox("Ahead of package")
negative_offset_checkbox.setChecked(negative)
negative_offset_checkbox.toggled.connect(self.toggle_negative_offset)
tot_offset_layout.addWidget(negative_offset_checkbox)
self.tot_offset_spinner = QTimeEdit(QTime(hours, minutes, seconds))
self.tot_offset_spinner.setMaximumTime(QTime(59, 0))
self.tot_offset_spinner.setDisplayFormat("mm:ss")
self.tot_offset_spinner.timeChanged.connect(self.set_tot_offset)
self.tot_offset_spinner.setToolTip("Flight TOT offset from package TOT")
tot_offset_layout.addWidget(self.tot_offset_spinner)
layout.addWidget(
QLabel(
"Determined based on the package TOT. Edit the "
@@ -58,7 +93,7 @@ class FlightAirfieldDisplay(QGroupBox):
# is an invalid state for calling anything in TotEstimator.
return
self.departure_time.setText(
f"At {self.flight.flight_plan.startup_time():%H:%M%S}"
f"At {self.flight.flight_plan.startup_time():%H:%M:%S}"
)
def set_divert(self, index: int) -> None:
@@ -76,3 +111,13 @@ class FlightAirfieldDisplay(QGroupBox):
QMessageBox.critical(
self, "Could not update flight plan", str(ex), QMessageBox.Ok
)
def set_tot_offset(self, offset: QTime) -> None:
self.flight.flight_plan.tot_offset = timedelta(
hours=offset.hour(), minutes=offset.minute(), seconds=offset.second()
)
self.update_departure_time()
def toggle_negative_offset(self) -> None:
self.flight.flight_plan.tot_offset = -self.flight.flight_plan.tot_offset
self.update_departure_time()

View File

@@ -4,15 +4,15 @@ from PySide6.QtWidgets import QFrame, QGridLayout, QVBoxLayout
from game import Game
from game.ato.flight import Flight
from qt_ui.models import PackageModel
from qt_ui.windows.mission.flight.settings.FlightAirfieldDisplay import (
FlightAirfieldDisplay,
from qt_ui.windows.mission.flight.settings.FlightPlanPropertiesGroup import (
FlightPlanPropertiesGroup,
)
from qt_ui.windows.mission.flight.settings.QCustomName import QFlightCustomName
from qt_ui.windows.mission.flight.settings.QFlightSlotEditor import QFlightSlotEditor
from qt_ui.windows.mission.flight.settings.QFlightStartType import QFlightStartType
from qt_ui.windows.mission.flight.settings.QFlightTypeTaskInfo import (
QFlightTypeTaskInfo,
)
from qt_ui.windows.mission.flight.settings.QCustomName import QFlightCustomName
class QGeneralFlightSettingsTab(QFrame):
@@ -23,7 +23,7 @@ class QGeneralFlightSettingsTab(QFrame):
layout = QGridLayout()
layout.addWidget(QFlightTypeTaskInfo(flight), 0, 0)
layout.addWidget(FlightAirfieldDisplay(game, package_model, flight), 1, 0)
layout.addWidget(FlightPlanPropertiesGroup(game, package_model, flight), 1, 0)
layout.addWidget(QFlightSlotEditor(package_model, flight, game), 2, 0)
layout.addWidget(QFlightStartType(package_model, flight), 3, 0)
layout.addWidget(QFlightCustomName(flight), 4, 0)

View File

@@ -1,6 +1,7 @@
from __future__ import unicode_literals
import logging
import textwrap
from datetime import datetime, timedelta
from typing import List
@@ -13,6 +14,7 @@ from PySide6.QtWidgets import (
QTextEdit,
QVBoxLayout,
QWidget,
QDialog,
)
from jinja2 import Environment, FileSystemLoader, select_autoescape
@@ -39,7 +41,6 @@ jinja_env = Environment(
lstrip_blocks=True,
)
"""
Possible time periods for new games
@@ -86,6 +87,10 @@ TIME_PERIODS = {
}
def wrap_label_text(text: str, width: int = 100) -> str:
return "<br />".join(textwrap.wrap(text, width=width))
class NewGameWizard(QtWidgets.QWizard):
def __init__(self, parent=None):
super(NewGameWizard, self).__init__(parent)
@@ -114,7 +119,9 @@ class NewGameWizard(QtWidgets.QWizard):
self.addPage(self.theater_page)
self.addPage(self.faction_selection_page)
self.addPage(GeneratorOptions(default_settings, mod_settings))
self.difficulty_page = DifficultyAndAutomationOptions(default_settings)
self.difficulty_page = DifficultyAndAutomationOptions(
default_settings, self.theater_page.campaignList.selected_campaign
)
self.plugins_page = PluginsPage(self.lua_plugin_manager)
# Update difficulty page on campaign select
@@ -223,7 +230,12 @@ class NewGameWizard(QtWidgets.QWizard):
)
self.generatedGame = generator.generate()
AirWingConfigurationDialog(self.generatedGame, self).exec_()
if (
AirWingConfigurationDialog(self.generatedGame, self).exec()
== QDialog.DialogCode.Rejected
):
logging.info("Aborted air wing configuration")
return
self.generatedGame.begin_turn_0(squadrons_start_full=use_new_squadron_rules)
@@ -567,8 +579,39 @@ class BudgetInputs(QtWidgets.QGridLayout):
self.addWidget(self.starting_money, 1, 1)
class NewSquadronRulesWarning(QLabel):
def __init__(
self, campaign: Campaign | None, parent: QWidget | None = None
) -> None:
super().__init__(parent)
self.set_campaign(campaign)
def set_campaign(self, campaign: Campaign | None) -> None:
if campaign is None:
self.setText("No campaign selected")
return
if campaign.version >= (10, 9):
text = f"{campaign.name} is compatible with the new squadron rules."
elif campaign.version >= (10, 7):
text = (
f"{campaign.name} has been updated since the new squadron rules were "
"introduced, but support for those rules was still optional. You may "
"need to remove, resize, or relocate squadrons before beginning the "
"game."
)
else:
text = (
f"{campaign.name} has not been updated since the new squadron rules. "
"Were introduced. You may need to remove, resize, or relocate "
"squadrons before beginning the game."
)
self.setText(wrap_label_text(text))
class DifficultyAndAutomationOptions(QtWidgets.QWizardPage):
def __init__(self, default_settings: Settings, parent=None) -> None:
def __init__(
self, default_settings: Settings, current_campaign: Campaign | None, parent=None
) -> None:
super().__init__(parent)
self.setTitle("Difficulty and automation options")
@@ -609,10 +652,15 @@ class DifficultyAndAutomationOptions(QtWidgets.QWizardPage):
new_squadron_rules.setChecked(default_settings.enable_squadron_aircraft_limits)
self.registerField("use_new_squadron_rules", new_squadron_rules)
economy_layout.addWidget(new_squadron_rules)
self.new_squadron_rules_warning = NewSquadronRulesWarning(current_campaign)
economy_layout.addWidget(self.new_squadron_rules_warning)
economy_layout.addWidget(
QLabel(
"With new squadron rules enabled, squadrons will not be able to exceed a maximum number of aircraft "
"(configurable), and the campaign will begin with all squadrons at full strength."
wrap_label_text(
"With new squadron rules enabled, squadrons will not be able to "
"exceed a maximum number of aircraft (configurable), and the "
"campaign will begin with all squadrons at full strength."
)
)
)
@@ -650,6 +698,7 @@ class DifficultyAndAutomationOptions(QtWidgets.QWizardPage):
self.enemy_income.spinner.setValue(
int(campaign.recommended_enemy_income_multiplier * 10)
)
self.new_squadron_rules_warning.set_campaign(campaign)
class PluginOptionCheckbox(QCheckBox):

View File

@@ -32,7 +32,7 @@ platformdirs==2.6.2
pluggy==1.0.0
pre-commit==2.21.0
pydantic==1.10.7
git+https://github.com/pydcs/dcs@8fdeda106ba7e847a5d0a1ed358a1463636b513d#egg=pydcs
git+https://github.com/pydcs/dcs@e74c3885a55affda09b36907aa7afe5588b0702f#egg=pydcs
pyinstaller==5.7.0
pyinstaller-hooks-contrib==2022.14
pyproj==3.4.1

View File

@@ -9,7 +9,7 @@ description:
pushing south.</p>
miz: battle_of_abu_dhabi.miz
performance: 2
version: "10.2"
version: "10.9"
squadrons:
# Blue CPs:
# The default faction is Iran, but the F-14B is given higher precedence so
@@ -41,16 +41,20 @@ squadrons:
- F-16CM Fighting Falcon (Block 50)
- F-4E Phantom II
- primary: AEW&C
size: 2
aircraft:
- E-3A
- primary: Refueling
size: 2
aircraft:
- KC-135 Stratotanker
- primary: Transport
size: 4
aircraft:
- C-17A
- primary: Strike
secondary: air-to-ground
size: 4
aircraft:
- B-1B Lancer
- Su-24MK Fencer-D
@@ -72,6 +76,7 @@ squadrons:
- Su-25 Frogfoot
- primary: BAI
secondary: air-to-ground
size: 8
aircraft:
- F-16CM Fighting Falcon (Block 50)
- Su-24MK Fencer-D
@@ -102,6 +107,7 @@ squadrons:
- F/A-18C Hornet (Lot 20)
- F-14A Tomcat (Block 135-GR Late)
- primary: Refueling
size: 2
aircraft:
- S-3B Tanker
@@ -111,6 +117,7 @@ squadrons:
aircraft:
- AV-8B Harrier II Night Attack
- primary: CAS
size: 8
secondary: air-to-ground
aircraft:
- UH-1H Iroquois

View File

@@ -9,7 +9,7 @@ recommended_enemy_faction: Russia 2010
recommended_start_date: 2004-01-07
miz: black_sea.miz
performance: 2
version: "10.7"
version: "10.9"
squadrons:
# Anapa-Vityazevo
12:
@@ -148,6 +148,7 @@ squadrons:
secondary: air-to-ground
aircraft:
- UH-1H Iroquois
size: 8
Red CV:
- primary: BARCAP
secondary: air-to-air
@@ -164,3 +165,4 @@ squadrons:
secondary: air-to-ground
- primary: CAS
secondary: air-to-ground
size: 8

View File

@@ -12,89 +12,61 @@ recommended_enemy_faction: Germany 1944
recommended_start_date: 1944-07-04
miz: caen_to_evreux.miz
performance: 1
version: "10.0"
version: "10.9"
squadrons:
# Evreux
26:
- primary: BARCAP
aircraft:
- Bf 109 K-4 Kurfürst
- primary: BARCAP
aircraft:
- Fw 190 A-8 Anton
- primary: BARCAP
- primary: Escort
aircraft:
- Fw 190 D-9 Dora
size: 12
- primary: Strike
secondary: air-to-ground
aircraft:
- Ju 88 A-4
- primary: AEW&C
- primary: Refueling
- primary: Transport
size: 12
# Conches
40:
- primary: BARCAP
secondary: any
aircraft:
- Bf 109 K-4 Kurfürst
- primary: BARCAP
aircraft:
- Fw 190 A-8 Anton
- primary: BARCAP
aircraft:
- Fw 190 D-9 Dora
- primary: SEAD
secondary: any
- primary: DEAD
secondary: any
size: 4
# Carpiquet
19:
- primary: BARCAP
- primary: CAS
secondary: any
aircraft:
- Thunderbolt Mk.II (Late)
- P-47D-40 Thunderbolt
size: 12
- primary: BARCAP
aircraft:
- Mustang Mk.IV (Late)
- P-51D-30-NA Mustang
- primary: BARCAP
secondary: any
aircraft:
- Spitfire LF Mk IX
- primary: BARCAP
aircraft:
- Spitfire LF Mk IX (Clipped Wings)
- primary: Strike
size: 12
- primary: BAI
secondary: air-to-ground
aircraft:
- MosquitoFBMkVI
- primary: SEAD
secondary: any
- primary: DEAD
secondary: any
size: 12
- primary: OCA/Runway
secondary: air-to-ground
aircraft:
- Boston Mk.III
- A-20G Havoc
size: 10
# Ford_AF
31:
- primary: BARCAP
aircraft:
- Thunderbolt Mk.II (Mid)
- P-47D-30 Thunderbolt (Late)
- primary: BARCAP
aircraft:
- Thunderbolt Mk.II (Early)
- P-47D-30 Thunderbolt (Early)
- primary: BARCAP
- primary: Escort
secondary: air-to-air
aircraft:
- Mustang Mk.IV (Early)
- P-51D-25-NA Mustang
- primary: Strike
secondary: air-to-ground
aircraft:
- Boston Mk.III
- A-20G Havoc
size: 10
- primary: Strike
secondary: air-to-ground
aircraft:
- Fortress Mk.III
- B-17G Flying Fortress
- primary: AEW&C
- primary: Refueling
- primary: Transport
size: 10

View File

@@ -8,7 +8,7 @@ description: <p>Welcome to Vegas Nerve, an asymmetrical Red Flag Exercise scenar
miz: exercise_vegas_nerve.miz
performance: 1
recommended_start_date: 2011-02-24
version: "10.7"
version: "10.9"
squadrons:
# Tonopah Airport
17:

Binary file not shown.

View File

@@ -0,0 +1,175 @@
---
name: Normandy - The Final Countdown II
theater: Normandy
authors: Starfire
recommended_player_faction:
country: Combined Joint Task Forces Blue
name: D-Day Allied Forces 1944 and 1990
authors: Starfire
description:
<p>Faction for Final Countdown II</p>
locales:
- en_US
aircrafts:
- Boston Mk.III
- Fortress Mk.III
- Mustang Mk.IV (Late)
- Spitfire LF Mk IX
- Thunderbolt Mk.II (Late)
- MosquitoFBMkVI
- F-14B Tomcat
- F/A-18C Hornet (Lot 20)
- SH-60B Seahawk
awacs:
- E-2C Hawkeye
tankers:
- S-3B Tanker
frontline_units:
- A17 Light Tank Mk VII Tetrarch
- A22 Infantry Tank MK IV Churchill VII
- A27L Cruiser Tank MK VIII Centaur IV
- A27M Cruiser Tank MK VIII Cromwell IV
- Daimler Armoured Car Mk I
- M2A1 Half-Track
- QF 40 mm Mark III
- Sherman Firefly VC
- Sherman III
artillery_units:
- M12 Gun Motor Carriage
logistics_units:
- Truck Bedford
- Truck GMC "Jimmy" 6x6 Truck
infantry_units:
- Infantry M1 Garand
naval_units:
- DDG Arleigh Burke IIa
- CG Ticonderoga
- CVN-74 John C. Stennis
missiles: []
air_defense_units:
- Bofors 40 mm Gun
preset_groups:
- Ally Flak
requirements:
WW2 Asset Pack: https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/
carrier_names:
- CVN-71 Theodore Roosevelt
has_jtac: true
jtac_unit: MQ-9 Reaper
unrestricted_satnav: true
doctrine: ww2
building_set: ww2ally
recommended_enemy_faction: Germany 1944
description:
<p>While enroute to the Persian Gulf for Operation Desert Shield, the USS Theodore Roosevelt and its carrier strike group are engufled by an electrical vortex and transported through time and space to the English channel on the morning of the Normandy Landings - June 6th 1944. Seeking to reduce the cost in lives to the Allied Forces about to storm the beaches, the captain of the Roosevelt has elected to provide air support for the landings.</p><p><strong>Note:</strong> This campaign has a custom faction that combines modern US naval forces with WW2 Allied forces. To play it as intended, you should carefully ration your use of modern aircraft and not replenish them if shot down (as you cannot get new Tomcats and Hornets in 1944). You can also choose to play it as a purely WW2 campaign by switching to one of the WW2 Ally factions.</p>
miz: final_countdown_2.miz
performance: 2
recommended_start_date: 1944-06-06
version: "10.9"
squadrons:
#Blue CV (90)
Blue-CV:
- primary: TARCAP
secondary: any
aircraft:
- F-14B Tomcat
size: 24
- primary: DEAD
secondary: any
aircraft:
- F/A-18C Hornet (Lot 20)
size: 24
- primary: AEW&C
aircraft:
- E-2C Hawkeye
size: 2
- primary: Refueling
aircraft:
- S-3B Tanker
size: 2
- primary: Air Assault
secondary: any
aircraft:
- SH-60B Seahawk
size: 4
#Stoney Cross (39)
58:
- primary: OCA/Runway
secondary: air-to-ground
aircraft:
- A-20G Havoc
- Boston Mk.III
size: 20
#Needs Oar Point (55)
28:
- primary: BARCAP
secondary: any
aircraft:
- Spitfire LF Mk IX
size: 20
- primary: DEAD
secondary: air-to-ground
aircraft:
- MosquitoFBMkVI
size: 20
#RAF Grafton Underwood (1000)
From RAF Grafton Underwood:
- primary: Strike
secondary: air-to-ground
aircraft:
- B-17G Flying Fortress
- Fortress Mk.III
size: 20
#Lymington (56)
37:
- primary: Escort
secondary: any
aircraft:
- P-51D-30-NA Mustang
- Mustang Mk.IV (Late)
size: 20
- primary: BAI
secondary: any
aircraft:
- P-47D-40 Thunderbolt
- Thunderbolt Mk.II (Late)
size: 20
#Carpiquet (47)
19:
- primary: TARCAP
secondary: air-to-air
aircraft:
- Fw 190 D-9 Dora
size: 12
- primary: CAS
secondary: air-to-ground
aircraft:
- Ju 88 A-4
size: 8
#Broglie (32)
68:
- primary: Escort
secondary: any
aircraft:
- Bf 109 K-4 Kurfürst
size: 24
#Saint-Andre-de-lEure (30)
70:
- primary: BAI
secondary: air-to-ground
aircraft:
- Ju 88 A-4
size: 12
- primary: Strike
secondary: air-to-ground
aircraft:
- Ju 88 A-4
size: 12
#Vilacoublay (76)
42:
- primary: BARCAP
secondary: any
aircraft:
- Fw 190 A-8 Anton
size: 20

View File

@@ -7,7 +7,7 @@ recommended_enemy_faction: Syria 2011
description: <p>In this scenario, you start in Israel and the conflict is focused around the golan heights, an historically disputed territory.<br/><br/>This scenario is designed to be performance and helicopter friendly.</p>
miz: golan_heights_lite.miz
performance: 1
version: "10.5"
version: "10.9"
advanced_iads: true # Campaign has connection_nodes / power_sources / command_centers
iads_config:
- LHA-1 Tarawa # A Naval Group without connections but still participating as EWR
@@ -102,28 +102,31 @@ squadrons:
- primary: AEW&C
aircraft:
- E-3A
size: 1
- primary: Refueling
aircraft:
- KC-135 Stratotanker
- primary: Transport
aircraft:
- C-130
- primary: BARCAP
secondary: any
size: 1
- primary: Escort
secondary: air-to-air
aircraft:
- F-15C Eagle
size: 10
- primary: BARCAP
secondary: any
aircraft:
- F-4E Phantom II
size: 10
- primary: Strike
secondary: air-to-ground
aircraft:
- F-15E Strike Eagle
size: 10
- primary: SEAD
secondary: any
aircraft:
- F-16CM Fighting Falcon (Block 50)
size: 10
# Golan South
Golan South:
- primary: CAS
@@ -132,65 +135,67 @@ squadrons:
female_pilot_percentage: 15
aircraft:
- AH-1W SuperCobra
- primary: CAS
size: 4
- primary: BAI
secondary: air-to-ground
nickname: Defenders of Golan
female_pilot_percentage: 25
aircraft:
- AH-64D Apache Longbow
- primary: Transport
size: 6
- primary: Air Assault
secondary: any
aircraft:
- UH-1H Iroquois
size: 2
# Golan North
Golan North:
- primary: CAS
secondary: air-to-ground
aircraft:
- Mi-24P Hind-F
size: 4
- primary: CAS
aircraft:
- SA 342M Gazelle
- primary: Transport
secondary: air-to-ground
aircraft:
- SA 342M Gazelle
size: 4
- primary: Air Assault
secondary: any
aircraft:
- Mi-8MTV2 Hip
size: 2
# Marj Ruhayyil
23:
- primary: BARCAP
- primary: BAI
secondary: any
aircraft:
- MiG-21bis Fishbed-N
- primary: BARCAP
size: 12
- primary: Escort
secondary: any
aircraft:
- MiG-23MLD Flogger-K
- primary: SEAD
secondary: air-to-ground
aircraft:
- Su-17M4 Fitter-K
- primary: Strike
secondary: air-to-ground
aircraft:
- Su-17M4 Fitter-K
- MiG-21bis Fishbed-N
size: 12
# Damascus
7:
- primary: BARCAP
secondary: any
aircraft:
- MiG-29S Fulcrum-C
- primary: BARCAP
secondary: any
aircraft:
- MiG-21bis Fishbed-N
- primary: BARCAP
- MiG-23MLD Flogger-K
size: 12
- primary: TARCAP
secondary: any
aircraft:
- MiG-25PD Foxbat-E
size: 12
- primary: SEAD
secondary: air-to-ground
aircraft:
- Su-24M Fencer-D
size: 12
- primary: Strike
secondary: air-to-ground
aircraft:
- Su-17M4 Fitter-K
size: 12

View File

@@ -16,7 +16,7 @@ description:
miz: grabthars_hammer.miz
performance: 2
recommended_start_date: 1999-12-25
version: "10.7"
version: "10.9"
squadrons:
#Mount Pleasant
2:

View File

@@ -38,16 +38,20 @@ squadrons:
- primary: AEW&C
aircraft:
- VAW-125
size: 2
- primary: Refueling
aircraft:
- VS-35 (Tanker)
size: 4
- primary: Anti-ship
secondary: air-to-ground
aircraft:
- VS-35
size: 8
- primary: Transport
aircraft:
- HSM-40
size: 2
# BLUFOR LHA
Naval-3:
- primary: BAI
@@ -58,6 +62,7 @@ squadrons:
secondary: air-to-ground
aircraft:
- HMLA-169 (UH-1H)
size: 4
- primary: CAS
secondary: air-to-ground
aircraft:
@@ -109,6 +114,7 @@ squadrons:
- primary: Refueling
aircraft:
- IL-78M
size: 2
# Rio Gallegos
7:
- primary: BARCAP

View File

@@ -198,16 +198,20 @@ squadrons:
- primary: AEW&C
aircraft:
- VAW-125
size: 2
- primary: Refueling
aircraft:
- VS-35 (Tanker)
size: 4
- primary: Anti-ship
secondary: air-to-ground
aircraft:
- VS-35
size: 8
- primary: Transport
aircraft:
- HSM-40
size: 2
#BLUFOR LHA
Naval-3:
- primary: BAI
@@ -218,6 +222,7 @@ squadrons:
secondary: air-to-ground
aircraft:
- HMLA-269 (UH-1H)
size: 4
- primary: CAS
secondary: air-to-ground
aircraft:
@@ -257,6 +262,7 @@ squadrons:
- primary: Refueling
aircraft:
- VMGR-352
size: 2
- primary: Strike
secondary: any
aircraft:
@@ -322,6 +328,7 @@ squadrons:
- primary: Refueling
aircraft:
- IL-78M
size: 2
# Bassel Al-Assad
21:
- primary: TARCAP
@@ -335,12 +342,15 @@ squadrons:
- primary: Refueling
aircraft:
- IL-78M
size: 2
- primary: Transport
aircraft:
- IL-76MD
size: 2
- primary: AEW&C
aircraft:
- A-50
size: 2
- primary: Strike
secondary: air-to-ground
aircraft:
@@ -389,3 +399,4 @@ squadrons:
secondary: air-to-ground
aircraft:
- Mi-8MTV2 Hip
size: 4

View File

@@ -27,16 +27,20 @@ squadrons:
- primary: AEW&C
aircraft:
- VAW-125
size: 2
- primary: Refueling
aircraft:
- VS-35 (Tanker)
size: 4
- primary: Anti-ship
secondary: air-to-ground
aircraft:
- VS-35
size: 8
- primary: Transport
aircraft:
- HSM-40
size: 2
# BLUFOR LHA
Naval-2:
- primary: BAI
@@ -47,6 +51,7 @@ squadrons:
secondary: air-to-ground
aircraft:
- HMLA-269 (UH-1H)
size: 4
- primary: CAS
secondary: air-to-ground
aircraft:
@@ -118,12 +123,15 @@ squadrons:
- primary: AEW&C
aircraft:
- A-50
size: 2
- primary: Refueling
aircraft:
- IL-78M
size: 2
- primary: Transport
aircraft:
- IL-78MD
size: 2
# OPFOR First FOB
FOB Gecitkale:
- primary: CAS
@@ -136,6 +144,7 @@ squadrons:
secondary: air-to-ground
aircraft:
- Mi-8MTV2 Hip
size: 2
- primary: CAS
secondary: air-to-ground
aircraft:

View File

@@ -8,7 +8,7 @@ description: <p>This is a semi-fictional what-if scenario for Operation Peace Sp
miz: operation_peace_spring.miz
performance: 1
recommended_start_date: 2019-12-23
version: "10.7"
version: "10.9"
squadrons:
# Ramat David
30:

View File

@@ -8,7 +8,7 @@ description: <p>United Nations Observer Mission in Georgia (UNOMIG) observers st
miz: operation_vectrons_claw.miz
performance: 1
recommended_start_date: 2008-08-08
version: "10.7"
version: "10.9"
squadrons:
Blue CV-1:
- primary: BARCAP

View File

@@ -27,16 +27,20 @@ squadrons:
- primary: AEW&C
aircraft:
- VAW-125
size: 2
- primary: Refueling
aircraft:
- VS-35 (Tanker)
size: 4
- primary: Anti-ship
secondary: air-to-ground
aircraft:
- VS-35
size: 8
- primary: Transport
aircraft:
- HSM-40
size: 2
# BLUFOR LHA
Naval-2:
- primary: BAI
@@ -47,6 +51,7 @@ squadrons:
secondary: air-to-ground
aircraft:
- HMLA-169 (UH-1H)
size: 4
- primary: CAS
secondary: air-to-ground
aircraft:
@@ -92,6 +97,7 @@ squadrons:
- primary: Transport
aircraft:
- IL-76MD
size: 2
- primary: BARCAP
secondary: any
aircraft:
@@ -105,6 +111,7 @@ squadrons:
- primary: Refueling
aircraft:
- IL-78M
size: 2
# Andersen AFB
6:
- primary: TARCAP
@@ -122,6 +129,7 @@ squadrons:
- primary: Transport
aircraft:
- IL-76MD
size: 2
# Antonio B. Won Pat Intl
4:
- primary: TARCAP

View File

@@ -37,6 +37,7 @@ squadrons:
aircraft:
- 101st Combat Aviation Brigade
#US Army UH-60
size: 6
# Havadarya
9:
- primary: BARCAP
@@ -62,9 +63,11 @@ squadrons:
- primary: AEW&C
aircraft:
- VAW-125
size: 2
- primary: Refueling
aircraft:
- VS-35 (Tanker)
size: 4
# BLUFOR LHA
BLUFOR LHA:
- primary: BAI
@@ -75,6 +78,7 @@ squadrons:
secondary: air-to-ground
aircraft:
- HMLA-169 (UH-1H)
size: 4
# BLUFOR Start FOB
FOB Anguran:
- primary: CAS
@@ -87,6 +91,7 @@ squadrons:
aircraft:
- Wolfpack, 1-82 ARB
#US Army Apache AH-64D
size: 2
# OPFOR L1F1
FOB Tang-e Dalan:
- primary: CAS
@@ -140,9 +145,11 @@ squadrons:
- primary: AEW&C
aircraft:
- A-50
size: 2
- primary: Refueling
aircraft:
- IL-78M
size: 2
- primary: BARCAP
secondary: any
aircraft:
@@ -159,6 +166,7 @@ squadrons:
secondary: air-to-ground
aircraft:
- Mi-24P Hind-F
size: 4
- primary: SEAD
secondary: air-to-ground
aircraft:
@@ -228,12 +236,15 @@ squadrons:
- primary: AEW&C
aircraft:
- A-50
size: 2
- primary: Refueling
aircraft:
- IL-78M
size: 2
- primary: Transport
aircraft:
- IL-78MD
size: 2
- primary: BARCAP
secondary: any
aircraft:
@@ -249,4 +260,4 @@ squadrons:
- primary: CAS
secondary: air-to-ground
aircraft:
- Mi-24P Hind-F
- Mi-24P Hind-F

View File

@@ -28,16 +28,20 @@ squadrons:
- primary: AEW&C
aircraft:
- VAW-125
size: 2
- primary: Refueling
aircraft:
- VS-35 (Tanker)
size: 4
- primary: Anti-ship
secondary: air-to-ground
aircraft:
- VS-35
size: 8
- primary: Transport
aircraft:
- HSM-40
size: 2
# BLUFOR LHA
Naval-2:
- primary: BAI
@@ -48,6 +52,7 @@ squadrons:
secondary: air-to-ground
aircraft:
- HMLA-169 (UH-1H)
size: 4
- primary: CAS
secondary: air-to-ground
aircraft:
@@ -72,12 +77,15 @@ squadrons:
- primary: AEW&C
aircraft:
- A-50
size: 2
- primary: Refueling
aircraft:
- IL-78M
size: 2
- primary: Transport
aircraft:
- IL-78MD
size: 2
- primary: BARCAP
secondary: any
aircraft:

View File

@@ -24,6 +24,7 @@ squadrons:
aircraft:
- 960th AAC Squadron
#USAF E-3A
size: 2
# King Hussein Air College, BLUFOR start
19:
- primary: BARCAP
@@ -46,6 +47,7 @@ squadrons:
aircraft:
- 101st Combat Aviation Brigade
#US Army UH-60
size: 4
# FOB Tha'lah, BLUFOR 1st FOB north
FOB Tha'lah:
- primary: CAS

View File

@@ -9,7 +9,7 @@ local unitPayloads = {
["num"] = 3,
},
[2] = {
["CLSID"] = "{M261_M282}",
["CLSID"] = "{88D18A5E-99C8-4B04-B40B-1C02F2018B6E}",
["num"] = 4,
},
[3] = {
@@ -17,7 +17,7 @@ local unitPayloads = {
["num"] = 2,
},
[4] = {
["CLSID"] = "{M261_M282}",
["CLSID"] = "{88D18A5E-99C8-4B04-B40B-1C02F2018B6E}",
["num"] = 1,
},
},
@@ -30,19 +30,19 @@ local unitPayloads = {
["name"] = "Liberation BAI",
["pylons"] = {
[1] = {
["CLSID"] = "{88D18A5E-99C8-4B04-B40B-1C02F2018B6E}",
["CLSID"] = "{M299_4xAGM_114L}",
["num"] = 3,
},
[2] = {
["CLSID"] = "{88D18A5E-99C8-4B04-B40B-1C02F2018B6E}",
["CLSID"] = "{M299_4xAGM_114L}",
["num"] = 4,
},
[3] = {
["CLSID"] = "{88D18A5E-99C8-4B04-B40B-1C02F2018B6E}",
["CLSID"] = "{M299_4xAGM_114L}",
["num"] = 2,
},
[4] = {
["CLSID"] = "{88D18A5E-99C8-4B04-B40B-1C02F2018B6E}",
["CLSID"] = "{M299_4xAGM_114L}",
["num"] = 1,
},
},
@@ -54,21 +54,21 @@ local unitPayloads = {
["name"] = "Liberation OCA/Aircraft",
["pylons"] = {
[1] = {
["CLSID"] = "{M261_M229}",
["num"] = 4,
},
[2] = {
["CLSID"] = "{M261_M229}",
["num"] = 1,
},
[3] = {
["CLSID"] = "{88D18A5E-99C8-4B04-B40B-1C02F2018B6E}",
["num"] = 3,
},
[4] = {
[2] = {
["CLSID"] = "{88D18A5E-99C8-4B04-B40B-1C02F2018B6E}",
["num"] = 2,
},
[3] = {
["CLSID"] = "{M261_M229}",
["num"] = 4,
},
[4] = {
["CLSID"] = "{M261_M229}",
["num"] = 1,
},
},
["tasks"] = {
[1] = 31,

View File

@@ -78,7 +78,7 @@
},
"airfield32_3": {
"name": "Beslan",
"callsign": "",
"callsign": "ICH",
"beacon_type": 14,
"hertz": 110500000,
"channel": null

View File

@@ -167,6 +167,13 @@
"hertz": 1000000,
"channel": 31
},
"airfield20_0": {
"name": "BIO",
"callsign": "BIO",
"beacon_type": 9,
"hertz": 205000000,
"channel": null
},
"airfield11_0": {
"name": "San Julian",
"callsign": "",

View File

@@ -1,18 +1,193 @@
{
"airfield22_0": {
"name": "ABUDHABI",
"callsign": "ADV",
"beacon_type": 1,
"hertz": 114250000,
"world_0": {
"name": "Kish",
"callsign": "KIS",
"beacon_type": 3,
"hertz": 117400000,
"channel": 121
},
"world_1": {
"name": "DohaAirport",
"callsign": "DIA",
"beacon_type": 3,
"hertz": 112400000,
"channel": 71
},
"world_2": {
"name": "HamadInternationalAirport",
"callsign": "DOH",
"beacon_type": 3,
"hertz": 114400000,
"channel": 91
},
"world_3": {
"name": "DezfulAirport",
"callsign": "DZF",
"beacon_type": 8,
"hertz": 293000,
"channel": null
},
"airfield22_1": {
"world_4": {
"name": "AbadanIntAirport",
"callsign": "ABD",
"beacon_type": 3,
"hertz": 115100000,
"channel": 98
},
"world_5": {
"name": "AhvazIntAirport",
"callsign": "AWZ",
"beacon_type": 3,
"hertz": 114000000,
"channel": 87
},
"world_6": {
"name": "AghajariAirport",
"callsign": "AJR",
"beacon_type": 3,
"hertz": 114900000,
"channel": 96
},
"world_7": {
"name": "BirjandIntAirport",
"callsign": "BJD",
"beacon_type": 3,
"hertz": 113500000,
"channel": 82
},
"world_8": {
"name": "BushehrIntAirport",
"callsign": "BUZ",
"beacon_type": 3,
"hertz": 117450000,
"channel": 121
},
"world_9": {
"name": "KonarakAirport",
"callsign": "CBH",
"beacon_type": 3,
"hertz": 115600000,
"channel": 103
},
"world_10": {
"name": "IsfahanIntAirport",
"callsign": "ISN",
"beacon_type": 3,
"hertz": 113200000,
"channel": 79
},
"world_11": {
"name": "KhoramabadAirport",
"callsign": "KRD",
"beacon_type": 3,
"hertz": 113750000,
"channel": 84
},
"world_12": {
"name": "PersianGulfIntAirport",
"callsign": "PRG",
"beacon_type": 3,
"hertz": 112100000,
"channel": 58
},
"world_13": {
"name": "YasoujAirport",
"callsign": "YSJ",
"beacon_type": 3,
"hertz": 116550000,
"channel": 112
},
"world_14": {
"name": "BamAirport",
"callsign": "BAM",
"beacon_type": 3,
"hertz": 114900000,
"channel": 96
},
"world_15": {
"name": "MahshahrAirport",
"callsign": "MAH",
"beacon_type": 3,
"hertz": 115800000,
"channel": 105
},
"world_16": {
"name": "IranShahrAirport",
"callsign": "ISR",
"beacon_type": 3,
"hertz": 117000000,
"channel": 117
},
"world_17": {
"name": "LamerdAirport",
"callsign": "LAM",
"beacon_type": 3,
"hertz": 117000000,
"channel": 117
},
"world_18": {
"name": "SirjanAirport",
"callsign": "SRJ",
"beacon_type": 3,
"hertz": 114600000,
"channel": 93
},
"world_19": {
"name": "YazdIntAirport",
"callsign": "YZD",
"beacon_type": 3,
"hertz": 117700000,
"channel": 124
},
"world_20": {
"name": "ZabolAirport",
"callsign": "ZAL",
"beacon_type": 3,
"hertz": 113100000,
"channel": 78
},
"world_21": {
"name": "ZahedanIntAirport",
"callsign": "ZDN",
"beacon_type": 3,
"hertz": 116000000,
"channel": 107
},
"world_22": {
"name": "RafsanjanAirport",
"callsign": "RAF",
"beacon_type": 3,
"hertz": 112300000,
"channel": 70
},
"world_23": {
"name": "SaravanAirport",
"callsign": "SRN",
"beacon_type": 3,
"hertz": 114100000,
"channel": 88
},
"world_24": {
"name": "BuHasa",
"callsign": "BH",
"beacon_type": 2,
"hertz": 309000000,
"channel": null
},
"airfield22_0": {
"name": "AbuDhabiInt",
"callsign": "ADV",
"beacon_type": 2,
"hertz": 114250000,
"channel": 119
},
"airfield22_1": {
"name": "ABUDHABI",
"callsign": "ADV",
"beacon_type": 1,
"hertz": 114250000,
"channel": null
},
"airfield1_0": {
"name": "Abumusa",
"callsign": "ABM",
@@ -530,180 +705,5 @@
"beacon_type": 4,
"hertz": 114200000,
"channel": 89
},
"world_0": {
"name": "Kish",
"callsign": "KIS",
"beacon_type": 3,
"hertz": 117400000,
"channel": 121
},
"world_1": {
"name": "DohaAirport",
"callsign": "DIA",
"beacon_type": 3,
"hertz": 112400000,
"channel": 71
},
"world_2": {
"name": "HamadInternationalAirport",
"callsign": "DOH",
"beacon_type": 3,
"hertz": 114400000,
"channel": 91
},
"world_3": {
"name": "DezfulAirport",
"callsign": "DZF",
"beacon_type": 8,
"hertz": 293000,
"channel": null
},
"world_4": {
"name": "AbadanIntAirport",
"callsign": "ABD",
"beacon_type": 3,
"hertz": 115100000,
"channel": 98
},
"world_5": {
"name": "AhvazIntAirport",
"callsign": "AWZ",
"beacon_type": 3,
"hertz": 114000000,
"channel": 87
},
"world_6": {
"name": "AghajariAirport",
"callsign": "AJR",
"beacon_type": 3,
"hertz": 114900000,
"channel": 96
},
"world_7": {
"name": "BirjandIntAirport",
"callsign": "BJD",
"beacon_type": 3,
"hertz": 113500000,
"channel": 82
},
"world_8": {
"name": "BushehrIntAirport",
"callsign": "BUZ",
"beacon_type": 3,
"hertz": 117450000,
"channel": 121
},
"world_9": {
"name": "KonarakAirport",
"callsign": "CBH",
"beacon_type": 3,
"hertz": 115600000,
"channel": 103
},
"world_10": {
"name": "IsfahanIntAirport",
"callsign": "ISN",
"beacon_type": 3,
"hertz": 113200000,
"channel": 79
},
"world_11": {
"name": "KhoramabadAirport",
"callsign": "KRD",
"beacon_type": 3,
"hertz": 113750000,
"channel": 84
},
"world_12": {
"name": "PersianGulfIntAirport",
"callsign": "PRG",
"beacon_type": 3,
"hertz": 112100000,
"channel": 58
},
"world_13": {
"name": "YasoujAirport",
"callsign": "YSJ",
"beacon_type": 3,
"hertz": 116550000,
"channel": 112
},
"world_14": {
"name": "BamAirport",
"callsign": "BAM",
"beacon_type": 3,
"hertz": 114900000,
"channel": 96
},
"world_15": {
"name": "MahshahrAirport",
"callsign": "MAH",
"beacon_type": 3,
"hertz": 115800000,
"channel": 105
},
"world_16": {
"name": "IranShahrAirport",
"callsign": "ISR",
"beacon_type": 3,
"hertz": 117000000,
"channel": 117
},
"world_17": {
"name": "LamerdAirport",
"callsign": "LAM",
"beacon_type": 3,
"hertz": 117000000,
"channel": 117
},
"world_18": {
"name": "SirjanAirport",
"callsign": "SRJ",
"beacon_type": 3,
"hertz": 114600000,
"channel": 93
},
"world_19": {
"name": "YazdIntAirport",
"callsign": "YZD",
"beacon_type": 3,
"hertz": 117700000,
"channel": 124
},
"world_20": {
"name": "ZabolAirport",
"callsign": "ZAL",
"beacon_type": 3,
"hertz": 113100000,
"channel": 78
},
"world_21": {
"name": "ZahedanIntAirport",
"callsign": "ZDN",
"beacon_type": 3,
"hertz": 116000000,
"channel": 107
},
"world_22": {
"name": "RafsanjanAirport",
"callsign": "RAF",
"beacon_type": 3,
"hertz": 112300000,
"channel": 70
},
"world_23": {
"name": "SaravanAirport",
"callsign": "SRN",
"beacon_type": 3,
"hertz": 114100000,
"channel": 88
},
"world_24": {
"name": "BuHasa",
"callsign": "BH",
"beacon_type": 2,
"hertz": 309000000,
"channel": null
}
}

View File

@@ -72,7 +72,7 @@ naval_units:
- CVN-74 John C. Stennis
missiles: []
air_defense_units:
- SAM Patriot STR
- EWR AN/FPS-117 Radar
- M163 Vulcan Air Defense System
- M1097 Heavy HMMWV Avenger
- M48 Chaparral

View File

@@ -75,7 +75,7 @@ naval_units:
- CVN-74 John C. Stennis
missiles: []
air_defense_units:
- SAM Patriot STR
- EWR AN/FPS-117 Radar
- M163 Vulcan Air Defense System
- M1097 Heavy HMMWV Avenger
- M48 Chaparral

View File

@@ -30,6 +30,7 @@ frontline_units:
- QF 3.7-inch AA Gun
artillery_units:
- M12 Gun Motor Carriage
- FH M2A1 105mm
logistics_units:
- Truck Bedford
- Truck GMC "Jimmy" 6x6 Truck

View File

@@ -86,7 +86,7 @@ naval_units:
- CVN-74 John C. Stennis
missiles: []
air_defense_units:
- SAM Patriot STR
- EWR AN/FPS-117 Radar
- M1097 Heavy HMMWV Avenger
requirements: {}
carrier_names:
@@ -94,6 +94,7 @@ carrier_names:
- CVN-72 Abraham Lincoln
- CVN-73 George Washington
- CVN-74 John C. Stennis
- CVN-75 Harry S. Truman
helicopter_carrier_names:
- LHA-1 Tarawa
- LHA-2 Saipan
@@ -107,7 +108,7 @@ liveries_overrides:
J-11A Flanker-L:
- USN Aggressor VFC-13 'Ferris' (Fictional)
JF-17 Thunder:
- 'Chips' Camo for Blue Side (Fictional)
- "'Chips' Camo for Blue Side (Fictional)"
Ka-50 Hokum:
- georgia camo
Ka-50 Hokum (Blackshark 3):

View File

@@ -22,7 +22,7 @@ tankers:
frontline_units:
- BMP-1
- HQ-7 Launcher
- T-55A
- MT Type 59
- Type 04A (ZBD-04A)
- Type 96B (ZTZ-96B)
artillery_units:
@@ -52,6 +52,7 @@ naval_units:
- Type 052B Destroyer
- Type 052C Destroyer
- Type 054A Frigate
- Type 093 Attack Submarine
- CV 1143.5 Admiral Kuznetsov
- Type 071 Amphibious Transport Dock
air_defense_units:

View File

@@ -42,7 +42,7 @@ naval_units:
- CG Ticonderoga
missiles: []
air_defense_units:
- SAM Patriot STR
- EWR AN/FPS-117 Radar
- Flakpanzer Gepard
requirements: {}
carrier_names: []

View File

@@ -74,3 +74,6 @@ missiles:
- SSM SS-1C Scud-B
has_jtac: true
jtac_unit: MQ-9 Reaper
liveries_overrides:
F-14A Tomcat (Block 135-GR Late):
- Rogue Nation(Top Gun - Maverick)

View File

@@ -48,7 +48,7 @@ naval_units:
- LHA-1 Tarawa
missiles: []
air_defense_units:
- SAM Patriot STR
- EWR AN/FPS-117 Radar
- Flakpanzer Gepard
requirements: {}
carrier_names: []

View File

@@ -35,7 +35,7 @@ naval_units:
- FFG Oliver Hazard Perry
missiles: []
air_defense_units:
- SAM Patriot STR
- EWR AN/FPS-117 Radar
- M1097 Heavy HMMWV Avenger
- Flakpanzer Gepard
requirements: {}

View File

@@ -68,6 +68,8 @@ naval_units:
- Frigate 1135M Rezky
- Corvette 1241.1 Molniya
- CV 1143.5 Admiral Kuznetsov
- LS Ropucha
- SSK 636 Improved Kilo
air_defense_units:
- EWR 1L13
- EWR 55G6

View File

@@ -44,7 +44,7 @@ naval_units:
- CVN-74 John C. Stennis
missiles: []
air_defense_units:
- SAM Patriot STR
- EWR AN/FPS-117 Radar
- M1097 Heavy HMMWV Avenger
requirements: {}
carrier_names:

View File

@@ -39,7 +39,7 @@ naval_units:
- FFG Oliver Hazard Perry
missiles: []
air_defense_units:
- SAM Patriot STR
- EWR AN/FPS-117 Radar
requirements: {}
carrier_names: []
has_jtac: true

View File

@@ -60,7 +60,7 @@ naval_units:
- FFG Oliver Hazard Perry
missiles: []
air_defense_units:
- SAM Patriot STR
- EWR AN/FPS-117 Radar
- M1097 Heavy HMMWV Avenger
requirements: {}
has_jtac: true

View File

@@ -65,7 +65,7 @@ naval_units:
- CVN-74 John C. Stennis
missiles: []
air_defense_units:
- SAM Patriot STR
- EWR AN/FPS-117 Radar
- M1097 Heavy HMMWV Avenger
- M48 Chaparral
requirements: {}

View File

@@ -71,7 +71,7 @@ naval_units:
- CVN-74 John C. Stennis
missiles: []
air_defense_units:
- SAM Patriot STR
- EWR AN/FPS-117 Radar
- M1097 Heavy HMMWV Avenger
- M6 Linebacker
requirements: {}

View File

@@ -8,6 +8,7 @@ from pathlib import Path
from pyproj import CRS, Transformer
from shapefile import Reader, Shape
from shapely import validation
from shapely.geometry import LineString, MultiPolygon, Polygon, shape
from shapely.ops import unary_union
@@ -46,11 +47,13 @@ class CoordinateConverter:
for poly in polys:
for boundary, holes in self._boundary_and_holes_of(poly):
new_polys.append(
Polygon(
self._convert_line_to_dcs_coords(boundary),
holes=[
self._convert_line_to_dcs_coords(hole) for hole in holes
],
validation.make_valid(
Polygon(
self._convert_line_to_dcs_coords(boundary),
holes=[
self._convert_line_to_dcs_coords(hole) for hole in holes
],
)
)
)
return new_polys

View File

@@ -0,0 +1,4 @@
class: Logistics
price: 1
variants:
LUV Horch 901 Staff Car: null

View File

@@ -0,0 +1,9 @@
class: Artillery
description:
introduced:
manufacturer:
origin:
price: 5
role: Field Howitzer
variants:
FH LeFH-18 105mm: {}

View File

@@ -0,0 +1,9 @@
class: Artillery
description:
introduced:
manufacturer:
origin:
price: 10
role: Field Howitzer
variants:
FH M2A1 105mm: {}

View File

@@ -0,0 +1,9 @@
class: Artillery
description:
introduced:
manufacturer:
origin:
price: 5
role: Field Howitzer
variants:
FH Pak 40 75mm: {}

View File

@@ -0,0 +1,9 @@
class: Tank
description: Type 59
introduced:
manufacturer:
origin: China
price: 15
role: Main Battle Tank
variants:
MT Type 59: {}

View File

@@ -0,0 +1,9 @@
class: Artillery
description:
introduced:
manufacturer:
origin:
price: 15
role: Self Propelled Gun
variants:
SPH Sd.Kfz.124 Wespe 105mm: {}

View File

@@ -0,0 +1,4 @@
class: LandingShip
price: 0
variants:
LS Ropucha: null

View File

@@ -0,0 +1,4 @@
class: LandingShip
price: 0
variants:
Boat LCVP Higgins: null

View File

@@ -0,0 +1,4 @@
class: Submarine
price: 0
variants:
SSK 636 Improved Kilo: null

View File

@@ -0,0 +1,4 @@
class: Submarine
price: 0
variants:
Type 093 Attack Submarine: null

View File

@@ -0,0 +1,4 @@
class: Frigate
price: 0
variants:
CNS Almirante Condell (PFG-06): null

View File

@@ -0,0 +1,4 @@
class: Frigate
price: 0
variants:
CNS Almirante Lynch (PFG-07): null

View File

@@ -0,0 +1,4 @@
class: Submarine
price: 0
variants:
ARA Santa Fe S-21: null

View File

@@ -56,6 +56,16 @@ def test_save_start_of_turn(game: Game, tmp_bundle: SaveGameBundle) -> None:
assert zip_file.namelist() == [SaveGameBundle.START_OF_TURN_SAVE_NAME]
def test_save_pre_sim_checkpoint(game: Game, tmp_bundle: SaveGameBundle) -> None:
with ZipFile(tmp_bundle.bundle_path, "r") as zip_file:
with pytest.raises(KeyError):
zip_file.read(SaveGameBundle.PRE_SIM_CHECKPOINT_SAVE_NAME)
tmp_bundle.save_pre_sim_checkpoint(game)
with ZipFile(tmp_bundle.bundle_path, "r") as zip_file:
assert zip_file.namelist() == [SaveGameBundle.PRE_SIM_CHECKPOINT_SAVE_NAME]
def test_failed_save_leaves_original_intact(
game: Game, tmp_bundle: SaveGameBundle
) -> None:

View File

@@ -180,7 +180,8 @@ def test_mission_types_enemy(mocker: Any) -> None:
control_point=dummy_control_point,
)
mission_types = list(ground_object.mission_types(for_player=False))
assert len(mission_types) == 6
assert len(mission_types) == 7
assert FlightType.BAI in mission_types
assert FlightType.STRIKE in mission_types
assert FlightType.REFUELING in mission_types
assert FlightType.ESCORT in mission_types

View File

@@ -0,0 +1 @@
PROJCS["WGS_1984_UTM_Zone_31N",GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",3.0],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]]

View File

@@ -0,0 +1,27 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis version="3.30.2-'s-Hertogenbosch">
<identifier></identifier>
<parentidentifier></parentidentifier>
<language></language>
<type>dataset</type>
<title></title>
<abstract></abstract>
<links/>
<dates/>
<fees></fees>
<encoding></encoding>
<crs>
<spatialrefsys nativeFormat="Wkt">
<wkt>PROJCRS["WGS 84 / UTM zone 31N",BASEGEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4326]],CONVERSION["UTM zone 31N",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",3,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Navigation and medium accuracy spatial referencing."],AREA["Between 0°E and 6°E, northern hemisphere between equator and 84°N, onshore and offshore. Algeria. Andorra. Belgium. Benin. Burkina Faso. Denmark - North Sea. France. Germany - North Sea. Ghana. Luxembourg. Mali. Netherlands. Niger. Nigeria. Norway. Spain. Togo. United Kingdom (UK) - North Sea."],BBOX[0,0,84,6]],ID["EPSG",32631]]</wkt>
<proj4>+proj=utm +zone=31 +datum=WGS84 +units=m +no_defs</proj4>
<srsid>3115</srsid>
<srid>32631</srid>
<authid>EPSG:32631</authid>
<description>WGS 84 / UTM zone 31N</description>
<projectionacronym>utm</projectionacronym>
<ellipsoidacronym>EPSG:7030</ellipsoidacronym>
<geographicflag>false</geographicflag>
</spatialrefsys>
</crs>
<extent/>
</qgis>

View File

@@ -0,0 +1 @@
UTF-8

View File

@@ -0,0 +1 @@
PROJCS["WGS_1984_UTM_Zone_31N",GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",3.0],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]]

View File

@@ -0,0 +1,27 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis version="3.30.2-'s-Hertogenbosch">
<identifier></identifier>
<parentidentifier></parentidentifier>
<language></language>
<type>dataset</type>
<title></title>
<abstract></abstract>
<links/>
<dates/>
<fees></fees>
<encoding></encoding>
<crs>
<spatialrefsys nativeFormat="Wkt">
<wkt>PROJCRS["WGS 84 / UTM zone 31N",BASEGEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4326]],CONVERSION["UTM zone 31N",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",3,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["(N)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Navigation and medium accuracy spatial referencing."],AREA["Between 0°E and 6°E, northern hemisphere between equator and 84°N, onshore and offshore. Algeria. Andorra. Belgium. Benin. Burkina Faso. Denmark - North Sea. France. Germany - North Sea. Ghana. Luxembourg. Mali. Netherlands. Niger. Nigeria. Norway. Spain. Togo. United Kingdom (UK) - North Sea."],BBOX[0,0,84,6]],ID["EPSG",32631]]</wkt>
<proj4>+proj=utm +zone=31 +datum=WGS84 +units=m +no_defs</proj4>
<srsid>3115</srsid>
<srid>32631</srid>
<authid>EPSG:32631</authid>
<description>WGS 84 / UTM zone 31N</description>
<projectionacronym>utm</projectionacronym>
<ellipsoidacronym>EPSG:7030</ellipsoidacronym>
<geographicflag>false</geographicflag>
</spatialrefsys>
</crs>
<extent/>
</qgis>

View File

@@ -0,0 +1 @@
UTF-8

View File

@@ -0,0 +1 @@
PROJCS["WGS_1984_UTM_Zone_31N",GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",3.0],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]]