mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Compare commits
60 Commits
zhexu14-pa
...
develop-7.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8e41c0bc1 | ||
|
|
9bd99f9cde | ||
|
|
d2cee713d8 | ||
|
|
1e0ef288be | ||
|
|
12a0186246 | ||
|
|
2f1a9d3dfd | ||
|
|
7c690227d6 | ||
|
|
8172461db4 | ||
|
|
87ed02deb0 | ||
|
|
59676fb0fe | ||
|
|
7cfd6381fb | ||
|
|
9f7aa7b75b | ||
|
|
5f5422b579 | ||
|
|
16ad43f260 | ||
|
|
9cd1a06651 | ||
|
|
2bf1ba6d12 | ||
|
|
e705a2ddbf | ||
|
|
60dd8f3245 | ||
|
|
0ecc53ef27 | ||
|
|
2f715a1427 | ||
|
|
e28ffe97ac | ||
|
|
54bd4189bd | ||
|
|
a762970469 | ||
|
|
c5ebde3cd3 | ||
|
|
38ce82f3f9 | ||
|
|
16ef182a8d | ||
|
|
8ac582b9a8 | ||
|
|
11d77c0fe6 | ||
|
|
c1eab6715b | ||
|
|
4be77472e7 | ||
|
|
af65254db5 | ||
|
|
b523b03e3c | ||
|
|
aae53ffc63 | ||
|
|
c6916d8da2 | ||
|
|
8ae64f57b5 | ||
|
|
b132543b7e | ||
|
|
1836b0bd98 | ||
|
|
29a05fa0e7 | ||
|
|
da1f84a8f5 | ||
|
|
5d22d4f43c | ||
|
|
6adde1cb3e | ||
|
|
81a00981eb | ||
|
|
ed17fc97d9 | ||
|
|
75ee0de23f | ||
|
|
a4d7c66621 | ||
|
|
998864797d | ||
|
|
5cca4eb051 | ||
|
|
d5c335c698 | ||
|
|
7fea15ee07 | ||
|
|
a31296cbc0 | ||
|
|
fe60757891 | ||
|
|
7614017828 | ||
|
|
61879aeafa | ||
|
|
f5b9052257 | ||
|
|
b27d2be0d1 | ||
|
|
e1a1eca5da | ||
|
|
c695db0f98 | ||
|
|
ff20f16109 | ||
|
|
8af3dc6965 | ||
|
|
e6cf253e45 |
30
changelog.md
30
changelog.md
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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]):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ?
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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__:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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__(
|
||||
|
||||
@@ -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)
|
||||
|
||||
135
qt_ui/main.py
135
qt_ui/main.py
@@ -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__":
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -29,6 +29,7 @@ class QEditFlightDialog(QDialog):
|
||||
|
||||
self.setWindowTitle("Edit flight")
|
||||
self.setWindowIcon(EVENT_ICONS["strike"])
|
||||
self.setModal(True)
|
||||
|
||||
layout = QVBoxLayout()
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
BIN
resources/campaigns/final_countdown_2.miz
Normal file
BIN
resources/campaigns/final_countdown_2.miz
Normal file
Binary file not shown.
175
resources/campaigns/final_countdown_2.yaml
Normal file
175
resources/campaigns/final_countdown_2.yaml
Normal 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
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
},
|
||||
"airfield32_3": {
|
||||
"name": "Beslan",
|
||||
"callsign": "",
|
||||
"callsign": "ICH",
|
||||
"beacon_type": 14,
|
||||
"hertz": 110500000,
|
||||
"channel": null
|
||||
|
||||
@@ -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": "",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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: []
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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: []
|
||||
|
||||
@@ -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: {}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: {}
|
||||
|
||||
@@ -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: {}
|
||||
|
||||
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
class: Logistics
|
||||
price: 1
|
||||
variants:
|
||||
LUV Horch 901 Staff Car: null
|
||||
9
resources/units/ground_units/LeFH_18-40-105.yaml
Normal file
9
resources/units/ground_units/LeFH_18-40-105.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
class: Artillery
|
||||
description:
|
||||
introduced:
|
||||
manufacturer:
|
||||
origin:
|
||||
price: 5
|
||||
role: Field Howitzer
|
||||
variants:
|
||||
FH LeFH-18 105mm: {}
|
||||
9
resources/units/ground_units/M2A1-105.yaml
Normal file
9
resources/units/ground_units/M2A1-105.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
class: Artillery
|
||||
description:
|
||||
introduced:
|
||||
manufacturer:
|
||||
origin:
|
||||
price: 10
|
||||
role: Field Howitzer
|
||||
variants:
|
||||
FH M2A1 105mm: {}
|
||||
9
resources/units/ground_units/Pak40.yaml
Normal file
9
resources/units/ground_units/Pak40.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
class: Artillery
|
||||
description:
|
||||
introduced:
|
||||
manufacturer:
|
||||
origin:
|
||||
price: 5
|
||||
role: Field Howitzer
|
||||
variants:
|
||||
FH Pak 40 75mm: {}
|
||||
9
resources/units/ground_units/TYPE-59.yaml
Normal file
9
resources/units/ground_units/TYPE-59.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
class: Tank
|
||||
description: Type 59
|
||||
introduced:
|
||||
manufacturer:
|
||||
origin: China
|
||||
price: 15
|
||||
role: Main Battle Tank
|
||||
variants:
|
||||
MT Type 59: {}
|
||||
9
resources/units/ground_units/Wespe124.yaml
Normal file
9
resources/units/ground_units/Wespe124.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
class: Artillery
|
||||
description:
|
||||
introduced:
|
||||
manufacturer:
|
||||
origin:
|
||||
price: 15
|
||||
role: Self Propelled Gun
|
||||
variants:
|
||||
SPH Sd.Kfz.124 Wespe 105mm: {}
|
||||
4
resources/units/ships/BDK-775.yaml
Normal file
4
resources/units/ships/BDK-775.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
class: LandingShip
|
||||
price: 0
|
||||
variants:
|
||||
LS Ropucha: null
|
||||
4
resources/units/ships/Higgins_boat.yaml
Normal file
4
resources/units/ships/Higgins_boat.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
class: LandingShip
|
||||
price: 0
|
||||
variants:
|
||||
Boat LCVP Higgins: null
|
||||
4
resources/units/ships/IMPROVED_KILO.yaml
Normal file
4
resources/units/ships/IMPROVED_KILO.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
class: Submarine
|
||||
price: 0
|
||||
variants:
|
||||
SSK 636 Improved Kilo: null
|
||||
4
resources/units/ships/Type_093.yaml
Normal file
4
resources/units/ships/Type_093.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
class: Submarine
|
||||
price: 0
|
||||
variants:
|
||||
Type 093 Attack Submarine: null
|
||||
4
resources/units/ships/leander-gun-condell.yaml
Normal file
4
resources/units/ships/leander-gun-condell.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
class: Frigate
|
||||
price: 0
|
||||
variants:
|
||||
CNS Almirante Condell (PFG-06): null
|
||||
4
resources/units/ships/leander-gun-lynch.yaml
Normal file
4
resources/units/ships/leander-gun-lynch.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
class: Frigate
|
||||
price: 0
|
||||
variants:
|
||||
CNS Almirante Lynch (PFG-07): null
|
||||
4
resources/units/ships/santafe.yaml
Normal file
4
resources/units/ships/santafe.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
class: Submarine
|
||||
price: 0
|
||||
variants:
|
||||
ARA Santa Fe S-21: null
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
UTF-8
|
||||
Binary file not shown.
@@ -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]]
|
||||
@@ -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>
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
UTF-8
|
||||
Binary file not shown.
@@ -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]]
|
||||
@@ -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>
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
UTF-8
|
||||
BIN
unshipped_data/arcgis_maps/normandy/sea/NormandyOceanv2.dbf
Normal file
BIN
unshipped_data/arcgis_maps/normandy/sea/NormandyOceanv2.dbf
Normal file
Binary file not shown.
@@ -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]]
|
||||
BIN
unshipped_data/arcgis_maps/normandy/sea/NormandyOceanv2.shp
Normal file
BIN
unshipped_data/arcgis_maps/normandy/sea/NormandyOceanv2.shp
Normal file
Binary file not shown.
BIN
unshipped_data/arcgis_maps/normandy/sea/NormandyOceanv2.shx
Normal file
BIN
unshipped_data/arcgis_maps/normandy/sea/NormandyOceanv2.shx
Normal file
Binary file not shown.
Reference in New Issue
Block a user