Merge branch 'develop' into helipads

# Conflicts:
#	game/game.py
#	resources/campaigns/golan_heights_lite.miz
This commit is contained in:
Khopa 2021-06-26 18:00:36 +02:00
commit 7667a4f8c0
182 changed files with 1613 additions and 2146 deletions

1
.gitignore vendored
View File

@ -12,6 +12,7 @@ resources/tools/a.miz
# User-specific stuff
.idea/
.env
env/
/kneeboards
/liberation_preferences.json

4
.gitmodules vendored
View File

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

View File

@ -1,30 +1,51 @@
# 5.0.0
Saves from 3.x are not compatible with 5.0.
## Features/Improvements
## Fixes
# 4.0.0
Saves from 3.x are not compatible with 4.0.
## Features/Improvements
* **[Campaign]** Squadrons now have a maximum size and killed pilots replenish at a limited rate.
* **[Campaign]** Squadrons now (optionally, off by default) have a maximum size and killed pilots replenish at a limited rate.
* **[Campaign]** Added an option to disable levelling up of AI pilots.
* **[Campaign]** Added Russian Intervention 2015 campaign on Syria, for a small and somewhat realistic Russian COIN scenario.
* **[Campaign]** Added Operation Atilla campaign on Syria, for a reasonably large invasion of Cyprus scenario.
* **[Campaign AI]** AI will plan Tanker flights.
* **[Campaign AI]** Removed max distance for AEW&C auto planning.
* **[Economy]** Adjusted prices for aircraft to balance out some price inconsistencies.
* **[Factions]** Added more tankers to factions.
* **[Flight Planner]** Added ability to plan Tankers.
* **[Modding]** Campaign format version is now 7.0 to account for DCS map changes that made scenery strike targets incompatible with existing campaigns.
* **[Mods]** Added support for the Gripen mod.
* **[Mods]** Removes MB-339PAN support, as the mod is now deprecated and no longer works with DCS 2.7+.
* **[Mission Generation]** Added support for "Neutral Dot" label options.
* **[New Game Wizard]** Mods are now selected via checkboxes in the new game wizard, not as separate factions.
* **[UI]** Ctrl click and shift click now buy or sell 5 or 10 units respectively.
* **[UI]** Multiple waypoints can now be deleted simultaneously if multiple waypoints are selected.
* **[UI]** Carriers and LHAs now match the colour of airfields, and their destination icons are translucent.
* **[UI]** Updated intel box text for first turn.
* **[UI]** Base Capture Cheat is now usable at all bases and can also be used to transfer player-owned bases to OPFOR.
* **[UI]** Pass Turn button is relabled as "Begin Campaign" on Turn 0.
* **[UI]** Added a ruler to the map.
* **[UI]** Liberation now saves games to `<DCS user directory>/Liberation/Saves` by default to declutter the main directory.
## Fixes
* **[Campaign AI]** Fix procurement for factions that lack some unit types.
* **[Campaign AI]** Improved pruning of unplannable missions which should improve turn cycle time and prevent the auto-planner from quitting early.
* **[Campaign AI]** Fix auto purchase of aircraft for factions that have no transport aircraft.
* **[Campaign AI]** Fix refunding of pending aircraft purchases when a side has no factory available.
* **[Mission Generation]** Fixed problem with mission load when control point name contained an apostrophe.
* **[Mission Generation]** Fixed EWR group names so they contribute to Skynet again.
* **[Mission Generation]** Fixed duplicate name error when generating convoys and cargo ships when creating manual transfers after loading a game.
* **[Mission Generation]** Fixed empty convoys not being disbanded when all units are killed/removed.
* **[Mission Generation]** Fixed player losing frontline progress when skipping from turn 0 to turn 1.
* **[Mission Generation]** Fixed issue where frontline would only search to the right for valid locations.
* **[UI]** Made non-interactive map elements less obstructive.
* **[UI]** Added support for Neutral Dot difficulty label
* **[UI]** Clear skies at night no longer described as "Sunny" by the weather widget.

View File

@ -1,51 +0,0 @@
from dcs.planes import (
Bf_109K_4,
C_101CC,
FW_190A8,
FW_190D9,
F_5E_3,
F_86F_Sabre,
I_16,
L_39ZA,
MiG_15bis,
MiG_19P,
MiG_21Bis,
P_47D_30,
P_47D_30bl1,
P_47D_40,
P_51D,
P_51D_30_NA,
SpitfireLFMkIX,
SpitfireLFMkIXCW,
)
from pydcs_extensions.a4ec.a4ec import A_4E_C
"""
This list contains the aircraft that do not use the guns as the last resort weapons, but as a main weapon
They'll RTB when they don't have gun ammo left
"""
GUNFIGHTERS = [
# Cold War
MiG_15bis,
MiG_19P,
MiG_21Bis,
F_86F_Sabre,
A_4E_C,
F_5E_3,
# Trainers
C_101CC,
L_39ZA,
# WW2
P_51D_30_NA,
P_51D,
P_47D_30,
P_47D_30bl1,
P_47D_40,
SpitfireLFMkIXCW,
SpitfireLFMkIX,
Bf_109K_4,
FW_190D9,
FW_190A8,
I_16,
]

View File

@ -4,7 +4,7 @@ import datetime
import inspect
import logging
from collections import defaultdict
from dataclasses import dataclass
from dataclasses import dataclass, field
from typing import Dict, Iterator, Optional, Set, Tuple, Union, cast
from dcs.unitgroup import FlyingGroup
@ -21,8 +21,8 @@ class Weapon:
"""Wraps a pydcs weapon dict in a hashable type."""
cls_id: str
name: str
weight: int
name: str = field(compare=False)
weight: int = field(compare=False)
def available_on(self, date: datetime.date) -> bool:
introduction_year = WEAPON_INTRODUCTION_YEARS.get(self)

View File

@ -44,12 +44,10 @@ from pydcs_extensions.a4ec.a4ec import A_4E_C
from pydcs_extensions.f22a.f22a import F_22A
from pydcs_extensions.hercules.hercules import Hercules
from pydcs_extensions.jas39.jas39 import JAS39Gripen, JAS39Gripen_AG
from pydcs_extensions.mb339.mb339 import MB_339PAN
from pydcs_extensions.su57.su57 import Su_57
plane_map["A-4E-C"] = A_4E_C
plane_map["F-22A"] = F_22A
plane_map["MB-339PAN"] = MB_339PAN
plane_map["Su-57"] = Su_57
plane_map["Hercules"] = Hercules
plane_map["JAS39Gripen"] = JAS39Gripen

View File

@ -29,7 +29,7 @@ from game.radio.channels import (
ViggenRadioChannelAllocator,
NoOpChannelAllocator,
)
from game.utils import Speed, kph
from game.utils import Distance, Speed, feet, kph, knots
if TYPE_CHECKING:
from gen.aircraft import FlightData
@ -90,11 +90,34 @@ class RadioConfig:
}[config.get("namer", "default")]
@dataclass(frozen=True)
class PatrolConfig:
altitude: Optional[Distance]
speed: Optional[Speed]
@classmethod
def from_data(cls, data: dict[str, Any]) -> PatrolConfig:
altitude = data.get("altitude", None)
speed = data.get("altitude", None)
return PatrolConfig(
feet(altitude) if altitude is not None else None,
knots(speed) if speed is not None else None,
)
@dataclass(frozen=True)
class AircraftType(UnitType[FlyingType]):
carrier_capable: bool
lha_capable: bool
always_keeps_gun: bool
# If true, the aircraft does not use the guns as the last resort weapons, but as a main weapon.
# It'll RTB when it doesn't have gun ammo left.
gunfighter: bool
max_group_size: int
patrol_altitude: Optional[Distance]
patrol_speed: Optional[Speed]
intra_flight_radio: Optional[Radio]
channel_allocator: Optional[RadioChannelAllocator]
channel_namer: Type[ChannelNamer]
@ -146,6 +169,12 @@ class AircraftType(UnitType[FlyingType]):
def channel_name(self, radio_id: int, channel_id: int) -> str:
return self.channel_namer.channel_name(radio_id, channel_id)
def __setstate__(self, state: dict[str, Any]) -> None:
# Update any existing models with new data on load.
updated = AircraftType.named(state["name"])
state.update(updated.__dict__)
self.__dict__.update(state)
@classmethod
def register(cls, aircraft_type: AircraftType) -> None:
cls._by_name[aircraft_type.name] = aircraft_type
@ -159,6 +188,8 @@ class AircraftType(UnitType[FlyingType]):
@classmethod
def for_dcs_type(cls, dcs_unit_type: Type[FlyingType]) -> Iterator[AircraftType]:
if not cls._loaded:
cls._load_all()
yield from cls._by_unit_type[dcs_unit_type]
@staticmethod
@ -189,6 +220,7 @@ class AircraftType(UnitType[FlyingType]):
raise KeyError(f"Missing required price field: {data_path}") from ex
radio_config = RadioConfig.from_data(data.get("radios", {}))
patrol_config = PatrolConfig.from_data(data.get("patrol", {}))
try:
introduction = data["introduced"]
@ -210,6 +242,10 @@ class AircraftType(UnitType[FlyingType]):
carrier_capable=data.get("carrier_capable", False),
lha_capable=data.get("lha_capable", False),
always_keeps_gun=data.get("always_keeps_gun", False),
gunfighter=data.get("gunfighter", False),
max_group_size=data.get("max_group_size", aircraft.group_size_max),
patrol_altitude=patrol_config.altitude,
patrol_speed=patrol_config.speed,
intra_flight_radio=radio_config.intra_flight,
channel_allocator=radio_config.channel_allocator,
channel_namer=radio_config.channel_namer,

View File

@ -45,6 +45,8 @@ class GroundUnitType(UnitType[VehicleType]):
@classmethod
def for_dcs_type(cls, dcs_unit_type: Type[VehicleType]) -> Iterator[GroundUnitType]:
if not cls._loaded:
cls._load_all()
yield from cls._by_unit_type[dcs_unit_type]
@staticmethod

View File

@ -382,15 +382,21 @@ class PollDebriefingFileThread(threading.Thread):
else:
last_modified = 0
while not self.stopped():
if (
os.path.isfile("state.json")
and os.path.getmtime("state.json") > last_modified
):
with open("state.json", "r") as json_file:
json_data = json.load(json_file)
debriefing = Debriefing(json_data, self.game, self.unit_map)
self.callback(debriefing)
break
try:
if (
os.path.isfile("state.json")
and os.path.getmtime("state.json") > last_modified
):
with open("state.json", "r") as json_file:
json_data = json.load(json_file)
debriefing = Debriefing(json_data, self.game, self.unit_map)
self.callback(debriefing)
break
except json.JSONDecodeError:
logging.exception(
"Failed to decode state.json. Probably attempted read while DCS "
"was still writing the file. Will retry in 5 seconds."
)
time.sleep(5)

View File

@ -54,7 +54,7 @@ class Event:
@property
def is_player_attacking(self) -> bool:
return self.attacker_name == self.game.player_name
return self.attacker_name == self.game.player_faction.name
@property
def tasks(self) -> List[Type[Task]]:

View File

@ -257,6 +257,76 @@ class Faction:
if unit.unit_class is unit_class:
yield unit
def apply_mod_settings(self, mod_settings) -> Faction:
# aircraft
if not mod_settings.a4_skyhawk:
self.remove_aircraft("A-4E-C")
if not mod_settings.hercules:
self.remove_aircraft("Hercules")
if not mod_settings.f22_raptor:
self.remove_aircraft("F-22A")
if not mod_settings.jas39_gripen:
self.remove_aircraft("JAS39Gripen")
self.remove_aircraft("JAS39Gripen_AG")
if not mod_settings.su57_felon:
self.remove_aircraft("Su-57")
# frenchpack
if not mod_settings.frenchpack:
self.remove_vehicle("AMX10RCR")
self.remove_vehicle("SEPAR")
self.remove_vehicle("ERC")
self.remove_vehicle("M120")
self.remove_vehicle("AA20")
self.remove_vehicle("TRM2000")
self.remove_vehicle("TRM2000_Citerne")
self.remove_vehicle("TRM2000_AA20")
self.remove_vehicle("TRMMISTRAL")
self.remove_vehicle("VABH")
self.remove_vehicle("VAB_RADIO")
self.remove_vehicle("VAB_50")
self.remove_vehicle("VIB_VBR")
self.remove_vehicle("VAB_HOT")
self.remove_vehicle("VAB_MORTIER")
self.remove_vehicle("VBL50")
self.remove_vehicle("VBLANF1")
self.remove_vehicle("VBL-radio")
self.remove_vehicle("VBAE")
self.remove_vehicle("VBAE_MMP")
self.remove_vehicle("AMX-30B2")
self.remove_vehicle("Tracma")
self.remove_vehicle("JTACFP")
self.remove_vehicle("SHERIDAN")
self.remove_vehicle("Leclerc_XXI")
self.remove_vehicle("Toyota_bleu")
self.remove_vehicle("Toyota_vert")
self.remove_vehicle("Toyota_desert")
self.remove_vehicle("Kamikaze")
# high digit sams
if not mod_settings.high_digit_sams:
self.remove_air_defenses("SA10BGenerator")
self.remove_air_defenses("SA12Generator")
self.remove_air_defenses("SA20Generator")
self.remove_air_defenses("SA20BGenerator")
self.remove_air_defenses("SA23Generator")
self.remove_air_defenses("SA17Generator")
self.remove_air_defenses("KS19Generator")
return self
def remove_aircraft(self, name):
for i in self.aircrafts:
if i.dcs_unit_type.id == name:
self.aircrafts.remove(i)
def remove_air_defenses(self, name):
for i in self.air_defenses:
if i == name:
self.air_defenses.remove(i)
def remove_vehicle(self, name):
for i in self.frontline_units:
if i.dcs_unit_type.id == name:
self.frontline_units.remove(i)
def load_ship(name: str) -> Optional[Type[ShipType]]:
if (ship := getattr(dcs.ships, name, None)) is not None:

View File

@ -1,23 +1,25 @@
from game.dcs.aircrafttype import AircraftType
import itertools
import logging
import random
import sys
from datetime import date, datetime, timedelta
from enum import Enum
from typing import Any, Dict, List
from typing import Any, List
from dcs.action import Coalition
from dcs.countries import Switzerland, UnitedNationsPeacekeepers, USAFAggressors
from dcs.mapping import Point
from dcs.task import CAP, CAS, PinpointStrike
from dcs.vehicles import AirDefence
from pydcs_extensions.a4ec.a4ec import A_4E_C
from faker import Faker
from game import db
from game.inventory import GlobalAircraftInventory
from game.models.game_stats import GameStats
from game.plugins import LuaPluginManager
from gen import naming
from gen import aircraft, naming
from gen.ato import AirTaskingOrder
from gen.conflictgen import Conflict
from gen.flights.ai_flight_planner import CoalitionMissionPlanner
@ -87,8 +89,8 @@ class TurnState(Enum):
class Game:
def __init__(
self,
player_name: str,
enemy_name: str,
player_faction: Faction,
enemy_faction: Faction,
theater: ConflictTheater,
start_date: datetime,
settings: Settings,
@ -98,10 +100,10 @@ class Game:
self.settings = settings
self.events: List[Event] = []
self.theater = theater
self.player_name = player_name
self.player_country = db.FACTIONS[player_name].country
self.enemy_name = enemy_name
self.enemy_country = db.FACTIONS[enemy_name].country
self.player_faction = player_faction
self.player_country = player_faction.country
self.enemy_faction = enemy_faction
self.enemy_country = enemy_faction.country
# pass_turn() will be called when initialization is complete which will
# increment this to turn 0 before it reaches the player.
self.turn = -1
@ -109,7 +111,7 @@ class Game:
self.date = date(start_date.year, start_date.month, start_date.day)
self.game_stats = GameStats()
self.game_stats.update(self)
self.ground_planners: Dict[int, GroundPlanner] = {}
self.ground_planners: dict[int, GroundPlanner] = {}
self.informations = []
self.informations.append(Information("Game Start", "-" * 40, 0))
# Culling Zones are for areas around points of interest that contain things we may not wish to cull.
@ -150,7 +152,7 @@ class Game:
self.on_load(game_still_initializing=True)
def __getstate__(self) -> Dict[str, Any]:
def __getstate__(self) -> dict[str, Any]:
state = self.__dict__.copy()
# Avoid persisting any volatile types that can be deterministically
# recomputed on load for the sake of save compatibility.
@ -162,7 +164,7 @@ class Game:
del state["red_faker"]
return state
def __setstate__(self, state: Dict[str, Any]) -> None:
def __setstate__(self, state: dict[str, Any]) -> None:
self.__dict__.update(state)
# Regenerate any state that was not persisted.
self.on_load()
@ -202,13 +204,49 @@ class Game:
else:
self.enemy_country = "Russia"
@property
def player_faction(self) -> Faction:
return db.FACTIONS[self.player_name]
def faction_for(self, player: bool) -> Faction:
if player:
return self.player_faction
return self.enemy_faction
@property
def enemy_faction(self) -> Faction:
return db.FACTIONS[self.enemy_name]
def faker_for(self, player: bool) -> Faker:
if player:
return self.blue_faker
return self.red_faker
def air_wing_for(self, player: bool) -> AirWing:
if player:
return self.blue_air_wing
return self.red_air_wing
def country_for(self, player: bool) -> str:
if player:
return self.player_country
return self.enemy_country
def bullseye_for(self, player: bool) -> Bullseye:
if player:
return self.blue_bullseye
return self.red_bullseye
def _roll(self, prob, mult):
if self.settings.version == "dev":
# always generate all events for dev
return 100
else:
return random.randint(1, 100) <= prob * mult
def _generate_player_event(self, event_class, player_cp, enemy_cp):
self.events.append(
event_class(
self,
player_cp,
enemy_cp,
enemy_cp.position,
self.player_faction.name,
self.enemy_faction.name,
)
)
@property
def neutral_country(self):
@ -260,8 +298,8 @@ class Game:
player_cp,
enemy_cp,
enemy_cp.position,
self.player_name,
self.enemy_name,
self.player_faction.name,
self.enemy_faction.name,
)
)
@ -307,7 +345,7 @@ class Game:
return (
event
and event.attacker_name
and event.attacker_name == self.player_name
and event.attacker_name == self.player_faction.name
)
else:
raise RuntimeError(f"{event} was passed when an Event type was expected")
@ -356,10 +394,10 @@ class Game:
self.blue_air_wing.replenish()
self.red_air_wing.replenish()
if not skipped and self.turn > 1:
if not skipped:
for cp in self.theater.player_points():
cp.base.affect_strength(+PLAYER_BASE_STRENGTH_RECOVERY)
else:
elif self.turn > 1:
for cp in self.theater.player_points():
if not cp.is_carrier and not cp.is_lha:
cp.base.affect_strength(-PLAYER_BASE_STRENGTH_RECOVERY)

View File

@ -77,8 +77,8 @@ class Operation:
yield Conflict(
cls.game.theater,
frontline,
cls.game.player_name,
cls.game.enemy_name,
cls.game.player_faction.name,
cls.game.enemy_faction.name,
cls.game.player_country,
cls.game.enemy_country,
frontline.position,
@ -95,8 +95,8 @@ class Operation:
return Conflict(
cls.game.theater,
FrontLine(player_cp, enemy_cp),
cls.game.player_name,
cls.game.enemy_name,
cls.game.player_faction.name,
cls.game.enemy_faction.name,
cls.game.player_country,
cls.game.enemy_country,
mid_point,
@ -403,8 +403,8 @@ class Operation:
player_cp = front_line.blue_cp
enemy_cp = front_line.red_cp
conflict = Conflict.frontline_cas_conflict(
cls.game.player_name,
cls.game.enemy_name,
cls.game.player_faction.name,
cls.game.enemy_faction.name,
cls.current_mission.country(cls.game.player_country),
cls.current_mission.country(cls.game.enemy_country),
front_line,

View File

@ -2,16 +2,18 @@ import logging
import os
import pickle
import shutil
from pathlib import Path
from typing import Optional
_dcs_saved_game_folder: Optional[str] = None
_file_abs_path = None
def setup(user_folder: str):
global _dcs_saved_game_folder
_dcs_saved_game_folder = user_folder
_file_abs_path = os.path.join(base_path(), "default.liberation")
if not save_dir().exists():
save_dir().mkdir(parents=True)
def base_path() -> str:
@ -20,16 +22,20 @@ def base_path() -> str:
return _dcs_saved_game_folder
def save_dir() -> Path:
return Path(base_path()) / "Liberation" / "Saves"
def _temporary_save_file() -> str:
return os.path.join(base_path(), "tmpsave.liberation")
return str(save_dir() / "tmpsave.liberation")
def _autosave_path() -> str:
return os.path.join(base_path(), "autosave.liberation")
return str(save_dir() / "autosave.liberation")
def mission_path_for(name: str) -> str:
return os.path.join(base_path(), "Missions", "{}".format(name))
return os.path.join(base_path(), "Missions", name)
def load_game(path):

View File

@ -34,11 +34,14 @@ class Settings:
player_income_multiplier: float = 1.0
enemy_income_multiplier: float = 1.0
#: Feature flag for squadron limits.
enable_squadron_pilot_limits: bool = False
#: The maximum number of pilots a squadron can have at one time. Changing this after
#: the campaign has started will have no immediate effect; pilots already in the
#: squadron will not be removed if the limit is lowered and pilots will not be
#: immediately created if the limit is raised.
squadron_pilot_limit: int = 24
squadron_pilot_limit: int = 4
#: The number of pilots a squadron can replace per turn.
squadron_replenishment_rate: int = 4

View File

@ -112,9 +112,19 @@ class Squadron:
return self.name
return f'{self.name} "{self.nickname}"'
@property
def pilot_limits_enabled(self) -> bool:
return self.game.settings.enable_squadron_pilot_limits
def claim_new_pilot_if_allowed(self) -> Optional[Pilot]:
if self.pilot_limits_enabled:
return None
self._recruit_pilots(1)
return self.available_pilots.pop()
def claim_available_pilot(self) -> Optional[Pilot]:
if not self.available_pilots:
return None
return self.claim_new_pilot_if_allowed()
# For opfor, so player/AI option is irrelevant.
if not self.player:
@ -140,7 +150,7 @@ class Squadron:
# If they only *prefer* players and we're out of players, just return an AI
# pilot.
if not prefer_players:
return None
return self.claim_new_pilot_if_allowed()
return self.available_pilots.pop()
def claim_pilot(self, pilot: Pilot) -> None:
@ -169,9 +179,12 @@ class Squadron:
self.available_pilots.extend(new_pilots)
def replenish_lost_pilots(self) -> None:
if not self.pilot_limits_enabled:
return
replenish_count = min(
self.game.settings.squadron_replenishment_rate,
self.number_of_unfilled_pilot_slots,
self._number_of_unfilled_pilot_slots,
)
if replenish_count > 0:
self._recruit_pilots(replenish_count)
@ -213,20 +226,23 @@ class Squadron:
return len(self.current_roster)
@property
def number_of_unfilled_pilot_slots(self) -> int:
def _number_of_unfilled_pilot_slots(self) -> int:
return self.game.settings.squadron_pilot_limit - len(self.active_pilots)
@property
def number_of_available_pilots(self) -> int:
return len(self.available_pilots)
def can_provide_pilots(self, count: int) -> bool:
return not self.pilot_limits_enabled or self.number_of_available_pilots >= count
@property
def has_available_pilots(self) -> bool:
return bool(self.available_pilots)
return not self.pilot_limits_enabled or bool(self.available_pilots)
@property
def has_unfilled_pilot_slots(self) -> bool:
return self.number_of_unfilled_pilot_slots > 0
return not self.pilot_limits_enabled or self._number_of_unfilled_pilot_slots > 0
def can_auto_assign(self, task: FlightType) -> bool:
return task in self.auto_assignable_mission_types
@ -368,9 +384,16 @@ class AirWing:
def squadrons_for(self, aircraft: AircraftType) -> Sequence[Squadron]:
return self.squadrons[aircraft]
def squadrons_for_task(self, task: FlightType) -> Iterator[Squadron]:
def can_auto_plan(self, task: FlightType) -> bool:
try:
next(self.auto_assignable_for_task(task))
return True
except StopIteration:
return False
def auto_assignable_for_task(self, task: FlightType) -> Iterator[Squadron]:
for squadron in self.iter_squadrons():
if task in squadron.mission_types:
if squadron.can_auto_assign(task):
yield squadron
def auto_assignable_for_task_with_type(

View File

@ -78,20 +78,33 @@ class GeneratorSettings:
no_enemy_navy: bool
@dataclass
class ModSettings:
a4_skyhawk: bool = False
f22_raptor: bool = False
hercules: bool = False
jas39_gripen: bool = False
su57_felon: bool = False
frenchpack: bool = False
high_digit_sams: bool = False
class GameGenerator:
def __init__(
self,
player: str,
enemy: str,
player: Faction,
enemy: Faction,
theater: ConflictTheater,
settings: Settings,
generator_settings: GeneratorSettings,
mod_settings: ModSettings,
) -> None:
self.player = player
self.enemy = enemy
self.theater = theater
self.settings = settings
self.generator_settings = generator_settings
self.mod_settings = mod_settings
def generate(self) -> Game:
with logged_duration("TGO population"):
@ -99,8 +112,8 @@ class GameGenerator:
namegen.reset()
self.prepare_theater()
game = Game(
player_name=self.player,
enemy_name=self.enemy,
player_faction=self.player.apply_mod_settings(self.mod_settings),
enemy_faction=self.enemy.apply_mod_settings(self.mod_settings),
theater=self.theater,
start_date=self.generator_settings.start_date,
settings=self.settings,
@ -159,9 +172,9 @@ class ControlPointGroundObjectGenerator:
@property
def faction_name(self) -> str:
if self.control_point.captured:
return self.game.player_name
return self.game.player_faction.name
else:
return self.game.enemy_name
return self.game.enemy_faction.name
@property
def faction(self) -> Faction:

View File

@ -1,3 +1,34 @@
"""Implements support for ground unit transfers between bases.
Ground units can be transferred between bases via a number of transport methods, and
doing so can take multiple turns.
There are a few main concepts here:
* A TransferOrder is a request to move units from one base to another. It is described
by its origin, destination, current position, and contents. TransferOrders persist
across turns, and if no Transport is available to move the units in a given turn it
will have no Transport assigned.
* Transports: A Transport is the planned move of a group of units for a leg of the
journey *this turn*. A Transport has an assigned mode of transportation and has
vehicles assigned to move the units if needed. This might be a Convoy, a CargoShip, or
an Airlift.
The TransportMap (more accurately, it's subtypes) is responsible for managing the
transports moving from A to B for the turn. Transfers that are moving between A and B
this turn will be added to the TransportMap, which will create a new transport if needed
or add the units to an existing transport if one exists. This allows transfers from
A->B->C and D->B->C to share a transport between B and C.
AirLifts do not use TransportMap because no merging will take place between orders. It
instead uses AirLiftPlanner to create transport packages.
PendingTransfers manages all the incomplete transfer orders for the game. New transfer
orders are registered with PendingTransfers and it is responsible for allocating
transports and processing the turn's transit actions.
Routing is handled by TransitNetwork.
"""
from __future__ import annotations
import logging
@ -6,7 +37,6 @@ from collections import defaultdict
from dataclasses import dataclass, field
from functools import singledispatchmethod
from typing import (
Dict,
Generic,
Iterator,
List,
@ -29,7 +59,7 @@ from game.theater.transitnetwork import (
)
from game.utils import meters, nautical_miles
from gen.ato import Package
from gen.flights.ai_flight_planner_db import TRANSPORT_CAPABLE, aircraft_for_task
from gen.flights.ai_flight_planner_db import aircraft_for_task
from gen.flights.closestairfields import ObjectiveDistanceCache
from gen.flights.flight import Flight, FlightType
from gen.flights.flightplan import FlightPlanBuilder
@ -72,10 +102,18 @@ class TransferOrder:
player: bool = field(init=False)
#: The units being transferred.
units: Dict[GroundUnitType, int]
units: dict[GroundUnitType, int]
transport: Optional[Transport] = field(default=None)
def __str__(self) -> str:
"""Returns the text that should be displayed for the transfer."""
count = self.size
origin = self.origin.name
destination = self.destination.name
description = "Transfer" if self.player else "Enemy transfer"
return f"{description} of {count} units from {origin} to {destination}"
def __post_init__(self) -> None:
self.position = self.origin
self.player = self.origin.is_friendly(to_player=True)
@ -91,12 +129,12 @@ class TransferOrder:
def kill_unit(self, unit_type: GroundUnitType) -> None:
if unit_type not in self.units or not self.units[unit_type]:
raise KeyError(f"{self.destination} has no {unit_type} remaining")
raise KeyError(f"{self} has no {unit_type} remaining")
self.units[unit_type] -= 1
@property
def size(self) -> int:
return sum(c for c in self.units.values())
return sum(self.units.values())
def iter_units(self) -> Iterator[GroundUnitType]:
for unit_type, count in self.units.items():
@ -105,7 +143,7 @@ class TransferOrder:
@property
def completed(self) -> bool:
return self.destination == self.position or not self.units
return self.destination == self.position or not self.size
def disband_at(self, location: ControlPoint) -> None:
logging.info(f"Units halting at {location}.")
@ -156,7 +194,7 @@ class Airlift(Transport):
self.flight = flight
@property
def units(self) -> Dict[GroundUnitType, int]:
def units(self) -> dict[GroundUnitType, int]:
return self.transfer.units
@property
@ -261,8 +299,12 @@ class AirliftPlanner:
required,
available_aircraft,
squadron.aircraft.dcs_unit_type.group_size_max,
squadron.number_of_available_pilots,
)
# TODO: Use number_of_available_pilots directly once feature flag is gone.
# The number of currently available pilots is not relevant when pilot limits
# are disabled.
if not squadron.can_provide_pilots(flight_size):
flight_size = squadron.number_of_available_pilots
capacity = flight_size * capacity_each
if capacity < self.transfer.size:
@ -334,11 +376,11 @@ class MultiGroupTransport(MissionTarget, Transport):
@property
def size(self) -> int:
return sum(sum(t.units.values()) for t in self.transfers)
return sum(t.size for t in self.transfers)
@property
def units(self) -> dict[GroundUnitType, int]:
units: Dict[GroundUnitType, int] = defaultdict(int)
units: dict[GroundUnitType, int] = defaultdict(int)
for transfer in self.transfers:
for unit_type, count in transfer.units.items():
units[unit_type] += count
@ -414,8 +456,8 @@ TransportType = TypeVar("TransportType", bound=MultiGroupTransport)
class TransportMap(Generic[TransportType]):
def __init__(self) -> None:
# Dict of origin -> destination -> transport.
self.transports: Dict[
ControlPoint, Dict[ControlPoint, TransportType]
self.transports: dict[
ControlPoint, dict[ControlPoint, TransportType]
] = defaultdict(dict)
def create_transport(
@ -592,7 +634,10 @@ class PendingTransfers:
def order_airlift_assets(self) -> None:
for control_point in self.game.theater.controlpoints:
self.order_airlift_assets_at(control_point)
if self.game.air_wing_for(control_point.captured).can_auto_plan(
FlightType.TRANSPORT
):
self.order_airlift_assets_at(control_point)
@staticmethod
def desired_airlift_capacity(control_point: ControlPoint) -> int:
@ -600,10 +645,10 @@ class PendingTransfers:
def current_airlift_capacity(self, control_point: ControlPoint) -> int:
inventory = self.game.aircraft_inventory.for_control_point(control_point)
squadrons = self.game.air_wing_for(control_point.captured).squadrons_for_task(
FlightType.TRANSPORT
)
unit_types = {s.aircraft for s in squadrons}.intersection(TRANSPORT_CAPABLE)
squadrons = self.game.air_wing_for(
control_point.captured
).auto_assignable_for_task(FlightType.TRANSPORT)
unit_types = {s.aircraft for s in squadrons}
return sum(
count
for unit_type, count in inventory.all_aircraft

View File

@ -3,7 +3,7 @@ from __future__ import annotations
import logging
from collections import defaultdict
from dataclasses import dataclass
from typing import Dict, Optional, TYPE_CHECKING, Any
from typing import Optional, TYPE_CHECKING, Any
from game.theater import ControlPoint
from .dcs.groundunittype import GroundUnitType
@ -28,16 +28,16 @@ class PendingUnitDeliveries:
self.destination = destination
# Maps unit type to order quantity.
self.units: Dict[UnitType, int] = defaultdict(int)
self.units: dict[UnitType, int] = defaultdict(int)
def __str__(self) -> str:
return f"Pending delivery to {self.destination}"
def order(self, units: Dict[UnitType, int]) -> None:
def order(self, units: dict[UnitType, int]) -> None:
for k, v in units.items():
self.units[k] += v
def sell(self, units: Dict[UnitType, int]) -> None:
def sell(self, units: dict[UnitType, int]) -> None:
for k, v in units.items():
self.units[k] -= v
@ -45,7 +45,15 @@ class PendingUnitDeliveries:
self.refund(game, self.units)
self.units = defaultdict(int)
def refund(self, game: Game, units: Dict[UnitType, int]) -> None:
def refund_ground_units(self, game: Game) -> None:
ground_units: dict[UnitType[Any], int] = {
u: self.units[u] for u in self.units.keys() if isinstance(u, GroundUnitType)
}
self.refund(game, ground_units)
for gu in ground_units.keys():
del self.units[gu]
def refund(self, game: Game, units: dict[UnitType, int]) -> None:
for unit_type, count in units.items():
logging.info(f"Refunding {count} {unit_type} at {self.destination.name}")
game.adjust_budget(
@ -69,12 +77,11 @@ class PendingUnitDeliveries:
f"{self.destination.name} lost its source for ground unit "
"reinforcements. Refunding purchase price."
)
self.refund_all(game)
return
self.refund_ground_units(game)
bought_units: Dict[UnitType, int] = {}
units_needing_transfer: Dict[GroundUnitType, int] = {}
sold_units: Dict[UnitType, int] = {}
bought_units: dict[UnitType, int] = {}
units_needing_transfer: dict[GroundUnitType, int] = {}
sold_units: dict[UnitType, int] = {}
for unit_type, count in self.units.items():
coalition = "Ally" if self.destination.captured else "Enemy"
d: dict[Any, int]
@ -102,11 +109,16 @@ class PendingUnitDeliveries:
self.destination.base.commit_losses(sold_units)
if units_needing_transfer:
if ground_unit_source is None:
raise RuntimeError(
f"ground unit source could not be found for {self.destination} but still tried to "
f"transfer units to there"
)
ground_unit_source.base.commission_units(units_needing_transfer)
self.create_transfer(game, ground_unit_source, units_needing_transfer)
def create_transfer(
self, game: Game, source: ControlPoint, units: Dict[GroundUnitType, int]
self, game: Game, source: ControlPoint, units: dict[GroundUnitType, int]
) -> None:
game.transfers.new_transfer(TransferOrder(source, self.destination, units))

View File

@ -2,7 +2,7 @@ from pathlib import Path
def _build_version_string() -> str:
components = ["4.0.0"]
components = ["5.0.0"]
build_number_path = Path("resources/buildnumber")
if build_number_path.exists():
with build_number_path.open("r") as build_number_file:
@ -90,4 +90,10 @@ VERSION = _build_version_string()
#:
#: Version 6.1
#: * Support for new Syrian airfields in DCS 2.7.2.7910.1 (Cyprus update).
CAMPAIGN_FORMAT_VERSION = (6, 1)
#:
#: Version 7.0
#: * DCS 2.7.2.7910.1 (Cyprus update) changed the IDs of scenery strike targets. Any
#: mission using map buildings as strike targets must check and potentially recreate
#: all those objectives. This definitely affects all Syria campaigns, other maps are
#: not yet verified.
CAMPAIGN_FORMAT_VERSION = (7, 0)

View File

@ -26,7 +26,6 @@ from dcs.planes import (
Su_33,
Tu_22M3,
)
from dcs.planes import IL_78M
from dcs.point import MovingPoint, PointAction
from dcs.task import (
AWACS,
@ -66,7 +65,6 @@ from dcs.unitgroup import FlyingGroup, ShipGroup, StaticGroup
from dcs.unittype import FlyingType
from game import db
from game.data.cap_capabilities_db import GUNFIGHTERS
from game.data.weapons import Pylon
from game.dcs.aircrafttype import AircraftType
from game.factions.faction import Faction
@ -866,7 +864,7 @@ class AircraftConflictGenerator:
group.task = CAP.name
self._setup_group(group, package, flight, dynamic_runways)
if flight.unit_type not in GUNFIGHTERS:
if not flight.unit_type.gunfighter:
ammo_type = OptRTBOnOutOfAmmo.Values.AAM
else:
ammo_type = OptRTBOnOutOfAmmo.Values.Cannon
@ -883,7 +881,7 @@ class AircraftConflictGenerator:
group.task = FighterSweep.name
self._setup_group(group, package, flight, dynamic_runways)
if flight.unit_type not in GUNFIGHTERS:
if not flight.unit_type.gunfighter:
ammo_type = OptRTBOnOutOfAmmo.Values.AAM
else:
ammo_type = OptRTBOnOutOfAmmo.Values.Cannon
@ -1208,7 +1206,7 @@ class AircraftConflictGenerator:
# under the current flight plans.
# TODO: Make this smarter, it currently selects a random unit in the group for target,
# this could be updated to make it pick the "best" two targets in the group.
if flight.unit_type is AJS37 and flight.client_count:
if flight.unit_type.dcs_unit_type is AJS37 and flight.client_count:
viggen_target_points = [
(idx, point)
for idx, point in enumerate(filtered_points)
@ -1372,9 +1370,10 @@ class PydcsWaypointBuilder:
"""Viggen player aircraft consider any waypoint with a TOT set to be a target ("M") waypoint.
If the flight is a player controlled Viggen flight, no TOT should be set on any waypoint except actual target waypoints.
"""
if (self.flight.client_count > 0 and self.flight.unit_type == AJS37) and (
self.waypoint.waypoint_type not in TARGET_WAYPOINTS
):
if (
self.flight.client_count > 0
and self.flight.unit_type.dcs_unit_type == AJS37
) and (self.waypoint.waypoint_type not in TARGET_WAYPOINTS):
return True
else:
return False
@ -1787,7 +1786,7 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
def configure_refueling_actions(self, waypoint: MovingPoint) -> None:
waypoint.add_task(Tanker())
if self.flight.unit_type != IL_78M:
if self.flight.unit_type.dcs_unit_type.tacan:
tanker_info = self.air_support.tankers[-1]
tacan = tanker_info.tacan
tacan_callsign = {

View File

@ -153,6 +153,8 @@ class Conflict:
if theater.is_on_land(pos):
return pos
pos = initial.point_from_heading(opposite_heading(heading), distance)
if theater.is_on_land(pos):
return pos
if coerce:
pos = theater.nearest_land_pos(initial)
return pos

View File

@ -178,7 +178,7 @@ class AircraftAllocator:
aircraft, task
)
for squadron in squadrons:
if squadron.number_of_available_pilots >= flight.num_aircraft:
if squadron.can_provide_pilots(flight.num_aircraft):
inventory.remove_aircraft(aircraft, flight.num_aircraft)
return airfield, squadron
return None
@ -604,27 +604,35 @@ class CoalitionMissionPlanner:
also possible for the player to exclude mission types from their squadron
designs.
"""
all_compatible = aircraft_for_task(mission_type)
for squadron in self.game.air_wing_for(self.is_player).iter_squadrons():
if (
squadron.aircraft in all_compatible
and mission_type in squadron.auto_assignable_mission_types
):
return True
return False
return self.game.air_wing_for(self.is_player).can_auto_plan(mission_type)
@property
def oca_aircraft_plannable(self) -> bool:
return (
self.air_wing_can_plan(FlightType.OCA_AIRCRAFT)
and self.game.settings.default_start_type == "Cold"
def critical_missions(self) -> Iterator[ProposedMission]:
"""Identifies the most important missions to plan this turn.
Non-critical missions that cannot be fulfilled will create purchase
orders for the next turn. Critical missions will create a purchase order
unless the mission can be doubly fulfilled. In other words, the AI will
attempt to have *double* the aircraft it needs for these missions to
ensure that they can be planned again next turn even if all aircraft are
eliminated this turn.
"""
# Find farthest, friendly CP for AEWC.
yield ProposedMission(
self.objective_finder.farthest_friendly_control_point(),
[ProposedFlight(FlightType.AEWC, 1, self.MAX_AWEC_RANGE)],
# Supports all the early CAP flights, so should be in the air ASAP.
asap=True,
)
yield ProposedMission(
self.objective_finder.closest_friendly_control_point(),
[ProposedFlight(FlightType.REFUELING, 1, self.MAX_TANKER_RANGE)],
)
def propose_barcap(self) -> Iterator[ProposedMission]:
# Find friendly CPs within 100 nmi from an enemy airfield, plan CAP.
for cp in self.objective_finder.vulnerable_control_points():
# Plan CAP in such a way, that it is established during the whole desired
# mission length.
# Plan CAP in such a way, that it is established during the whole desired mission length
for _ in range(
0,
int(self.game.settings.desired_player_mission_duration.total_seconds()),
@ -637,31 +645,36 @@ class CoalitionMissionPlanner:
],
)
def propose_cas(self) -> Iterator[ProposedMission]:
# Find front lines, plan CAS.
for front_line in self.objective_finder.front_lines():
flights = [ProposedFlight(FlightType.CAS, 2, self.MAX_CAS_RANGE)]
if self.air_wing_can_plan(FlightType.TARCAP):
# This is *not* an escort because front lines don't create a threat
# zone. Generating threat zones from front lines causes the front
# line to push back BARCAPs as it gets closer to the base. While
# front lines do have the same problem of potentially pulling
# BARCAPs off bases to engage a front line TARCAP, that's probably
# the one time where we do want that.
#
# TODO: Use intercepts and extra TARCAPs to cover bases near fronts.
# We don't have intercept missions yet so this isn't something we
# can do today, but we should probably return to having the front
# line project a threat zone (so that strike missions will route
# around it) and instead *not plan* a BARCAP at bases near the
# front, since there isn't a place to put a barrier. Instead, the
# aircraft that would have been a BARCAP could be used as additional
# interceptors and TARCAPs which will defend the base but won't be
# trying to avoid front line contacts.
flights.append(ProposedFlight(FlightType.TARCAP, 2, self.MAX_CAP_RANGE))
yield ProposedMission(front_line, flights)
yield ProposedMission(
front_line,
[
ProposedFlight(FlightType.CAS, 2, self.MAX_CAS_RANGE),
# This is *not* an escort because front lines don't create a threat
# zone. Generating threat zones from front lines causes the front
# line to push back BARCAPs as it gets closer to the base. While
# front lines do have the same problem of potentially pulling
# BARCAPs off bases to engage a front line TARCAP, that's probably
# the one time where we do want that.
#
# TODO: Use intercepts and extra TARCAPs to cover bases near fronts.
# We don't have intercept missions yet so this isn't something we
# can do today, but we should probably return to having the front
# line project a threat zone (so that strike missions will route
# around it) and instead *not plan* a BARCAP at bases near the
# front, since there isn't a place to put a barrier. Instead, the
# aircraft that would have been a BARCAP could be used as additional
# interceptors and TARCAPs which will defend the base but won't be
# trying to avoid front line contacts.
ProposedFlight(FlightType.TARCAP, 2, self.MAX_CAP_RANGE),
],
)
def propose_missions(self) -> Iterator[ProposedMission]:
"""Identifies and iterates over potential mission in priority order."""
yield from self.critical_missions()
def propose_dead(self) -> Iterator[ProposedMission]:
# Find enemy SAM sites with ranges that cover friendly CPs, front lines,
# or objects, plan DEAD.
# Find enemy SAM sites with ranges that extend to within 50 nmi of
@ -686,10 +699,7 @@ class CoalitionMissionPlanner:
else:
flights.append(
ProposedFlight(
FlightType.SEAD_ESCORT,
2,
self.MAX_SEAD_RANGE,
EscortType.Sead,
FlightType.SEAD_ESCORT, 2, self.MAX_SEAD_RANGE, EscortType.Sead
)
)
# TODO: Max escort range.
@ -700,7 +710,6 @@ class CoalitionMissionPlanner:
)
yield ProposedMission(sam, flights)
def propose_convoy_interdiction(self) -> Iterator[ProposedMission]:
# These will only rarely get planned. When a convoy is travelling multiple legs,
# they're targetable after the first leg. The reason for this is that
# procurement happens *after* mission planning so that the missions that could
@ -729,7 +738,6 @@ class CoalitionMissionPlanner:
],
)
def propose_shipping_interdiction(self) -> Iterator[ProposedMission]:
for ship in self.objective_finder.cargo_ships():
yield ProposedMission(
ship,
@ -745,7 +753,6 @@ class CoalitionMissionPlanner:
],
)
def propose_naval_strikes(self) -> Iterator[ProposedMission]:
for group in self.objective_finder.threatening_ships():
yield ProposedMission(
group,
@ -761,7 +768,6 @@ class CoalitionMissionPlanner:
],
)
def propose_bai(self) -> Iterator[ProposedMission]:
for group in self.objective_finder.threatening_vehicle_groups():
yield ProposedMission(
group,
@ -777,25 +783,16 @@ class CoalitionMissionPlanner:
],
)
def propose_oca_strikes(self) -> Iterator[ProposedMission]:
for target in self.objective_finder.oca_targets(min_aircraft=20):
flights = []
if self.air_wing_can_plan(FlightType.OCA_RUNWAY):
flights.append(
ProposedFlight(FlightType.OCA_RUNWAY, 2, self.MAX_OCA_RANGE)
)
if self.oca_aircraft_plannable:
flights = [
ProposedFlight(FlightType.OCA_RUNWAY, 2, self.MAX_OCA_RANGE),
]
if self.game.settings.default_start_type == "Cold":
# Only schedule if the default start type is Cold. If the player
# has set anything else there are no targets to hit.
flights.append(
ProposedFlight(FlightType.OCA_AIRCRAFT, 2, self.MAX_OCA_RANGE)
)
if not flights:
raise RuntimeError(
"Attempted planning of OCA strikes but neither OCA/Runway nor "
f"OCA/Aircraft are plannable for {self.faction.name} with the "
"current game settings."
)
flights.extend(
[
# TODO: Max escort range.
@ -809,7 +806,7 @@ class CoalitionMissionPlanner:
)
yield ProposedMission(target, flights)
def propose_building_strikes(self) -> Iterator[ProposedMission]:
# Plan strike missions.
for target in self.objective_finder.strike_targets():
yield ProposedMission(
target,
@ -828,48 +825,6 @@ class CoalitionMissionPlanner:
],
)
def propose_missions(self) -> Iterator[ProposedMission]:
"""Identifies and iterates over potential mission in priority order."""
# Find farthest, friendly CP for AEWC.
if self.air_wing_can_plan(FlightType.AEWC):
yield ProposedMission(
self.objective_finder.farthest_friendly_control_point(),
[ProposedFlight(FlightType.AEWC, 1, self.MAX_AWEC_RANGE)],
# Supports all the early CAP flights, so should be in the air ASAP.
asap=True,
)
if self.air_wing_can_plan(FlightType.REFUELING):
yield ProposedMission(
self.objective_finder.closest_friendly_control_point(),
[ProposedFlight(FlightType.REFUELING, 1, self.MAX_TANKER_RANGE)],
)
if self.air_wing_can_plan(FlightType.BARCAP):
yield from self.propose_barcap()
if self.air_wing_can_plan(FlightType.CAS):
yield from self.propose_cas()
if self.air_wing_can_plan(FlightType.DEAD):
yield from self.propose_dead()
if self.air_wing_can_plan(FlightType.BAI):
yield from self.propose_convoy_interdiction()
if self.air_wing_can_plan(FlightType.ANTISHIP):
yield from self.propose_shipping_interdiction()
yield from self.propose_naval_strikes()
if self.air_wing_can_plan(FlightType.BAI):
yield from self.propose_bai()
if self.air_wing_can_plan(FlightType.OCA_RUNWAY) or self.oca_aircraft_plannable:
yield from self.propose_oca_strikes()
if self.air_wing_can_plan(FlightType.STRIKE):
yield from self.propose_building_strikes()
def plan_missions(self) -> None:
"""Identifies and plans mission for the turn."""
player = "Blue" if self.is_player else "Red"
@ -878,6 +833,11 @@ class CoalitionMissionPlanner:
for proposed_mission in self.propose_missions():
self.plan_mission(proposed_mission, tracer)
with logged_duration(f"{player} reserve mission planning"):
with MultiEventTracer() as tracer:
for critical_mission in self.critical_missions():
self.plan_mission(critical_mission, tracer, reserves=True)
with logged_duration(f"{player} mission scheduling"):
self.stagger_missions()

View File

@ -111,7 +111,6 @@ from pydcs_extensions.a4ec.a4ec import A_4E_C
from pydcs_extensions.f22a.f22a import F_22A
from pydcs_extensions.hercules.hercules import Hercules
from pydcs_extensions.jas39.jas39 import JAS39Gripen, JAS39Gripen_AG
from pydcs_extensions.mb339.mb339 import MB_339PAN
from pydcs_extensions.su57.su57 import Su_57
# All aircraft lists are in priority order. Aircraft higher in the list will be
@ -219,7 +218,6 @@ CAS_CAPABLE = [
F_5E_3,
F_86F_Sabre,
C_101CC,
MB_339PAN,
L_39ZA,
A_20G,
Ju_88A4,
@ -332,7 +330,6 @@ STRIKE_CAPABLE = [
MiG_15bis,
F_5E_3,
F_86F_Sabre,
MB_339PAN,
C_101CC,
L_39ZA,
B_17G,

View File

@ -16,17 +16,6 @@ from functools import cached_property
from typing import Iterator, List, Optional, Set, TYPE_CHECKING, Tuple
from dcs.mapping import Point
from dcs.planes import (
E_3A,
E_2C,
A_50,
IL_78M,
KC130,
KC135MPRS,
KC_135,
KJ_2000,
S_3B_Tanker,
)
from dcs.unit import Unit
from shapely.geometry import Point as ShapelyPoint
@ -38,8 +27,9 @@ from game.theater import (
MissionTarget,
SamGroundObject,
TheaterGroundObject,
NavalControlPoint,
)
from game.theater.theatergroundobject import EwrGroundObject
from game.theater.theatergroundobject import EwrGroundObject, NavalGroundObject
from game.utils import Distance, Speed, feet, meters, nautical_miles, knots
from .closestairfields import ObjectiveDistanceCache
from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
@ -1092,15 +1082,8 @@ class FlightPlanBuilder:
orbit_location = self.aewc_orbit(location)
# As high as possible to maximize detection and on-station time.
if flight.unit_type == E_2C:
patrol_alt = feet(30000)
elif flight.unit_type == E_3A:
patrol_alt = feet(35000)
elif flight.unit_type == A_50:
patrol_alt = feet(33000)
elif flight.unit_type == KJ_2000:
patrol_alt = feet(40000)
if flight.unit_type.patrol_altitude is not None:
patrol_alt = flight.unit_type.patrol_altitude
else:
patrol_alt = feet(25000)
@ -1164,12 +1147,9 @@ class FlightPlanBuilder:
from game.transfers import CargoShip
if isinstance(location, ControlPoint):
if not location.is_fleet:
raise InvalidObjectiveLocation(flight.flight_type, location)
# The first group generated will be the carrier group itself.
targets = self.anti_ship_targets_for_tgo(location.ground_objects[0])
elif isinstance(location, TheaterGroundObject):
if isinstance(location, NavalControlPoint):
targets = self.anti_ship_targets_for_tgo(location.find_main_tgo())
elif isinstance(location, NavalGroundObject):
targets = self.anti_ship_targets_for_tgo(location)
elif isinstance(location, CargoShip):
targets = [StrikeTarget(location.name, location)]
@ -1680,31 +1660,17 @@ class FlightPlanBuilder:
builder = WaypointBuilder(flight, self.game, self.is_player)
tanker_type = flight.unit_type
if tanker_type is KC_135:
# ~300 knots IAS.
speed = knots(445)
altitude = feet(24000)
elif tanker_type is KC135MPRS:
# ~300 knots IAS.
speed = knots(440)
altitude = feet(23000)
elif tanker_type is KC130:
# ~210 knots IAS, roughly the max for the KC-130 at altitude.
speed = knots(370)
altitude = feet(22000)
elif tanker_type is S_3B_Tanker:
# ~265 knots IAS.
speed = knots(320)
altitude = feet(12000)
elif tanker_type is IL_78M:
# ~280 knots IAS.
speed = knots(400)
altitude = feet(21000)
if tanker_type.patrol_altitude is not None:
altitude = tanker_type.patrol_altitude
else:
# ~280 knots IAS.
speed = knots(400)
altitude = feet(21000)
if tanker_type.patrol_speed is not None:
speed = tanker_type.patrol_speed
else:
# ~280 knots IAS at 21000.
speed = knots(400)
racetrack = builder.race_track(racetrack_start, racetrack_end, altitude)
return RefuelingFlightPlan(

1
pydcs

@ -1 +0,0 @@
Subproject commit a459d8e1b0bd59bbd7f35390d7ad1bedc0caf76b

View File

@ -1,539 +0,0 @@
from enum import Enum
from dcs import task
from dcs.planes import PlaneType
from dcs.weapons_data import Weapons
from pydcs_extensions.weapon_injector import inject_weapons
class MB_339PAN_Weapons:
ARF8M3_TP = {"clsid": "{ARF8M3_TP}", "name": "ARF8M3 TP", "weight": None}
BRD_4_250_4_MK_76_2_ARF_8M3TP_ = {
"clsid": "{BRD-4-250}",
"name": "BRD-4-250(4*MK.76+2*ARF-8M3TP)",
"weight": 137.6,
}
Color_Oil_Tank = {"clsid": "{COLOR-TANK}", "name": "Color Oil Tank", "weight": 183}
Empty_Pylon = {"clsid": "{VOID-PYLON-MB339A}", "name": "Empty Pylon", "weight": 20}
Fuel_Tank_330lt = {
"clsid": "{FUEL-SUBAL_TANK-330}",
"name": "Fuel Tank 330lt",
"weight": 315,
}
GunPod_AN_M3 = {"clsid": "{MB339-AN-M3_L}", "name": "GunPod AN/M3", "weight": 75}
GunPod_AN_M3_ = {"clsid": "{MB339-AN-M3_R}", "name": "GunPod AN/M3", "weight": 75}
GunPod_DEFA553 = {
"clsid": "{MB339-DEFA553_L}",
"name": "GunPod DEFA553",
"weight": 190,
}
GunPod_DEFA553_ = {
"clsid": "{MB339-DEFA553_R}",
"name": "GunPod DEFA553",
"weight": 190,
}
LAU_10___4_ZUNI_MK_71___ = {
"clsid": "{LAU-10}",
"name": "LAU-10 - 4 ZUNI MK 71",
"weight": 308,
}
LR_25___25_ARF_8M3_API_ = {
"clsid": "{LR-25API}",
"name": "LR-25 - 25 ARF/8M3(API)",
"weight": 141,
}
LR_25___25_ARF_8M3_HEI_ = {
"clsid": "{LR-25HEI}",
"name": "LR-25 - 25 ARF/8M3(HEI)",
"weight": 161,
}
MAK79_2_MK_20 = {"clsid": "{MAK79_MK20 2L}", "name": "MAK79 2 MK-20", "weight": 464}
MAK79_2_MK_20_ = {
"clsid": "{MAK79_MK20 2R}",
"name": "MAK79 2 MK-20",
"weight": 464,
}
MAK79_MK_20 = {"clsid": "{MAK79_MK20 1R}", "name": "MAK79 MK-20", "weight": 232}
MAK79_MK_20_ = {"clsid": "{MAK79_MK20 1L}", "name": "MAK79 MK-20", "weight": 232}
MB339_Black_Smoke = {
"clsid": "{SMOKE-BLACK-MB339}",
"name": "MB339 Black Smoke",
"weight": 1,
}
MB339_Green_Smoke = {
"clsid": "{SMOKE-GREEN-MB339}",
"name": "MB339 Green Smoke",
"weight": 1,
}
MB339_ORANGE_Smoke = {
"clsid": "{SMOKE-ORANGE-MB339}",
"name": "MB339 ORANGE Smoke",
"weight": 1,
}
MB339_Red_Smoke = {
"clsid": "{SMOKE-RED-MB339}",
"name": "MB339 Red Smoke",
"weight": 1,
}
MB339_White_Smoke = {
"clsid": "{SMOKE-WHITE-MB339}",
"name": "MB339 White Smoke",
"weight": 1,
}
MB339_YELLOW_Smoke = {
"clsid": "{SMOKE-YELLOW-MB339}",
"name": "MB339 YELLOW Smoke",
"weight": 1,
}
MK76 = {"clsid": "{MK76}", "name": "MK76", "weight": 11.3}
Tip_Fuel_Tank_500lt = {
"clsid": "{FUEL-TIP-TANK-500-L}",
"name": "Tip Fuel Tank 500lt",
"weight": 471,
}
Tip_Fuel_Tank_500lt_ = {
"clsid": "{FUEL-TIP-TANK-500-R}",
"name": "Tip Fuel Tank 500lt",
"weight": 471,
}
Tip_Fuel_Tank_Ellittici_320lt = {
"clsid": "{FUEL-TIP-ELLITTIC-L}",
"name": "Tip Fuel Tank Ellittici 320lt",
"weight": 314.2,
}
Tip_Fuel_Tank_Ellittici_320lt_ = {
"clsid": "{FUEL-TIP-ELLITTIC-R}",
"name": "Tip Fuel Tank Ellittici 320lt",
"weight": 314.2,
}
inject_weapons(MB_339PAN_Weapons)
class MB_339PAN(PlaneType):
id = "MB-339PAN"
flyable = True
height = 4.77
width = 10.5
length = 12.13
fuel_max = 626
max_speed = 763.2
category = "Interceptor" # {78EFB7A2-FD52-4b57-A6A6-3BF0E1D6555F}
radio_frequency = 124
panel_radio = {
1: {
"channels": {
1: 225,
2: 258,
4: 270,
8: 257,
16: 252,
17: 268,
9: 253,
18: 269,
5: 255,
10: 263,
20: 269,
11: 267,
3: 260,
6: 259,
12: 254,
13: 264,
7: 262,
14: 266,
19: 268,
15: 265,
},
},
2: {
"channels": {
1: 225,
2: 258,
4: 270,
8: 257,
16: 252,
17: 268,
9: 253,
18: 269,
5: 255,
10: 263,
20: 269,
30: 263,
21: 225,
11: 267,
22: 258,
3: 260,
6: 259,
12: 254,
24: 270,
19: 268,
25: 255,
13: 264,
26: 259,
27: 262,
7: 262,
14: 266,
28: 257,
23: 260,
29: 253,
15: 265,
},
},
}
property_defaults = {
"SoloFlight": False,
"NetCrewControlPriority": 1,
}
class Properties:
class SoloFlight:
id = "SoloFlight"
class NetCrewControlPriority:
id = "NetCrewControlPriority"
class Values:
Pilot = 0
Instructor = 1
Ask_Always = -1
Equally_Responsible = -2
class Liveries:
class Georgia(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Syria(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Finland(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Australia(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Germany(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class SaudiArabia(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Israel(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Croatia(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class CzechRepublic(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Norway(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Romania(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Spain(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Ukraine(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Belgium(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Slovakia(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Greece(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class UK(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Insurgents(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Hungary(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class France(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Abkhazia(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Russia(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Sweden(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Austria(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Switzerland(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Italy(Enum):
MB339PAN__Frecce_Tricolori = "MB339PAN 'Frecce Tricolori'"
MB339A__SVBIA____FACTORY = "MB339A 'SVBIA' - FACTORY"
MB339A__61BRIGATA____CAMO = "MB339A '61BRIGATA' - CAMO"
MB339A__61STORMO____CAMO = "MB339A '61STORMO' - CAMO"
MB339A__61STORMO____GREY = "MB339A '61STORMO' - GREY"
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class SouthOssetia(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class SouthKorea(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Iran(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class China(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Pakistan(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Belarus(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class NorthKorea(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Iraq(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Kazakhstan(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Bulgaria(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Serbia(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class India(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class USAFAggressors(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class USA(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Denmark(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Egypt(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Canada(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class TheNetherlands(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Turkey(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Japan(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Poland(Enum):
MB339AA__ARMADA____Crippa = "MB339AA 'ARMADA' - Crippa"
MB339AA__ARMADA____Yellow_Band = "MB339AA 'ARMADA' - Yellow Band"
MB339__Factory = "MB339 'Factory'"
class Pylon1:
Tip_Fuel_Tank_500lt = (1, MB_339PAN_Weapons.Tip_Fuel_Tank_500lt)
Tip_Fuel_Tank_Ellittici_320lt = (
1,
MB_339PAN_Weapons.Tip_Fuel_Tank_Ellittici_320lt,
)
class Pylon2:
Empty_Pylon = (2, MB_339PAN_Weapons.Empty_Pylon)
LR_25___25_ARF_8M3_HEI_ = (2, MB_339PAN_Weapons.LR_25___25_ARF_8M3_HEI_)
LR_25___25_ARF_8M3_API_ = (2, MB_339PAN_Weapons.LR_25___25_ARF_8M3_API_)
Mk_82 = (2, Weapons.Mk_82)
Matra_Type_155_Rocket_Pod = (2, Weapons.Matra_Type_155_Rocket_Pod)
class Pylon3:
Fuel_Tank_330lt = (3, MB_339PAN_Weapons.Fuel_Tank_330lt)
Empty_Pylon = (3, MB_339PAN_Weapons.Empty_Pylon)
LR_25___25_ARF_8M3_HEI_ = (3, MB_339PAN_Weapons.LR_25___25_ARF_8M3_HEI_)
LR_25___25_ARF_8M3_API_ = (3, MB_339PAN_Weapons.LR_25___25_ARF_8M3_API_)
Mk_82 = (3, Weapons.Mk_82)
LAU_10___4_ZUNI_MK_71___ = (3, MB_339PAN_Weapons.LAU_10___4_ZUNI_MK_71___)
BRD_4_250_4_MK_76_2_ARF_8M3TP_ = (
3,
MB_339PAN_Weapons.BRD_4_250_4_MK_76_2_ARF_8M3TP_,
)
Matra_Type_155_Rocket_Pod = (3, Weapons.Matra_Type_155_Rocket_Pod)
class Pylon4:
Color_Oil_Tank = (4, MB_339PAN_Weapons.Color_Oil_Tank)
Empty_Pylon = (4, MB_339PAN_Weapons.Empty_Pylon)
GunPod_AN_M3 = (4, MB_339PAN_Weapons.GunPod_AN_M3)
GunPod_DEFA553 = (4, MB_339PAN_Weapons.GunPod_DEFA553)
LR_25___25_ARF_8M3_HEI_ = (4, MB_339PAN_Weapons.LR_25___25_ARF_8M3_HEI_)
LR_25___25_ARF_8M3_API_ = (4, MB_339PAN_Weapons.LR_25___25_ARF_8M3_API_)
Mk_82 = (4, Weapons.Mk_82)
Matra_Type_155_Rocket_Pod = (4, Weapons.Matra_Type_155_Rocket_Pod)
class Pylon5:
MB339_Red_Smoke = (5, MB_339PAN_Weapons.MB339_Red_Smoke)
MB339_Green_Smoke = (5, MB_339PAN_Weapons.MB339_Green_Smoke)
MB339_YELLOW_Smoke = (5, MB_339PAN_Weapons.MB339_YELLOW_Smoke)
MB339_ORANGE_Smoke = (5, MB_339PAN_Weapons.MB339_ORANGE_Smoke)
MB339_Black_Smoke = (5, MB_339PAN_Weapons.MB339_Black_Smoke)
class Pylon6:
MB339_White_Smoke = (6, MB_339PAN_Weapons.MB339_White_Smoke)
class Pylon7:
Color_Oil_Tank = (7, MB_339PAN_Weapons.Color_Oil_Tank)
Empty_Pylon = (7, MB_339PAN_Weapons.Empty_Pylon)
GunPod_AN_M3_ = (7, MB_339PAN_Weapons.GunPod_AN_M3_)
GunPod_DEFA553_ = (7, MB_339PAN_Weapons.GunPod_DEFA553_)
LR_25___25_ARF_8M3_HEI_ = (7, MB_339PAN_Weapons.LR_25___25_ARF_8M3_HEI_)
LR_25___25_ARF_8M3_API_ = (7, MB_339PAN_Weapons.LR_25___25_ARF_8M3_API_)
Mk_82 = (7, Weapons.Mk_82)
Matra_Type_155_Rocket_Pod = (7, Weapons.Matra_Type_155_Rocket_Pod)
class Pylon8:
Fuel_Tank_330lt = (8, MB_339PAN_Weapons.Fuel_Tank_330lt)
Empty_Pylon = (8, MB_339PAN_Weapons.Empty_Pylon)
LR_25___25_ARF_8M3_HEI_ = (8, MB_339PAN_Weapons.LR_25___25_ARF_8M3_HEI_)
LR_25___25_ARF_8M3_API_ = (8, MB_339PAN_Weapons.LR_25___25_ARF_8M3_API_)
Mk_82 = (8, Weapons.Mk_82)
LAU_10___4_ZUNI_MK_71___ = (8, MB_339PAN_Weapons.LAU_10___4_ZUNI_MK_71___)
Matra_Type_155_Rocket_Pod = (8, Weapons.Matra_Type_155_Rocket_Pod)
BRD_4_250_4_MK_76_2_ARF_8M3TP_ = (
8,
MB_339PAN_Weapons.BRD_4_250_4_MK_76_2_ARF_8M3TP_,
)
class Pylon9:
Empty_Pylon = (9, MB_339PAN_Weapons.Empty_Pylon)
LR_25___25_ARF_8M3_HEI_ = (9, MB_339PAN_Weapons.LR_25___25_ARF_8M3_HEI_)
LR_25___25_ARF_8M3_API_ = (9, MB_339PAN_Weapons.LR_25___25_ARF_8M3_API_)
Mk_82 = (9, Weapons.Mk_82)
Matra_Type_155_Rocket_Pod = (9, Weapons.Matra_Type_155_Rocket_Pod)
class Pylon10:
Tip_Fuel_Tank_500lt_ = (10, MB_339PAN_Weapons.Tip_Fuel_Tank_500lt_)
Tip_Fuel_Tank_Ellittici_320lt_ = (
10,
MB_339PAN_Weapons.Tip_Fuel_Tank_Ellittici_320lt_,
)
pylons = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
tasks = [
task.GroundAttack,
task.RunwayAttack,
task.CAS,
task.AntishipStrike,
task.Reconnaissance,
]
task_default = task.Nothing

View File

@ -3,13 +3,11 @@ from pydcs_extensions.f22a.f22a import F_22A
from pydcs_extensions.hercules.hercules import Hercules
from pydcs_extensions.highdigitsams import highdigitsams
from pydcs_extensions.jas39.jas39 import JAS39Gripen, JAS39Gripen_AG
from pydcs_extensions.mb339.mb339 import MB_339PAN
from pydcs_extensions.su57.su57 import Su_57
import pydcs_extensions.frenchpack.frenchpack as frenchpack
MODDED_AIRPLANES = [
A_4E_C,
MB_339PAN,
Su_57,
F_22A,
Hercules,

View File

@ -18,9 +18,10 @@ from game.data.weapons import (
WEAPON_INTRODUCTION_YEARS,
Weapon,
)
from game.db import FACTIONS
from game.profiling import logged_duration
from game.settings import Settings
from game.theater.start_generator import GameGenerator, GeneratorSettings
from game.theater.start_generator import GameGenerator, GeneratorSettings, ModSettings
from qt_ui import (
liberation_install,
liberation_theme,
@ -199,8 +200,8 @@ def create_game(
inject_custom_payloads(Path(persistency.base_path()))
campaign = Campaign.from_json(campaign_path)
generator = GameGenerator(
blue,
red,
FACTIONS[blue],
FACTIONS[red],
campaign.load_theater(),
Settings(
supercarrier=supercarrier,
@ -221,6 +222,15 @@ def create_game(
no_player_navy=False,
no_enemy_navy=False,
),
ModSettings(
a4_skyhawk=False,
f22_raptor=False,
hercules=False,
jas39_gripen=False,
su57_felon=False,
frenchpack=False,
high_digit_sams=False,
),
)
return generator.generate()

View File

@ -2,7 +2,7 @@
from __future__ import annotations
import datetime
from typing import Any, Callable, Dict, Iterator, Optional, TypeVar
from typing import Any, Callable, Iterator, Optional, TypeVar
from PySide2.QtCore import (
QAbstractListModel,
@ -51,7 +51,7 @@ class DeletableChildModelManager:
#: The type of model managed by this class.
ModelType = TypeVar("ModelType")
ModelDict = Dict[DataType, ModelType]
ModelDict = dict[DataType, ModelType]
def __init__(
self,
@ -334,11 +334,7 @@ class TransferModel(QAbstractListModel):
@staticmethod
def text_for_transfer(transfer: TransferOrder) -> str:
"""Returns the text that should be displayed for the transfer."""
count = sum(transfer.units.values())
origin = transfer.origin.name
destination = transfer.destination.name
description = "Transfer" if transfer.player else "Enemy transfer"
return f"{description} of {count} units from {origin} to {destination}"
return str(transfer)
@staticmethod
def icon_for_transfer(_transfer: TransferOrder) -> Optional[QIcon]:

View File

@ -24,8 +24,8 @@ class QFactionsInfos(QGroupBox):
def setGame(self, game: Game):
if game is not None:
self.player_name.setText(game.player_name)
self.enemy_name.setText(game.enemy_name)
self.player_name.setText(game.player_faction.name)
self.enemy_name.setText(game.enemy_faction.name)
else:
self.player_name.setText("")
self.enemy_name.setText("")

View File

@ -46,7 +46,10 @@ class QTopPanel(QFrame):
self.conditionsWidget = QConditionsWidget()
self.budgetBox = QBudgetBox(self.game)
self.passTurnButton = QPushButton("Pass Turn")
pass_turn_text = "Pass Turn"
if not self.game or self.game.turn == 0:
pass_turn_text = "Begin Campaign"
self.passTurnButton = QPushButton(pass_turn_text)
self.passTurnButton.setIcon(CONST.ICONS["PassTurn"])
self.passTurnButton.setProperty("style", "btn-primary")
self.passTurnButton.clicked.connect(self.passTurn)
@ -114,6 +117,8 @@ class QTopPanel(QFrame):
self.factionsInfos.setGame(game)
self.passTurnButton.setEnabled(True)
if game and game.turn > 0:
self.passTurnButton.setText("Pass Turn")
if game and game.turn == 0:
self.proceedButton.setEnabled(False)
@ -267,8 +272,8 @@ class QTopPanel(QFrame):
closest_cps[0],
closest_cps[1],
self.game.theater.controlpoints[0].position,
self.game.player_name,
self.game.enemy_name,
self.game.player_faction.name,
self.game.enemy_faction.name,
)
unit_map = self.game.initiate_event(game_event)

View File

@ -50,7 +50,7 @@ class QLiberationWindow(QMainWindow):
self.liberation_map = QLiberationMap(self.game_model, self)
self.setGeometry(300, 100, 270, 100)
self.setWindowTitle(f"DCS Liberation - v{VERSION}")
self.updateWindowTitle()
self.setWindowIcon(QIcon("./resources/icon.png"))
self.statusBar().showMessage("Ready")
@ -71,6 +71,7 @@ class QLiberationWindow(QMainWindow):
logging.info("Loading last saved game : " + str(last_save_file))
game = persistency.load_game(last_save_file)
self.onGameGenerated(game)
self.updateWindowTitle(last_save_file if game else None)
except:
logging.info("Error loading latest save game")
else:
@ -236,13 +237,15 @@ class QLiberationWindow(QMainWindow):
file = QFileDialog.getOpenFileName(
self,
"Select game file to open",
dir=persistency._dcs_saved_game_folder,
dir=self.game.savepath if self.game else persistency._dcs_saved_game_folder,
filter="*.liberation",
)
if file is not None:
if file is not None and file[0] != "":
game = persistency.load_game(file[0])
GameUpdateSignal.get_instance().game_loaded.emit(game)
self.updateWindowTitle(file[0])
def saveGame(self):
logging.info("Saving game")
@ -257,7 +260,7 @@ class QLiberationWindow(QMainWindow):
file = QFileDialog.getSaveFileName(
self,
"Save As",
dir=persistency._dcs_saved_game_folder,
dir=self.game.savepath if self.game else persistency._dcs_saved_game_folder,
filter="*.liberation",
)
if file is not None:
@ -266,7 +269,20 @@ class QLiberationWindow(QMainWindow):
liberation_install.setup_last_save_file(self.game.savepath)
liberation_install.save_config()
self.updateWindowTitle(file[0])
def updateWindowTitle(self, save_path: Optional[str] = None) -> None:
"""
to DCS Liberation - vX.X.X - file_name
"""
window_title = f"DCS Liberation - v{VERSION}"
if save_path: # appending the file name to title as it is updated
file_name = save_path.split("/")[-1].split(".liberation")[0]
window_title = f"{window_title} - {file_name}"
self.setWindowTitle(window_title)
def onGameGenerated(self, game: Game):
self.updateWindowTitle()
logging.info("On Game generated")
self.game = game
GameUpdateSignal.get_instance().game_loaded.emit(self.game)

View File

@ -33,7 +33,7 @@ class DepartingConvoyInfo(QGroupBox):
if unit_type.dcs_id in VEHICLES_ICONS.keys():
icon.setPixmap(VEHICLES_ICONS[unit_type.dcs_id])
else:
icon.setText("<b>" + unit_type.id[:8] + "</b>")
icon.setText("<b>" + unit_type.name + "</b>")
icon.setProperty("style", "icon-armor")
unit_layout.addWidget(icon, idx, 0)
unit_layout.addWidget(

View File

@ -284,7 +284,7 @@ class NewUnitTransferDialog(QDialog):
continue
logging.info(
f"Transferring {count} {unit_type.id} from {self.origin} to "
f"Transferring {count} {unit_type} from {self.origin} to "
f"{destination}"
)
transfers[unit_type] = count

View File

@ -118,18 +118,10 @@ class QBaseMenu2(QDialog):
@property
def cheat_capturable(self) -> bool:
if not self.game_model.game.settings.enable_base_capture_cheat:
return False
if self.cp.captured:
return False
for connected in self.cp.connected_points:
if connected.captured:
return True
return False
return self.game_model.game.settings.enable_base_capture_cheat
def cheat_capture(self) -> None:
self.cp.capture(self.game_model.game, for_player=True)
self.cp.capture(self.game_model.game, for_player=not self.cp.captured)
# Reinitialized ground planners and the like. The ATO needs to be reset because
# missions planned against the flipped base are no longer valid.
self.game_model.game.reset_ato()

View File

@ -18,7 +18,7 @@ from PySide2.QtWidgets import (
from dcs import Point
from dcs import vehicles
from game import Game, db
from game import Game
from game.data.building_data import FORTIFICATION_BUILDINGS
from game.db import REWARDS
from game.dcs.groundunittype import GroundUnitType
@ -258,20 +258,16 @@ class QGroundObjectMenu(QDialog):
self.game.budget = self.game.budget + self.total_value
self.ground_object.groups = []
self.do_refresh_layout()
GameUpdateSignal.get_instance().updateBudget(self.game)
GameUpdateSignal.get_instance().updateGame(self.game)
def buy_group(self):
self.subwindow = QBuyGroupForGroundObjectDialog(
self, self.ground_object, self.cp, self.game, self.total_value
)
self.subwindow.changed.connect(self.do_refresh_layout)
self.subwindow.show()
class QBuyGroupForGroundObjectDialog(QDialog):
changed = QtCore.Signal()
def __init__(
self,
parent,
@ -434,10 +430,7 @@ class QBuyGroupForGroundObjectDialog(QDialog):
)
self.ground_object.groups = [group]
GameUpdateSignal.get_instance().updateBudget(self.game)
self.changed.emit()
self.close()
GameUpdateSignal.get_instance().updateGame(self.game)
def buySam(self):
sam_generator = self.samCombo.itemData(self.samCombo.currentIndex())
@ -453,10 +446,7 @@ class QBuyGroupForGroundObjectDialog(QDialog):
generator.generate()
self.ground_object.groups = list(generator.groups)
GameUpdateSignal.get_instance().updateBudget(self.game)
self.changed.emit()
self.close()
GameUpdateSignal.get_instance().updateGame(self.game)
def buy_ewr(self):
ewr_generator = self.ewr_selector.itemData(self.ewr_selector.currentIndex())
@ -471,10 +461,7 @@ class QBuyGroupForGroundObjectDialog(QDialog):
generator.generate()
self.ground_object.groups = [generator.vg]
GameUpdateSignal.get_instance().updateBudget(self.game)
self.changed.emit()
self.close()
GameUpdateSignal.get_instance().updateGame(self.game)
def error_money(self):
msg = QMessageBox()

View File

@ -1,4 +1,5 @@
import itertools
from typing import Optional
from PySide2.QtWidgets import (
QCheckBox,
@ -63,10 +64,11 @@ class IntelTableLayout(QGridLayout):
0,
)
def add_row(self, text: str, count: int) -> None:
def add_row(self, text: str, count: Optional[int] = None) -> None:
row = next(self.row)
self.addWidget(QLabel(text), row, 0)
self.addWidget(QLabel(str(count)), row, 1)
if count is not None: # optional count for blank rows
self.addWidget(QLabel(str(count)), row, 1)
class AircraftIntelLayout(IntelTableLayout):
@ -80,14 +82,16 @@ class AircraftIntelLayout(IntelTableLayout):
if not base.total_aircraft:
continue
self.add_header(control_point.name)
for airframe, count in base.aircraft.items():
self.add_header(f"{control_point.name} ({base.total_aircraft})")
for airframe in sorted(base.aircraft, key=lambda k: k.name):
count = base.aircraft[airframe]
if not count:
continue
self.add_row(airframe.name, count)
self.add_row(f" {airframe.name}", count)
self.add_row("")
self.add_spacer()
self.add_row("<b>Total</b>", total)
self.add_spacer()
class AircraftIntelTab(ScrollingFrame):
@ -107,14 +111,16 @@ class ArmyIntelLayout(IntelTableLayout):
if not base.total_armor:
continue
self.add_header(control_point.name)
for vehicle, count in base.armor.items():
self.add_header(f"{control_point.name} ({base.total_armor})")
for vehicle in sorted(base.armor, key=lambda k: k.name):
count = base.armor[vehicle]
if not count:
continue
self.add_row(vehicle.name, count)
self.add_row(f" {vehicle.name}", count)
self.add_row("")
self.add_spacer()
self.add_row("<b>Total</b>", total)
self.add_spacer()
class ArmyIntelTab(ScrollingFrame):

View File

@ -1,4 +1,3 @@
import logging
from typing import Optional, Type
from PySide2.QtCore import Qt, Signal
@ -148,6 +147,11 @@ class QFlightCreator(QDialog):
self.on_departure_changed(self.departure.currentIndex())
def reject(self) -> None:
super().reject()
# Clear the roster to return pilots to the pool.
self.roster_editor.replace(None)
def set_custom_name_text(self, text: str):
self.custom_name_text = text
@ -217,7 +221,7 @@ class QFlightCreator(QDialog):
# noinspection PyUnresolvedReferences
self.created.emit(flight)
self.close()
self.accept()
def on_aircraft_changed(self, index: int) -> None:
new_aircraft = self.aircraft_selector.itemData(index)
@ -260,7 +264,12 @@ class QFlightCreator(QDialog):
)
def update_max_size(self, available: int) -> None:
self.flight_size_spinner.setMaximum(min(available, 4))
aircraft = self.aircraft_selector.currentData()
if aircraft is None:
self.flight_size_spinner.setMaximum(0)
return
self.flight_size_spinner.setMaximum(min(available, aircraft.max_group_size))
if self.flight_size_spinner.maximum() >= 2:
if self.flight_size_spinner.value() < 2:
self.flight_size_spinner.setValue(2)
self.flight_size_spinner.setValue(2)

View File

@ -11,7 +11,8 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape
from game import db
from game.settings import Settings
from game.theater.start_generator import GameGenerator, GeneratorSettings
from game.theater.start_generator import GameGenerator, GeneratorSettings, ModSettings
from game.factions.faction import Faction
from qt_ui.widgets.QLiberationCalendar import QLiberationCalendar
from qt_ui.widgets.spinsliders import TenthsSpinSlider, TimeInputs, CurrencySpinner
from qt_ui.windows.newgame.QCampaignList import (
@ -102,15 +103,25 @@ class NewGameWizard(QtWidgets.QWizard):
no_player_navy=self.field("no_player_navy"),
no_enemy_navy=self.field("no_enemy_navy"),
)
mod_settings = ModSettings(
a4_skyhawk=self.field("a4_skyhawk"),
f22_raptor=self.field("f22_raptor"),
hercules=self.field("hercules"),
jas39_gripen=self.field("jas39_gripen"),
su57_felon=self.field("su57_felon"),
frenchpack=self.field("frenchpack"),
high_digit_sams=self.field("high_digit_sams"),
)
blue_faction = [c for c in db.FACTIONS][self.field("blueFaction")]
red_faction = [c for c in db.FACTIONS][self.field("redFaction")]
blue_faction = self.faction_selection_page.selected_blue_faction
red_faction = self.faction_selection_page.selected_red_faction
generator = GameGenerator(
blue_faction,
red_faction,
campaign.load_theater(),
settings,
generator_settings,
mod_settings,
)
self.generatedGame = generator.generate()
@ -196,14 +207,6 @@ class FactionSelection(QtWidgets.QWizardPage):
self.factionsGroupLayout.addLayout(self.redGroupLayout)
self.factionsGroup.setLayout(self.factionsGroupLayout)
# Create required mod layout
self.requiredModsGroup = QtWidgets.QGroupBox("Required Mods")
self.requiredModsGroupLayout = QtWidgets.QHBoxLayout()
self.requiredMods = QtWidgets.QLabel("<ul><li>None</li></ul>")
self.requiredMods.setOpenExternalLinks(True)
self.requiredModsGroupLayout.addWidget(self.requiredMods)
self.requiredModsGroup.setLayout(self.requiredModsGroupLayout)
# Docs Link
docsText = QtWidgets.QLabel(
'<a href="https://github.com/dcs-liberation/dcs_liberation/wiki/Custom-Factions"><span style="color:#FFFFFF;">How to create your own faction</span></a>'
@ -218,7 +221,6 @@ class FactionSelection(QtWidgets.QWizardPage):
# Build layout
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.factionsGroup)
layout.addWidget(self.requiredModsGroup)
layout.addWidget(docsText)
self.setLayout(layout)
self.updateUnitRecap()
@ -257,42 +259,13 @@ class FactionSelection(QtWidgets.QWizardPage):
self.blueFactionDescription.setText(blue_faction_txt)
self.redFactionDescription.setText(red_faction_txt)
# Compute mod requirements txt
self.requiredMods.setText("<ul>")
has_mod = False
if len(red_faction.requirements.keys()) > 0:
has_mod = True
for mod in red_faction.requirements.keys():
self.requiredMods.setText(
self.requiredMods.text()
+ "\n<li>"
+ mod
+ ': <a href="'
+ red_faction.requirements[mod]
+ '">'
+ red_faction.requirements[mod]
+ "</a></li>"
)
@property
def selected_blue_faction(self) -> Faction:
return db.FACTIONS[self.blueFactionSelect.currentText()]
if len(blue_faction.requirements.keys()) > 0:
has_mod = True
for mod in blue_faction.requirements.keys():
if mod not in red_faction.requirements.keys():
self.requiredMods.setText(
self.requiredMods.text()
+ "\n<li>"
+ mod
+ ': <a href="'
+ blue_faction.requirements[mod]
+ '">'
+ blue_faction.requirements[mod]
+ "</a></li>"
)
if has_mod:
self.requiredMods.setText(self.requiredMods.text() + "</ul>\n\n")
else:
self.requiredMods.setText(self.requiredMods.text() + "<li>None</li></ul>\n")
@property
def selected_red_faction(self) -> Faction:
return db.FACTIONS[self.redFactionSelect.currentText()]
class TheaterConfiguration(QtWidgets.QWizardPage):
@ -561,8 +534,48 @@ class GeneratorOptions(QtWidgets.QWizardPage):
generatorLayout.addLayout(desired_player_mission_duration, 6, 0)
generatorSettingsGroup.setLayout(generatorLayout)
modSettingsGroup = QtWidgets.QGroupBox("Mod Settings")
a4_skyhawk = QtWidgets.QCheckBox()
self.registerField("a4_skyhawk", a4_skyhawk)
hercules = QtWidgets.QCheckBox()
self.registerField("hercules", hercules)
f22_raptor = QtWidgets.QCheckBox()
self.registerField("f22_raptor", f22_raptor)
jas39_gripen = QtWidgets.QCheckBox()
self.registerField("jas39_gripen", jas39_gripen)
su57_felon = QtWidgets.QCheckBox()
self.registerField("su57_felon", su57_felon)
frenchpack = QtWidgets.QCheckBox()
self.registerField("frenchpack", frenchpack)
high_digit_sams = QtWidgets.QCheckBox()
self.registerField("high_digit_sams", high_digit_sams)
modHelpText = QtWidgets.QLabel(
"<p>Select the mods you have installed. If your chosen factions support them, you'll be able to use these mods in your campaign.</p>"
)
modHelpText.setAlignment(Qt.AlignCenter)
modLayout = QtWidgets.QGridLayout()
modLayout.addWidget(QtWidgets.QLabel("A-4E Skyhawk"), 1, 0)
modLayout.addWidget(a4_skyhawk, 1, 1)
modLayout.addWidget(QtWidgets.QLabel("F-22A Raptor"), 2, 0)
modLayout.addWidget(f22_raptor, 2, 1)
modLayout.addWidget(QtWidgets.QLabel("C-130J-30 Super Hercules"), 3, 0)
modLayout.addWidget(hercules, 3, 1)
modLayout.addWidget(QtWidgets.QLabel("JAS 39 Gripen"), 4, 0)
modLayout.addWidget(jas39_gripen, 4, 1)
modLayout.addWidget(QtWidgets.QLabel("Su-57 Felon"), 5, 0)
modLayout.addWidget(su57_felon, 5, 1)
modLayout.addWidget(QtWidgets.QLabel("Frenchpack"), 6, 0)
modLayout.addWidget(frenchpack, 6, 1)
modLayout.addWidget(QtWidgets.QLabel("High Digit SAMs"), 7, 0)
modLayout.addWidget(high_digit_sams, 7, 1)
modSettingsGroup.setLayout(modLayout)
mlayout = QVBoxLayout()
mlayout.addWidget(generatorSettingsGroup)
mlayout.addWidget(modSettingsGroup)
mlayout.addWidget(modHelpText)
self.setLayout(mlayout)

View File

@ -179,6 +179,116 @@ class HqAutomationSettingsBox(QGroupBox):
self.game.settings.auto_ato_player_missions_asap = value
class PilotSettingsBox(QGroupBox):
def __init__(self, game: Game) -> None:
super().__init__("Pilots and Squadrons")
self.game = game
layout = QGridLayout()
self.setLayout(layout)
self.ai_pilot_levelling = QCheckBox()
self.ai_pilot_levelling.setChecked(self.game.settings.ai_pilot_levelling)
self.ai_pilot_levelling.toggled.connect(self.set_ai_pilot_leveling)
ai_pilot_levelling_info = (
"Set whether or not AI pilots will level up after completing a number of"
" sorties. Since pilot level affects the AI skill, you may wish to disable"
" this, lest you face an Ace!"
)
self.ai_pilot_levelling.setToolTip(ai_pilot_levelling_info)
ai_pilot_levelling_label = QLabel("Allow AI pilot levelling")
ai_pilot_levelling_label.setToolTip(ai_pilot_levelling_info)
layout.addWidget(ai_pilot_levelling_label, 0, 0)
layout.addWidget(self.ai_pilot_levelling, 0, 1, Qt.AlignRight)
enable_squadron_pilot_limits_info = (
"If set, squadrons will be limited to a maximum number of pilots and dead "
"pilots will replenish at a fixed rate, each defined with the settings"
"below. Auto-purchase may buy aircraft for which there are no pilots"
"available, so this feature is still a work-in-progress."
)
enable_squadron_pilot_limits_label = QLabel(
"Enable per-squadron pilot limtits (WIP)"
)
enable_squadron_pilot_limits_label.setToolTip(enable_squadron_pilot_limits_info)
enable_squadron_pilot_limits = QCheckBox()
enable_squadron_pilot_limits.setToolTip(enable_squadron_pilot_limits_info)
enable_squadron_pilot_limits.setChecked(
self.game.settings.enable_squadron_pilot_limits
)
enable_squadron_pilot_limits.toggled.connect(
self.set_enable_squadron_pilot_limits
)
layout.addWidget(enable_squadron_pilot_limits_label, 1, 0)
layout.addWidget(enable_squadron_pilot_limits, 1, 1, Qt.AlignRight)
self.pilot_limit = QSpinBox()
self.pilot_limit.setMinimum(12)
self.pilot_limit.setMaximum(72)
self.pilot_limit.setValue(self.game.settings.squadron_pilot_limit)
self.pilot_limit.setEnabled(self.game.settings.enable_squadron_pilot_limits)
self.pilot_limit.valueChanged.connect(self.set_squadron_pilot_limit)
pilot_limit_info = (
"Sets the maximum number of pilots a squadron may have active. "
"Changing this value will not have an immediate effect, but will alter "
"replenishment for future turns."
)
self.pilot_limit.setToolTip(pilot_limit_info)
pilot_limit_label = QLabel("Maximum number of pilots per squadron")
pilot_limit_label.setToolTip(pilot_limit_info)
layout.addWidget(pilot_limit_label, 2, 0)
layout.addWidget(self.pilot_limit, 2, 1, Qt.AlignRight)
self.squadron_replenishment_rate = QSpinBox()
self.squadron_replenishment_rate.setMinimum(1)
self.squadron_replenishment_rate.setMaximum(20)
self.squadron_replenishment_rate.setValue(
self.game.settings.squadron_replenishment_rate
)
self.squadron_replenishment_rate.setEnabled(
self.game.settings.enable_squadron_pilot_limits
)
self.squadron_replenishment_rate.valueChanged.connect(
self.set_squadron_replenishment_rate
)
squadron_replenishment_rate_info = (
"Sets the maximum number of pilots that will be recruited to each squadron "
"at the end of each turn. Squadrons will not recruit new pilots beyond the "
"pilot limit, but each squadron with room for more pilots will recruit "
"this many pilots each turn up to the limit."
)
self.squadron_replenishment_rate.setToolTip(squadron_replenishment_rate_info)
squadron_replenishment_rate_label = QLabel("Squadron pilot replenishment rate")
squadron_replenishment_rate_label.setToolTip(squadron_replenishment_rate_info)
layout.addWidget(squadron_replenishment_rate_label, 3, 0)
layout.addWidget(self.squadron_replenishment_rate, 3, 1, Qt.AlignRight)
def set_enable_squadron_pilot_limits(self, checked: bool) -> None:
self.game.settings.enable_squadron_pilot_limits = checked
self.pilot_limit.setEnabled(checked)
self.squadron_replenishment_rate.setEnabled(checked)
def set_squadron_pilot_limit(self, value: int) -> None:
self.game.settings.squadron_pilot_limit = value
def set_squadron_replenishment_rate(self, value: int) -> None:
self.game.settings.squadron_replenishment_rate = value
def set_ai_pilot_leveling(self, checked: bool) -> None:
self.game.settings.ai_pilot_levelling = checked
START_TYPE_TOOLTIP = "Selects the start type used for AI aircraft."
@ -516,72 +626,7 @@ class QSettingsWindow(QDialog):
general_layout.addWidget(old_tanker_label, 2, 0)
general_layout.addWidget(old_tanker, 2, 1, Qt.AlignRight)
def set_squadron_pilot_limit(value: int) -> None:
self.game.settings.squadron_pilot_limit = value
pilot_limit = QSpinBox()
pilot_limit.setMinimum(12)
pilot_limit.setMaximum(72)
pilot_limit.setValue(self.game.settings.squadron_pilot_limit)
pilot_limit.valueChanged.connect(set_squadron_pilot_limit)
pilot_limit_info = (
"Sets the maximum number of pilots a squadron may have active. "
"Changing this value will not have an immediate effect, but will alter "
"replenishment for future turns."
)
pilot_limit.setToolTip(pilot_limit_info)
pilot_limit_label = QLabel("Maximum number of pilots per squadron")
pilot_limit_label.setToolTip(pilot_limit_info)
general_layout.addWidget(pilot_limit_label, 3, 0)
general_layout.addWidget(pilot_limit, 3, 1, Qt.AlignRight)
def set_squadron_replenishment_rate(value: int) -> None:
self.game.settings.squadron_replenishment_rate = value
squadron_replenishment_rate = QSpinBox()
squadron_replenishment_rate.setMinimum(1)
squadron_replenishment_rate.setMaximum(20)
squadron_replenishment_rate.setValue(
self.game.settings.squadron_replenishment_rate
)
squadron_replenishment_rate.valueChanged.connect(
set_squadron_replenishment_rate
)
squadron_replenishment_rate_info = (
"Sets the maximum number of pilots that will be recruited to each squadron "
"at the end of each turn. Squadrons will not recruit new pilots beyond the "
"pilot limit, but each squadron with room for more pilots will recruit "
"this many pilots each turn up to the limit."
)
squadron_replenishment_rate.setToolTip(squadron_replenishment_rate_info)
squadron_replenishment_rate_label = QLabel("Squadron pilot replenishment rate")
squadron_replenishment_rate_label.setToolTip(squadron_replenishment_rate_info)
general_layout.addWidget(squadron_replenishment_rate_label, 4, 0)
general_layout.addWidget(squadron_replenishment_rate, 4, 1, Qt.AlignRight)
ai_pilot_levelling = QCheckBox()
ai_pilot_levelling.setChecked(self.game.settings.ai_pilot_levelling)
ai_pilot_levelling.toggled.connect(self.applySettings)
ai_pilot_levelling_info = (
"Set whether or not AI pilots will level up after completing a number of"
" sorties. Since pilot level affects the AI skill, you may wish to disable"
" this, lest you face an Ace!"
)
ai_pilot_levelling.setToolTip(ai_pilot_levelling_info)
ai_pilot_levelling_label = QLabel("Allow AI pilot levelling")
ai_pilot_levelling_label.setToolTip(ai_pilot_levelling_info)
general_layout.addWidget(ai_pilot_levelling_label, 5, 0)
general_layout.addWidget(ai_pilot_levelling, 5, 1, Qt.AlignRight)
campaign_layout.addWidget(PilotSettingsBox(self.game))
campaign_layout.addWidget(HqAutomationSettingsBox(self.game))
def initGeneratorLayout(self):

View File

@ -19,6 +19,7 @@ pathspec==0.8.1
pefile==2019.4.18
Pillow==8.2.0
pre-commit==2.10.1
-e git://github.com/pydcs/dcs@7dea4f516d943c1f48454a46043b5f38d42a35f0#egg=pydcs
pyinstaller==4.3
pyinstaller-hooks-contrib==2021.1
pyparsing==2.4.7

View File

@ -5,7 +5,7 @@
"recommended_player_faction": "Bluefor Modern",
"recommended_enemy_faction": "Iran 2015",
"description": "<p>Following the Battle of Abu Dhabi, Iran's invasion of the UAE has been halted approximately 20 miles Northeast of Liwa Airbase by coalition forces.</p><p>After weeks of stalemate, coalition forces have consolidated their position and are ready to launch their counterattack to push Iranian forces off the peninsula.</p>",
"version": "6.0",
"version": "7.0",
"miz": "Battle_for_the_UAE_v3.0.2.miz",
"performance": 2
}

View File

@ -5,7 +5,7 @@
"recommended_player_faction": "Bluefor Modern",
"recommended_enemy_faction": "Russia 2010",
"description": "<p>This is a complete map of every airbase in the Caucasus Region, all bases are fully defended by Air, Land and/or Sea. The player starts by invading southern Georgia and works their way through Russia. The Strike and SAM targets are limited for performance reasons. If this Scenario is too taxing for your computer you may use the Multi-Part Scenarios. They are copied from this Campaign and are catered toward less powerful machines.</p>",
"version": "6.0",
"version": "7.0",
"miz": "Caucasus_Multi_Full.miz",
"performance": 3
}

View File

@ -5,7 +5,7 @@
"recommended_player_faction": "Bluefor Modern",
"recommended_enemy_faction": "Georgia 2008",
"description": "<p>This is Part 1 of the Caucasus Multi-Part Campaign. This is the invasion of Georgia starting from the southwest (Batumi) and ending in both Gudauta and Tiblisi. This is a straightforward campaign that is smaller and simpler than most. However, it acts great as either a stand alone campaign for beginners, or as a lead into the Caucasus Multi-Part Russia campaign.</p>",
"version": "6.0",
"version": "7.0",
"miz": "Caucasus_Multi_Georgia.miz",
"performance": 1
}

View File

@ -5,7 +5,7 @@
"recommended_player_faction": "Bluefor Modern",
"recommended_enemy_faction": "Russia 2010",
"description": "<p>This is part 2 of the Caucasus Multi-part campaign. After completing Multi-Part Georgia, play this campaign to invade Russia and finish the theater. As this is now Russia the recommended enemy faction has changed. To simulate still owning Georgia the player income has been supplemented through an increased number of blue strike targets at the starting bases. This is a more difficult scenario with a higher concentration of Redfor SAMs and Strike targets than usual.</p>",
"version": "6.0",
"version": "7.0",
"miz": "Caucasus_Multi_Russia.miz",
"performance": 2
}

View File

@ -6,6 +6,6 @@
"recommended_enemy_faction": "Syria 1982",
"description": "<p> 1100HRS, 06 June 1982: H-hour for Operation Peace for Galilee. </p><p>Objective: Push North towards Beirut and into the Bekaa Valley, eliminating or displacing any PLO and Syrian resistance. Airbases and their surrounding infrastructure in Syria are not the main objective but are still viable strategic targets.</p> <p>Background: Years of PLO encroachment into the UN neutral zone and their resulting terror attacks against Israelis have pushed tension along the border to a breaking point. On June 3, the attempted assassination of Israeli Ambassador, Shlomo Argov by gunmen with ties to the PLO have finally pushed the Israelis to action.</p><p>Recommended Starting Budget:</p><p>$1500m for recommended factions, $$2000m for modern scenarios</p><p>Income Multiplier:</p><p>Blue: 1.0x</p><p>Red: 0.7x-1.0x</p>",
"miz": "First_Lebanon_War_v3.0.2.miz",
"version": "6.0",
"version": "7.0",
"performance": 2
}

View File

@ -0,0 +1,11 @@
{
"name": "Syria - Operation Atilla",
"theater": "Syria",
"authors": "Malakhit",
"recommended_player_faction": "Turkey 2005",
"recommended_enemy_faction": "Greece 2005",
"description": "<p>This is based on the Turkish invasion of Cyprus, and the Greek defense of the island. You must make sure to keep your beachhead at all costs, otherwise reclaiming it will be tricky. It is recommended to reduce the per-turn income rate for both factions in this scenario due to the large oil depots both sides have - setting it to around 15-20% is probably reasonable.</p>",
"version": "6.0",
"miz": "Operation_Atilla.miz",
"performance": 2
}

Binary file not shown.

View File

@ -5,7 +5,7 @@
"recommended_player_faction": "Bluefor Modern",
"recommended_enemy_faction": "Syria 2011",
"description": "<p>In a scenario reminescent of the First Lebanon War, hostile Syrian-backed forces have flooded into the Bekaa Valley.</p><p>The objective of this operation is twofold: drive the enemy out of the Bekaa Valley and push past the Golan Heights into Syrian territory to capture Tiyas Airbase.</p>",
"version": "6.0",
"version": "7.0",
"miz": "Operation_Mole_Cricket_2010_v3.0.2.miz",
"performance": 2
}

View File

@ -3,7 +3,7 @@
"theater": "Persian Gulf",
"authors": "Doc_of_Mur",
"description": "<p>Small beginner friendly map</p><p><strong>Note:</strong> This scenario is based around Iran invading the UAE and you are trying to take it back. It is small and beginner friendly.</p>",
"version": "6.0",
"version": "7.0",
"recommended_player_faction": "USA 2005",
"recommended_enemy_faction": "Iran 2015",
"miz": "Road_to_Dubai.miz",

View File

@ -0,0 +1,11 @@
{
"name": "Syria - Russian Intervention 2015",
"theater": "Syria",
"authors": "Malakhit",
"recommended_player_faction": "Russia 2010",
"recommended_enemy_faction": "Insurgents (Hard)",
"description": "<p>This short campaign is loosely based on the 2015 Russian military intervention in Syria. It should be perfect for COIN operations, especially with the Hind.</p>",
"version": "6.0",
"miz": "Russian_Intervention_2015.miz",
"performance": 1
}

Binary file not shown.

View File

@ -0,0 +1,11 @@
{
"name": "Caucasus - Around The Mountain",
"theater": "Caucasus",
"authors": "Dillie",
"recommended_player_faction": "Russia 2010",
"recommended_enemy_faction": "USA 1990",
"description": "<p>Scenario from Russia to Georgia in two Frontlines.</p>",
"version": "7.0",
"miz": "around_the_mountain.miz",
"performance": 2
}

Binary file not shown.

View File

@ -7,5 +7,5 @@
"description": "<p>You have managed to establish a foothold at Khasab. Continue pushing south.</p>",
"miz": "battle_of_abu_dhabi.miz",
"performance": 2,
"version": "6.0"
"version": "7.0"
}

View File

@ -5,5 +5,5 @@
"description": "<p>A medium sized theater with bases along the coast of the Black Sea.</p>",
"miz": "black_sea.miz",
"performance": 2,
"version": "6.0"
"version": "7.0"
}

View File

@ -7,5 +7,5 @@
"description": "<p>This is a light scenario on the Normandy map.</p><p>August 1944, allied forces are pushing from Caen/Carpiquet to the cities of Lisieux and Evreux.<p>Lisieux is an important logistic hub for the Werhmacht, and Evreux airbase is hosting most of the Luftwaffe forces in the region.</p>",
"miz": "caen_to_evreux.miz",
"performance": 1,
"version": "6.0"
"version": "7.0"
}

View File

@ -5,7 +5,7 @@
"recommended_player_faction": "Bluefor Modern",
"recommended_enemy_faction": "Redfor (China) 2010",
"description": "<p>This is an asymmetrical Red Flag Exercise scenario for the NTTR comprising 4 control points. You start off in control of the two Tonopah airports, and will push south to capture Groom Lake and Nellis AFBs. Taking down Nellis AFB's IADS and striking their resource sites ASAP once Groom Lake has been captured is recommended to offset their resource advantage.</p>",
"version": "6.0",
"version": "7.0",
"miz": "exercise_vegas_nerve.miz",
"performance": 0
}

View File

@ -7,5 +7,5 @@
"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 friendly.</p>",
"miz": "golan_heights_lite.miz",
"performance": 1,
"version": "6.0"
"version": "7.0"
}

View File

@ -0,0 +1,11 @@
{
"name": "Caucasus - Mozdok to Maykop",
"theater": "Caucasus",
"authors": "Khopa",
"recommended_player_faction": "Russia 2010",
"recommended_enemy_faction": "USA 1990",
"description": "<p>A small theater in Russia, progress from Mozdok to Maykop.</p><p>This scenario is pretty simple, and is ideal if you want to run a short campaign to try liberation. If your PC is not powerful, this is also the less performance heavy scenario.</p>",
"miz": "mozdok_to_maykop.miz",
"performance": 0,
"version": "7.0"
}

Binary file not shown.

View File

@ -0,0 +1,11 @@
{
"name": "Nevada - Limited Air",
"theater": "Nevada",
"authors": "Doc_of_Mur",
"recommended_player_faction": "USA 2005",
"recommended_enemy_faction": "Russia 1975",
"description": "<p>This campaign is designed to be beginner friendly in that the number of aircraft slot have been limited. Other than the starting point and the 'boss' base the max slots in each of the airbases have a mere 3-5 slots.</p><p>This should prevent the airpower rush escperienced in most of the other larger campaign.</p>",
"version": "7.0",
"miz": "nevada_limited_air.miz",
"performance": 1
}

Binary file not shown.

View File

@ -2,10 +2,10 @@
"name": "Caucasus - Northern Russia",
"theater": "Caucasus",
"authors": "Plob",
"recommended_player_faction": "USA 2005",
"recommended_enemy_faction": "Russia 1990",
"description": "<p>A medium campaign through the north eastern part of the Caucasus map.</p><p>Russia has invaded Georgia through the eastern mountains. Mount a counter offense and push them back!",
"recommended_player_faction": "Bluefor Modern",
"recommended_enemy_faction": "Russia 1975",
"description": "<p>A medium campaign through the north eastern part of the Caucasus map. Play vs 1975 Russia for an low-medium difficulty campaign, play vs russia 1990 for a hard difficulty campaign. </p><p>Russia has invaded Georgia through the eastern mountains. Mount a counter offense and push them back!",
"miz": "northern_russia.miz",
"performance": 2,
"version": "6.0"
"version": "7.0"
}

View File

@ -2,8 +2,10 @@
"name": "Syria - Operation Allied Sword",
"theater": "Syria",
"authors": "Fuzzle",
"recommended_player_faction": "Israel-USN 2005 (Allied Sword)",
"recommended_enemy_faction": "Syria-Lebanon 2005 (Allied Sword)",
"description": "<p>In this fictional scenario, a US/Israeli coalition must push north from the Israeli border, through Syria and Lebanon to Aleppo.</p><p><strong>Backstory:</strong> A Syrian-Lebanese joint force (with Russian materiel support) has attacked Israel, attmepting to cross the northern border. With the arrival of a US carrier group, Israel prepares its counterattack. The US Navy will handle the Beirut region's coastal arena, while the IAF will push through Damascus and the inland mountain ranges.</p>",
"version": "6.0",
"version": "7.0",
"miz": "operation_allied_sword.miz",
"performance": 2
}

View File

@ -0,0 +1,11 @@
{
"name": "Syria - Operation Blackball",
"theater": "Syria",
"authors": "Fuzzle",
"recommended_player_faction": "US Navy 2005",
"recommended_enemy_faction": "Russia 2010",
"description": "<p><strong>Warning: This campaign will not work if the attacking faction does not have a carrier.</strong></p><p>A lightweight, fictional showcase of Cyprus for the Syria terrain. A US Navy force must deploy from a FOB and carrier group to push from the north-east down through the island.</p><p><strong>Backstory:</strong> The world is at war. With the help of her eastern allies, Russia has taken the Suez Canal and deployed a large naval force to the Mediterranean, trapping a US carrier group near the Turkish-Syrian border. Now, they must break out by taking Cyprus back.</p>",
"version": "7.0",
"miz": "operation_blackball.miz",
"performance": 1
}

Binary file not shown.

View File

@ -0,0 +1,11 @@
{
"name": "The Channel - Operation Dynamo",
"theater": "The Channel",
"authors": "Khopa",
"recommended_player_faction": "Allies 1940",
"recommended_enemy_faction": "Germany 1940",
"description": "<p>The Battle of Dunkirk (French: Bataille de Dunkerque) was fought around the French port of Dunkirk (Dunkerque) during the Second World War, between the Allies and Nazi Germany. As the Allies were losing the Battle of France on the Western Front, the Battle of Dunkirk was the defence and evacuation of British and other Allied forces to Britain from 26 May to 4 June 1940..</p>",
"version": 7.0,
"miz": "operation_dynamo.miz",
"performance": 1
}

Binary file not shown.

View File

@ -5,7 +5,7 @@
"recommended_player_faction": "Bluefor Modern",
"recommended_enemy_faction": "Turkey 2005",
"description": "<p>This is a semi-fictional what-if scenario for Operation Peace Spring, during which Turkish forces that crossed into Syria on an offensive against Kurdish militias were emboldened by early successes to continue pushing further southward. Attempts to broker a ceasefire have failed. Members of Operation Inherent Resolve have gathered at Ramat David Airbase in Israel to launch a counter-offensive. Campaign inversion is available if you wish to play as Turkey.</p>",
"version": "6.0",
"version": "7.0",
"miz": "operation_peace_spring.miz",
"performance": 1
}

View File

@ -5,7 +5,7 @@
"recommended_player_faction": "USA 2005",
"recommended_enemy_faction": "Russia 1990",
"description": "<p>United Nations Observer Mission in Georgia (UNOMIG) observers stationed in Georgia to monitor the ceasefire between Georgia and Abkhazia have been cut off from friendly forces by Russian troops backing the separatist state. The UNOMIG HQ at Sukhumi has been taken, and a small contingent of observers and troops at the Zugdidi Sector HQ will have to make a run for the coast, supported by offshore US naval aircraft. The contingent is aware that their best shot at survival is to swiftly retake Sukhumi before Russian forces have a chance to dig in, so that friendly ground forces can land and reinforce them.<br/></p><p><strong>Note:</strong> Ground unit purchase will not be available past Turn 0 until Sukhumi is retaken, so it is imperative you reach Sukhumi with at least one surviving ground unit to capture it. The player can either play the first leg of the scenario as an evacuation with a couple of light vehicles (e.g. Humvees) set on breakthrough (modifying waypoints in the mission editor so they are not charging head-on into enemy ground forces is suggested), or purchase heavier ground units if they wish to experience a more traditional ground war.</p>",
"version": "6.0",
"version": "7.0",
"miz": "operation_vectrons_claw.miz",
"performance": 1
}

View File

@ -1,11 +0,0 @@
{
"name": "Caucasus - Russia Small",
"theater": "Caucasus",
"authors": "Khopa",
"recommended_player_faction": "Russia 2010",
"recommended_enemy_faction": "USA 1990",
"description": "<p>A small theater in Russia, progress from Mozdok to Maykop.</p><p>This scenario is pretty simple, it is ideal if you want to run a short campaign. If your PC is not powerful, this is also the less performance heavy scenario.</p>",
"miz": "russia_small.miz",
"performance": 0,
"version": "6.0"
}

Binary file not shown.

View File

@ -0,0 +1,11 @@
{
"name": "Persian Gulf - Scenic Route",
"theater": "Persian Gulf",
"authors": "Fuzzle",
"recommended_player_faction": "US Navy 2005",
"recommended_enemy_faction": "Iran 2015",
"description": "<p>A lightweight naval campaign involving a US Navy carrier group pushing across the coast of Iran. <strong>Note that the ground units purchased on turn zero must sustain you until you've taken the first hostile FOB. The starting point does not have a factory to simulate a Marine Expeditionary Force deploying from the carrier group.</strong></p><p><strong>Backstory:</strong> Iran has declared war on all US forces in the Gulf, resulting in all local allies withdrawing their support for American troops. A lone carrier group must pacify the southern coast of Iran and hold out until backup can arrive, lest the US and her interests be ejected from the region permanently.</p>",
"version": "7.0",
"miz": "scenic_route.miz",
"performance": 1
}

Binary file not shown.

View File

@ -4,8 +4,8 @@
"authors": "Plob",
"recommended_player_faction": "Bluefor Modern",
"recommended_enemy_faction": "Syria 2011",
"description": "<p>Syria Full map, designed for groups of 4-12 players.</p>",
"description": "<p>A long campaign across the syria map, strap in.</p></p>Each turn after planning missions, enable culling to ensure correct culling behaviour.</p>",
"miz": "syria_full_map.miz",
"performance": 3,
"version": "6.0"
"version": "7.0"
}

View File

@ -1,164 +0,0 @@
local unitPayloads = {
["name"] = "MB-339PAN",
["payloads"] = {
[1] = {
["name"] = "CAP",
["pylons"] = {
[1] = {
["CLSID"] = "{MB339-DEFA553_R}",
["num"] = 7,
},
[2] = {
["CLSID"] = "{MB339-DEFA553_L}",
["num"] = 4,
},
[3] = {
["CLSID"] = "{FUEL-TIP-ELLITTIC-L}",
["num"] = 1,
},
[4] = {
["CLSID"] = "{FUEL-TIP-ELLITTIC-R}",
["num"] = 10,
},
},
["tasks"] = {
},
},
[2] = {
["name"] = "CAS",
["pylons"] = {
[1] = {
["CLSID"] = "{FUEL-TIP-TANK-500-R}",
["num"] = 10,
},
[2] = {
["CLSID"] = "{FUEL-TIP-TANK-500-L}",
["num"] = 1,
},
[3] = {
["CLSID"] = "{LAU-10}",
["num"] = 3,
},
[4] = {
["CLSID"] = "{LAU-10}",
["num"] = 8,
},
[5] = {
["CLSID"] = "{MB339-DEFA553_L}",
["num"] = 4,
},
[6] = {
["CLSID"] = "{MB339-DEFA553_R}",
["num"] = 7,
},
},
["tasks"] = {
},
},
[3] = {
["name"] = "ANTISHIP",
["pylons"] = {
[1] = {
["CLSID"] = "{FUEL-TIP-TANK-500-R}",
["num"] = 10,
},
[2] = {
["CLSID"] = "{FUEL-TIP-TANK-500-L}",
["num"] = 1,
},
[3] = {
["CLSID"] = "{LAU-10}",
["num"] = 3,
},
[4] = {
["CLSID"] = "{LAU-10}",
["num"] = 8,
},
[5] = {
["CLSID"] = "{MB339-DEFA553_L}",
["num"] = 4,
},
[6] = {
["CLSID"] = "{MB339-DEFA553_R}",
["num"] = 7,
},
},
["tasks"] = {
},
},
[4] = {
["name"] = "STRIKE",
["pylons"] = {
[1] = {
["CLSID"] = "{FUEL-TIP-TANK-500-R}",
["num"] = 10,
},
[2] = {
["CLSID"] = "{FUEL-TIP-TANK-500-L}",
["num"] = 1,
},
[3] = {
["CLSID"] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
["num"] = 3,
},
[4] = {
["CLSID"] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
["num"] = 8,
},
[5] = {
["CLSID"] = "{MB339-AN-M3_L}",
["num"] = 4,
},
[6] = {
["CLSID"] = "{MB339-AN-M3_R}",
["num"] = 7,
},
[7] = {
["CLSID"] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
["num"] = 9,
},
[8] = {
["CLSID"] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
["num"] = 2,
},
},
["tasks"] = {
},
},
[5] = {
["name"] = "SEAD",
["pylons"] = {
[1] = {
["CLSID"] = "{FUEL-TIP-TANK-500-R}",
["num"] = 10,
},
[2] = {
["CLSID"] = "{FUEL-TIP-TANK-500-L}",
["num"] = 1,
},
[3] = {
["CLSID"] = "{LAU-10}",
["num"] = 3,
},
[4] = {
["CLSID"] = "{LAU-10}",
["num"] = 8,
},
[5] = {
["CLSID"] = "{MB339-AN-M3_L}",
["num"] = 4,
},
[6] = {
["CLSID"] = "{MB339-AN-M3_R}",
["num"] = 7,
},
},
["tasks"] = {
},
},
},
["tasks"] = {
},
["unitType"] = "MB-339PAN",
}
return unitPayloads

View File

@ -1,49 +1,39 @@
{
"country": "USA",
"name": "USA 2005 Modded",
"authors": "Khopa",
"description": "<p>USA 2005 with the Raptor mod, with the F-22 mod by Grinelli Designs.</p>",
"country": "Israel",
"name": "Israel-USN 2005 (Allied Sword)",
"authors": "Fuzzle",
"description": "<p>A joint US Navy/Israeli modern faction for use with the Operation Allied Sword scenario.</p>",
"locales": [
"en_US"
],
"aircrafts": [
"A-10C Thunderbolt II (Suite 3)",
"A-10C Thunderbolt II (Suite 7)",
"AH-64D Apache Longbow",
"AV-8B Harrier II Night Attack",
"B-1B Lancer",
"B-52H Stratofortress",
"F-117A Nighthawk",
"F-14B Tomcat",
"F-4E Phantom II",
"F-15C Eagle",
"F-15E Strike Eagle",
"F-16CM Fighting Falcon (Block 50)",
"F-22A Raptor",
"F-14B Tomcat",
"F/A-18C Hornet (Lot 20)",
"AV-8B Harrier II Night Attack",
"AH-1W SuperCobra",
"AH-64A Apache",
"S-3B Viking",
"SH-60B Seahawk",
"UH-1H Iroquois"
],
"awacs": [
"E-2C Hawkeye",
"E-3A"
"E-2C Hawkeye"
],
"tankers": [
"KC-130",
"KC-135 Stratotanker",
"KC-135 Stratotanker MPRS",
"KC-130",
"S-3B Tanker"
],
"frontline_units": [
"LAV-25",
"M113",
"M1043 HMMWV (M2 HMG)",
"M1045 HMMWV (BGM-71 TOW)",
"M1097 Heavy HMMWV Avenger",
"M1126 Stryker ICV (M2 HMG)",
"M1134 Stryker ATGM (BGM-71 TOW)",
"M1A2 Abrams",
"M2A2 Bradley",
"M6 Linebacker"
"Merkava Mk IV",
"M163 Vulcan Air Defense System"
],
"artillery_units": [
"M109A6 Paladin",
@ -53,19 +43,18 @@
"Truck M818 6x6"
],
"infantry_units": [
"Infantry M249",
"Infantry M4",
"MANPADS Stinger",
"Mortar 2B11 120mm"
"Infantry M249",
"MANPADS Stinger"
],
"air_defenses": [
"AvengerGenerator",
"ChaparralGenerator",
"HawkGenerator",
"LinebackerGenerator",
"VulcanGenerator",
"PatriotGenerator"
],
"ewrs": [
"PatriotEwrGenerator"
"HawkEwrGenerator"
],
"aircraft_carrier": [
"Stennis"
@ -74,16 +63,20 @@
"LHA_Tarawa"
],
"destroyers": [
"USS_Arleigh_Burke_IIa"
"USS_Arleigh_Burke_IIa",
"PERRY"
],
"cruisers": [
"TICONDEROG"
],
"requirements": {
},
"carrier_names": [
"CVN-71 Theodore Roosevelt",
"CVN-72 Abraham Lincoln",
"CVN-73 George Washington",
"CVN-74 John C. Stennis"
"CVN-74 John C. Stennis",
"CVN-75 Harry S. Truman"
],
"helicopter_carrier_names": [
"LHA-1 Tarawa",
@ -96,29 +89,24 @@
"ArleighBurkeGroupGenerator",
"OliverHazardPerryGroupGenerator"
],
"requirements": {
"F-22A mod by Grinnelli Designs": "https://bestf22modever.com"
},
"has_jtac": true,
"jtac_unit": "MQ-9 Reaper",
"doctrine": "modern",
"liveries_overrides": {
"F-14B Tomcat": [
"VF-142 Ghostriders"
],
"F/A-18C Hornet (Lot 20)": [
"VFA-106",
"VFA-113",
"VFA-122",
"VFA-131",
"VFA-192",
"VFA-34",
"VFA-37",
"VFA-83",
"VFA-87",
"VFA-97",
"VMFA-122",
"VMFA-132",
"VMFA-251",
"VMFA-312",
"VMFA-314",
"VMFA-323"
"VMFA-251 high visibility"
],
"AV-8B Harrier II Night Attack": [
"VMAT-542"
],
"AH-1W SuperCobra": [
"Marines"
],
"UH-1H Iroquois": [
"US NAVY"
]
}
}
}

View File

@ -9,6 +9,8 @@
"AV-8B Harrier II Night Attack",
"B-1B Lancer",
"B-52H Stratofortress",
"C-130",
"C-130J-30 Super Hercules",
"F-117A Nighthawk",
"F-14A Tomcat (Block 135-GR Late)",
"F-14B Tomcat",

View File

@ -0,0 +1,102 @@
{
"country": "Combined Joint Task Forces Red",
"name": "Syria-Lebanon 2005 (Allied Sword)",
"authors": "Fuzzle",
"description": "<p>Syria-Lebanon alliance in a modern setting with several imported Russian assets. Designed for use with the Allied Sword scenario.</p>",
"aircrafts": [
"MiG-23ML Flogger-G",
"MiG-25RBT Foxbat-B",
"MiG-29A Fulcrum-A",
"Su-17M4 Fitter-K",
"Su-24M Fencer-D",
"Su-30 Flanker-C",
"Su-34 Fullback",
"L-39ZA Albatros",
"Tu-22M3 Backfire-C",
"Mi-24V Hind-E",
"Mi-8MTV2 Hip",
"SA 342M Gazelle",
"SA 342L Gazelle"
],
"awacs": [
"A-50"
],
"tankers": [
"IL-78M"
],
"frontline_units": [
"BMP-1",
"BMP-2",
"BTR-80",
"BRDM-2",
"MT-LB",
"T-55A",
"T-72B with Kontakt-1 ERA",
"T-90A",
"ZSU-57-2 'Sparka'"
],
"artillery_units": [
"BM-21 Grad",
"2S1 Gvozdika"
],
"logistics_units": [
"Truck Ural-375",
"LUV UAZ-469 Jeep"
],
"infantry_units": [
"Paratrooper AKS",
"Infantry AK-74 Rus",
"Paratrooper RPG-16",
"MANPADS SA-18 Igla-S \"Grouse\""
],
"air_defenses": [
"ColdWarFlakGenerator",
"SA2Generator",
"SA3Generator",
"SA6Generator",
"SA8Generator",
"SA9Generator",
"SA10Generator",
"SA11Generator",
"SA13Generator",
"SA19Generator",
"ZSU23Generator",
"ZU23Generator",
"ZU23UralGenerator",
"ZSU57Generator"
],
"ewrs": [
"BoxSpringGenerator",
"TallRackGenerator"
],
"missiles": [
"ScudGenerator"
],
"missiles_group_count": 2,
"coastal_defenses": [
"SilkwormGenerator"
],
"coastal_group_count": 4,
"aircraft_carrier": [
],
"helicopter_carrier": [
],
"helicopter_carrier_names": [
],
"destroyers": [
"REZKY",
"MOLNIYA"
],
"cruisers": [
],
"requirements": {},
"carrier_names": [
],
"coastal_group_count": 8,
"navy_generators": [
"GrishaGroupGenerator",
"MolniyaGroupGenerator",
"RussianNavyGroupGenerator",
"LaCombattanteIIGroupGenerator"
]
}

View File

@ -1,10 +1,11 @@
{
"country": "Australia",
"name": "Australia 2005",
"authors": "Khopa",
"authors": "Khopa, SpaceEnthusiast",
"description": "<p>The Australian army in 2005.</p><p>Some units might not be accurate, but were picked to represent at best this army.</p>",
"aircrafts": [
"AH-1W SuperCobra",
"C-130J-30 Super Hercules",
"F/A-18C Hornet (Lot 20)",
"SH-60B Seahawk",
"UH-1H Iroquois"
@ -47,7 +48,9 @@
"USS_Arleigh_Burke_IIa"
],
"cruisers": [],
"requirements": {},
"requirements": {
"C-130J-30 Super Hercules Mod by Anubis": "https://forums.eagle.ru/topic/252075-dcs-super-hercules-mod-by-anubis/"
},
"carrier_names": [],
"helicopter_carrier_names": [
"HMAS Canberra",

View File

@ -1,70 +0,0 @@
{
"country": "Australia",
"name": "Australia 2005 (With C-130)",
"authors": "Khopa, SpaceEnthusiast",
"description": "<p>The Australian army in 2005.</p><p>Some units might not be accurate, but were picked to represent at best this army.</p>",
"aircrafts": [
"AH-1W SuperCobra",
"C-130J-30 Super Hercules",
"F/A-18C Hornet (Lot 20)",
"SH-60B Seahawk",
"UH-1H Iroquois"
],
"awacs": [
"E-3A"
],
"tankers": [
"KC-130",
"KC-135 Stratotanker"
],
"frontline_units": [
"FV510 Warrior",
"LAV-25",
"Leopard 1A3",
"M113",
"M1A2 Abrams"
],
"artillery_units": [],
"logistics_units": [
"Truck M818 6x6"
],
"infantry_units": [
"Infantry M249",
"Infantry M4",
"MANPADS Stinger"
],
"air_defenses": [
"HawkGenerator",
"RapierGenerator"
],
"ewrs": [
"HawkEwrGenerator"
],
"aircraft_carrier": [],
"helicopter_carrier": [
"LHA_Tarawa"
],
"destroyers": [
"USS_Arleigh_Burke_IIa"
],
"cruisers": [],
"requirements": {
"C-130J-30 Super Hercules Mod by Anubis": "https://forums.eagle.ru/topic/252075-dcs-super-hercules-mod-by-anubis/"
},
"carrier_names": [],
"helicopter_carrier_names": [
"HMAS Canberra",
"HMAS Adelaide"
],
"navy_generators": [
"ArleighBurkeGroupGenerator"
],
"has_jtac": true,
"jtac_unit": "MQ-9 Reaper",
"liveries_overrides": {
"F/A-18C Hornet (Lot 20)": [
"Australian 75th Squadron",
"Australian 77th Squadron"
]
}
}

View File

@ -2,11 +2,14 @@
"country": "Combined Joint Task Forces Blue",
"name": "Bluefor Coldwar",
"authors": "Khopa",
"description": "<p>A generic bluefor coldwar faction.</p>",
"description": "<p>A generic bluefor coldwar faction. (With the A-4E-C mod)</p>",
"aircrafts": [
"A-10A Thunderbolt II",
"A-4E Skyhawk",
"AJS-37 Viggen",
"B-52H Stratofortress",
"C-130",
"C-130J-30 Super Hercules",
"F-14A Tomcat (Block 135-GR Late)",
"F-14B Tomcat",
"F-4E Phantom II",
@ -16,7 +19,6 @@
"UH-1H Iroquois"
],
"awacs": [
"C-130",
"E-2C Hawkeye",
"E-3A"
],
@ -60,7 +62,9 @@
"cruisers": [
"TICONDEROG"
],
"requirements": {},
"requirements": {
"Community A-4E": "https://heclak.github.io/community-a4e-c/"
},
"carrier_names": [
"CVN-71 Theodore Roosevelt",
"CVN-72 Abraham Lincoln",

View File

@ -1,86 +0,0 @@
{
"country": "Combined Joint Task Forces Blue",
"name": "Bluefor Coldwar (With A4)",
"authors": "Khopa",
"description": "<p>A generic bluefor coldwar faction. (With the A-4E-C mod)</p>",
"aircrafts": [
"A-10A Thunderbolt II",
"A-4E Skyhawk",
"AJS-37 Viggen",
"B-52H Stratofortress",
"F-14A Tomcat (Block 135-GR Late)",
"F-14B Tomcat",
"F-4E Phantom II",
"F-5E Tiger II",
"SA 342L Gazelle",
"SA 342M Gazelle",
"UH-1H Iroquois"
],
"awacs": [
"C-130",
"E-2C Hawkeye",
"E-3A"
],
"tankers": [
"KC-130",
"KC-135 Stratotanker"
],
"frontline_units": [
"M113",
"M48 Chaparral",
"M60A3 \"Patton\""
],
"artillery_units": [
"M109A6 Paladin"
],
"logistics_units": [
"Truck M818 6x6"
],
"infantry_units": [
"Infantry M249",
"Infantry M4"
],
"air_defenses": [
"ChaparralGenerator",
"EarlyColdWarFlakGenerator",
"HawkGenerator",
"VulcanGenerator"
],
"ewrs": [
"HawkEwrGenerator"
],
"aircraft_carrier": [
"Stennis"
],
"helicopter_carrier": [
"LHA_Tarawa"
],
"destroyers": [
"USS_Arleigh_Burke_IIa"
],
"cruisers": [
"TICONDEROG"
],
"requirements": {
"Community A-4E": "https://heclak.github.io/community-a4e-c/"
},
"carrier_names": [
"CVN-71 Theodore Roosevelt",
"CVN-72 Abraham Lincoln",
"CVN-73 George Washington",
"CVN-74 John C. Stennis"
],
"helicopter_carrier_names": [
"LHA-1 Tarawa",
"LHA-2 Saipan",
"LHA-3 Belleau Wood",
"LHA-4 Nassau",
"LHA-5 Peleliu"
],
"navy_generators": [
"ArleighBurkeGroupGenerator"
],
"has_jtac": true,
"jtac_unit": "MQ-9 Reaper",
"doctrine": "coldwar"
}

View File

@ -1,88 +0,0 @@
{
"country": "Combined Joint Task Forces Blue",
"name": "Bluefor Coldwar (With A4 & MB339)",
"authors": "Khopa",
"description": "<p>A generic bluefor coldwar faction. (With the A-4E-C and the MB-339 mods)</p>",
"aircrafts": [
"A-10A Thunderbolt II",
"A-4E Skyhawk",
"AJS-37 Viggen",
"B-52H Stratofortress",
"F-14A Tomcat (Block 135-GR Late)",
"F-14B Tomcat",
"F-4E Phantom II",
"F-5E Tiger II",
"MB-339PAN",
"SA 342L Gazelle",
"SA 342M Gazelle",
"UH-1H Iroquois"
],
"awacs": [
"C-130",
"E-2C Hawkeye",
"E-3A"
],
"tankers": [
"KC-130",
"KC-135 Stratotanker"
],
"frontline_units": [
"M113",
"M48 Chaparral",
"M60A3 \"Patton\""
],
"artillery_units": [
"M109A6 Paladin"
],
"logistics_units": [
"Truck M818 6x6"
],
"infantry_units": [
"Infantry M249",
"Infantry M4"
],
"air_defenses": [
"ChaparralGenerator",
"EarlyColdWarFlakGenerator",
"HawkGenerator",
"VulcanGenerator"
],
"ewrs": [
"HawkEwrGenerator"
],
"aircraft_carrier": [
"Stennis"
],
"helicopter_carrier": [
"LHA_Tarawa"
],
"destroyers": [
"USS_Arleigh_Burke_IIa"
],
"cruisers": [
"TICONDEROG"
],
"requirements": {
"MB-339A/PAN by Frecce Tricolori Virtuali": "http://www.freccetricolorivirtuali.net/",
"Community A-4E": "https://heclak.github.io/community-a4e-c/"
},
"carrier_names": [
"CVN-71 Theodore Roosevelt",
"CVN-72 Abraham Lincoln",
"CVN-73 George Washington",
"CVN-74 John C. Stennis"
],
"helicopter_carrier_names": [
"LHA-1 Tarawa",
"LHA-2 Saipan",
"LHA-3 Belleau Wood",
"LHA-4 Nassau",
"LHA-5 Peleliu"
],
"navy_generators": [
"ArleighBurkeGroupGenerator"
],
"has_jtac": true,
"jtac_unit": "MQ-9 Reaper",
"doctrine": "coldwar"
}

View File

@ -12,15 +12,20 @@
"AV-8B Harrier II Night Attack",
"B-1B Lancer",
"B-52H Stratofortress",
"C-130",
"C-130J-30 Super Hercules",
"F-14B Tomcat",
"F-15C Eagle",
"F-15E Strike Eagle",
"F-16CM Fighting Falcon (Block 50)",
"F-22A Raptor",
"F-5E Tiger II",
"F/A-18C Hornet (Lot 20)",
"JF-17 Thunder",
"Ka-50 Hokum",
"Mirage 2000C",
"Mi-24P Hind-F",
"Mi-8MTV2 Hip",
"SA 342L Gazelle",
"SA 342M Gazelle",
"Su-25T Frogfoot",

View File

@ -1,7 +1,7 @@
{
"country": "Canada",
"name": "Canada 2005",
"authors": "Khopa",
"authors": "Khopa, SpaceEnthusiast",
"description": "<p>Canada in the 2000s, an F/A-18C Hornet focused faction.</p>",
"locales": [
"en_US",
@ -9,6 +9,7 @@
],
"aircrafts": [
"AH-1W SuperCobra",
"C-130J-30 Super Hercules",
"CF-188 Hornet",
"UH-1H Iroquois"
],
@ -52,7 +53,9 @@
"cruisers": [
"TICONDEROG"
],
"requirements": {},
"requirements": {
"C-130J-30 Super Hercules Mod by Anubis": "https://forums.eagle.ru/topic/252075-dcs-super-hercules-mod-by-anubis/"
},
"carrier_names": [],
"helicopter_carrier_names": [],
"navy_generators": [
@ -64,6 +67,9 @@
"CF-188 Hornet": [
"Canada 409th Squadron",
"Canada 425th Squadron"
],
"C-130J-30 Super Hercules": [
"Royal Canadian AF CC-130J"
]
}
}

View File

@ -1,75 +0,0 @@
{
"country": "Canada",
"name": "Canada 2005 (With C-130)",
"authors": "Khopa, SpaceEnthusiast",
"description": "<p>Canada in the 2000s, an F/A-18C Hornet focused faction.</p>",
"locales": [
"en_US",
"fr_CA"
],
"aircrafts": [
"AH-1W SuperCobra",
"C-130J-30 Super Hercules",
"CF-188 Hornet",
"UH-1H Iroquois"
],
"awacs": [
"E-3A"
],
"tankers": [
"KC-130",
"KC-135 Stratotanker"
],
"frontline_units": [
"FV510 Warrior",
"LAV-25",
"Leopard 1A3",
"Leopard 2",
"Leopard 2A4",
"M1097 Heavy HMMWV Avenger",
"M113"
],
"artillery_units": [],
"logistics_units": [
"Truck M818 6x6"
],
"infantry_units": [
"Infantry M249",
"Infantry M4",
"MANPADS Stinger"
],
"air_defenses": [
"AvengerGenerator",
"HawkGenerator"
],
"ewrs": [
"HawkEwrGenerator"
],
"aircraft_carrier": [],
"helicopter_carrier": [],
"destroyers": [
"USS_Arleigh_Burke_IIa"
],
"cruisers": [
"TICONDEROG"
],
"requirements": {
"C-130J-30 Super Hercules Mod by Anubis": "https://forums.eagle.ru/topic/252075-dcs-super-hercules-mod-by-anubis/"
},
"carrier_names": [],
"helicopter_carrier_names": [],
"navy_generators": [
"ArleighBurkeGroupGenerator"
],
"has_jtac": true,
"jtac_unit": "MQ-9 Reaper",
"liveries_overrides": {
"CF-188 Hornet": [
"Canada 409th Squadron",
"Canada 425th Squadron"
],
"C-130J-30 Super Hercules": [
"Royal Canadian AF CC-130J"
]
}
}

View File

@ -8,10 +8,11 @@
],
"aircrafts": [
"FC-1 Fierce Dragon",
"IL-76MD",
"J-11A Flanker-L",
"J-15 Flanker X-2",
"J-7B",
"Mi-28N Havoc",
"Mi-24P Hind-F",
"Mi-8MTV2 Hip",
"Su-30MKK Flanker-G"
],
@ -51,6 +52,7 @@
"SA10Generator",
"SA11Generator",
"SA13Generator",
"SA20BGenerator",
"Tier2SA10Generator",
"ZSU23Generator",
"ZSU57Generator",

View File

@ -0,0 +1,81 @@
{
"country": "France",
"name": "France 1985",
"authors": "Colonel Panic",
"description": "<p>1980s French equipment using FrenchPack.</p>",
"locales": [
"fr_FR"
],
"doctrine": "coldwar",
"aircrafts": [
"C-130",
"Mirage 2000C",
"SA 342L Gazelle",
"SA 342M Gazelle",
"SA 342M Gazelle Mistral"
],
"awacs": [
"E-3A"
],
"tankers": [
"KC-130",
"KC-135 Stratotanker"
],
"frontline_units": [
"AMX.30B2",
"Leclerc S\u00e9ries 2",
"Leclerc S\u00e9ries 2",
"Pamela",
"Panhard",
"Roland 2 (Marder Chassis)",
"VAB .50",
"VAB Mephisto",
"VAB T20/13",
"VBL .50",
"VBL AANF1"
],
"artillery_units": [
"M109A6 Paladin",
"M270 Multiple Launch Rocket System"
],
"logistics_units": [
"Truck M818 6x6"
],
"infantry_units": [
"Infantry M249",
"Infantry M4",
"MANPADS Stinger"
],
"air_defenses": [
"RolandGenerator",
"HawkGenerator"
],
"aircraft_carrier": [],
"helicopter_carrier": [
"LHA_Tarawa"
],
"destroyers": [
"USS_Arleigh_Burke_IIa"
],
"cruisers": [
"TICONDEROG"
],
"requirements": {
"frenchpack V3.5": "https://forums.eagle.ru/showthread.php?t=279974"
},
"carrier_names": [
"R91 Charles de Gaulle"
],
"helicopter_carrier_names": [
"R97 Jeanne d'Arc",
"L9013 Mistral",
"L9014 Tonerre",
"L9015 Dixmude"
],
"navy_generators": [
"ArleighBurkeGroupGenerator"
],
"has_jtac": true,
"jtac_unit": "MQ-9 Reaper"
}

View File

@ -1,79 +0,0 @@
{
"country": "France",
"name": "France 1985 (Frenchpack)",
"authors": "Colonel Panic",
"description": "<p>1980s French equipment using FrenchPack.</p>",
"locales": [
"fr_FR"
],
"doctrine": "coldwar",
"aircrafts": [
"Mirage 2000C",
"SA 342L Gazelle",
"SA 342M Gazelle",
"SA 342M Gazelle Mistral"
],
"awacs": [
"E-3A"
],
"tankers": [
"KC-130",
"KC-135 Stratotanker"
],
"frontline_units": [
"AMX.30B2",
"Leclerc S\u00e9ries 2",
"Leclerc S\u00e9ries 2",
"Pamela",
"Panhard",
"Roland 2 (Marder Chassis)",
"VAB .50",
"VAB Mephisto",
"VAB T20/13",
"VBL .50",
"VBL AANF1"
],
"artillery_units": [
"M109A6 Paladin",
"M270 Multiple Launch Rocket System"
],
"logistics_units": [
"Truck M818 6x6"
],
"infantry_units": [
"Infantry M249",
"Infantry M4",
"MANPADS Stinger"
],
"air_defenses": [
"RolandGenerator",
"HawkGenerator"
],
"aircraft_carrier": [],
"helicopter_carrier": [
"LHA_Tarawa"
],
"destroyers": [
"USS_Arleigh_Burke_IIa"
],
"cruisers": [
"TICONDEROG"
],
"requirements": {
"frenchpack V3.5": "https://forums.eagle.ru/showthread.php?t=279974"
},
"carrier_names": [
"R91 Charles de Gaulle"
],
"helicopter_carrier_names": [
"R97 Jeanne d'Arc",
"L9013 Mistral",
"L9014 Tonerre",
"L9015 Dixmude"
],
"navy_generators": [
"ArleighBurkeGroupGenerator"
],
"has_jtac": true,
"jtac_unit": "SA 342L Gazelle"
}

View File

@ -7,6 +7,7 @@
"fr_FR"
],
"aircrafts": [
"C-130",
"Mirage 2000-5",
"Mirage 2000C",
"SA 342L Gazelle",

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