mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
commit
f8ae1e9076
2
.git-blame-ignore-revs
Normal file
2
.git-blame-ignore-revs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Black
|
||||||
|
a47bef1f1336fd264d0b175f4421758339a30acb
|
||||||
13
.github/workflows/black.yml
vendored
Normal file
13
.github/workflows/black.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
name: Lint
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
- uses: psf/black@stable
|
||||||
|
with:
|
||||||
|
black_args: ". --check"
|
||||||
6
.pre-commit-config.yaml
Normal file
6
.pre-commit-config.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
repos:
|
||||||
|
- repo: https://github.com/psf/black
|
||||||
|
rev: 20.8b1
|
||||||
|
hooks:
|
||||||
|
- id: black
|
||||||
|
language_version: python3
|
||||||
@ -17,5 +17,5 @@ AAA_UNITS = [
|
|||||||
AirDefence.AAA_Flak_Vierling_38,
|
AirDefence.AAA_Flak_Vierling_38,
|
||||||
AirDefence.AAA_Kdo_G_40,
|
AirDefence.AAA_Kdo_G_40,
|
||||||
AirDefence.AAA_8_8cm_Flak_41,
|
AirDefence.AAA_8_8cm_Flak_41,
|
||||||
AirDefence.AAA_Bofors_40mm
|
AirDefence.AAA_Bofors_40mm,
|
||||||
]
|
]
|
||||||
@ -1,17 +1,70 @@
|
|||||||
import inspect
|
import inspect
|
||||||
import dcs
|
import dcs
|
||||||
|
|
||||||
DEFAULT_AVAILABLE_BUILDINGS = ['fuel', 'ammo', 'comms', 'oil', 'ware', 'farp', 'fob', 'power', 'factory', 'derrick']
|
DEFAULT_AVAILABLE_BUILDINGS = [
|
||||||
|
"fuel",
|
||||||
|
"ammo",
|
||||||
|
"comms",
|
||||||
|
"oil",
|
||||||
|
"ware",
|
||||||
|
"farp",
|
||||||
|
"fob",
|
||||||
|
"power",
|
||||||
|
"factory",
|
||||||
|
"derrick",
|
||||||
|
]
|
||||||
|
|
||||||
WW2_FREE = ['fuel', 'factory', 'ware', 'fob']
|
WW2_FREE = ["fuel", "factory", "ware", "fob"]
|
||||||
WW2_GERMANY_BUILDINGS = ['fuel', 'factory', 'ww2bunker', 'ww2bunker', 'ww2bunker', 'allycamp', 'allycamp', 'fob']
|
WW2_GERMANY_BUILDINGS = [
|
||||||
WW2_ALLIES_BUILDINGS = ['fuel', 'factory', 'allycamp', 'allycamp', 'allycamp', 'allycamp', 'allycamp', 'fob']
|
"fuel",
|
||||||
|
"factory",
|
||||||
|
"ww2bunker",
|
||||||
|
"ww2bunker",
|
||||||
|
"ww2bunker",
|
||||||
|
"allycamp",
|
||||||
|
"allycamp",
|
||||||
|
"fob",
|
||||||
|
]
|
||||||
|
WW2_ALLIES_BUILDINGS = [
|
||||||
|
"fuel",
|
||||||
|
"factory",
|
||||||
|
"allycamp",
|
||||||
|
"allycamp",
|
||||||
|
"allycamp",
|
||||||
|
"allycamp",
|
||||||
|
"allycamp",
|
||||||
|
"fob",
|
||||||
|
]
|
||||||
|
|
||||||
FORTIFICATION_BUILDINGS = ['Siegfried Line', 'Concertina wire', 'Concertina Wire', 'Czech hedgehogs 1', 'Czech hedgehogs 2',
|
FORTIFICATION_BUILDINGS = [
|
||||||
'Dragonteeth 1', 'Dragonteeth 2', 'Dragonteeth 3', 'Dragonteeth 4', 'Dragonteeth 5',
|
"Siegfried Line",
|
||||||
'Haystack 1', 'Haystack 2', 'Haystack 3', 'Haystack 4', 'Hemmkurvenvenhindernis',
|
"Concertina wire",
|
||||||
'Log posts 1', 'Log posts 2', 'Log posts 3', 'Log ramps 1', 'Log ramps 2', 'Log ramps 3',
|
"Concertina Wire",
|
||||||
'Belgian Gate', 'Container white']
|
"Czech hedgehogs 1",
|
||||||
|
"Czech hedgehogs 2",
|
||||||
|
"Dragonteeth 1",
|
||||||
|
"Dragonteeth 2",
|
||||||
|
"Dragonteeth 3",
|
||||||
|
"Dragonteeth 4",
|
||||||
|
"Dragonteeth 5",
|
||||||
|
"Haystack 1",
|
||||||
|
"Haystack 2",
|
||||||
|
"Haystack 3",
|
||||||
|
"Haystack 4",
|
||||||
|
"Hemmkurvenvenhindernis",
|
||||||
|
"Log posts 1",
|
||||||
|
"Log posts 2",
|
||||||
|
"Log posts 3",
|
||||||
|
"Log ramps 1",
|
||||||
|
"Log ramps 2",
|
||||||
|
"Log ramps 3",
|
||||||
|
"Belgian Gate",
|
||||||
|
"Container white",
|
||||||
|
]
|
||||||
|
|
||||||
FORTIFICATION_UNITS = [c for c in vars(dcs.vehicles.Fortification).values() if inspect.isclass(c)]
|
FORTIFICATION_UNITS = [
|
||||||
FORTIFICATION_UNITS_ID = [c.id for c in vars(dcs.vehicles.Fortification).values() if inspect.isclass(c)]
|
c for c in vars(dcs.vehicles.Fortification).values() if inspect.isclass(c)
|
||||||
|
]
|
||||||
|
FORTIFICATION_UNITS_ID = [
|
||||||
|
c.id for c in vars(dcs.vehicles.Fortification).values() if inspect.isclass(c)
|
||||||
|
]
|
||||||
|
|||||||
@ -16,7 +16,7 @@ from dcs.planes import (
|
|||||||
P_51D,
|
P_51D,
|
||||||
P_51D_30_NA,
|
P_51D_30_NA,
|
||||||
SpitfireLFMkIX,
|
SpitfireLFMkIX,
|
||||||
SpitfireLFMkIXCW
|
SpitfireLFMkIXCW,
|
||||||
)
|
)
|
||||||
|
|
||||||
from pydcs_extensions.a4ec.a4ec import A_4E_C
|
from pydcs_extensions.a4ec.a4ec import A_4E_C
|
||||||
@ -26,7 +26,6 @@ This list contains the aircraft that do not use the guns as the last resort weap
|
|||||||
They'll RTB when they don't have gun ammo left
|
They'll RTB when they don't have gun ammo left
|
||||||
"""
|
"""
|
||||||
GUNFIGHTERS = [
|
GUNFIGHTERS = [
|
||||||
|
|
||||||
# Cold War
|
# Cold War
|
||||||
MiG_15bis,
|
MiG_15bis,
|
||||||
MiG_19P,
|
MiG_19P,
|
||||||
@ -34,11 +33,9 @@ GUNFIGHTERS = [
|
|||||||
F_86F_Sabre,
|
F_86F_Sabre,
|
||||||
A_4E_C,
|
A_4E_C,
|
||||||
F_5E_3,
|
F_5E_3,
|
||||||
|
|
||||||
# Trainers
|
# Trainers
|
||||||
C_101CC,
|
C_101CC,
|
||||||
L_39ZA,
|
L_39ZA,
|
||||||
|
|
||||||
# WW2
|
# WW2
|
||||||
P_51D_30_NA,
|
P_51D_30_NA,
|
||||||
P_51D,
|
P_51D,
|
||||||
@ -51,5 +48,4 @@ GUNFIGHTERS = [
|
|||||||
FW_190D9,
|
FW_190D9,
|
||||||
FW_190A8,
|
FW_190A8,
|
||||||
I_16,
|
I_16,
|
||||||
|
|
||||||
]
|
]
|
||||||
@ -23,7 +23,6 @@ from dcs.ships import (
|
|||||||
from dcs.vehicles import AirDefence
|
from dcs.vehicles import AirDefence
|
||||||
|
|
||||||
UNITS_WITH_RADAR = [
|
UNITS_WITH_RADAR = [
|
||||||
|
|
||||||
# Radars
|
# Radars
|
||||||
AirDefence.SAM_SA_15_Tor_9A331,
|
AirDefence.SAM_SA_15_Tor_9A331,
|
||||||
AirDefence.SAM_SA_11_Buk_CC_9S470M1,
|
AirDefence.SAM_SA_11_Buk_CC_9S470M1,
|
||||||
@ -49,7 +48,6 @@ UNITS_WITH_RADAR = [
|
|||||||
AirDefence.SAM_SA_3_S_125_TR_SNR,
|
AirDefence.SAM_SA_3_S_125_TR_SNR,
|
||||||
AirDefence.SAM_SA_2_TR_SNR_75_Fan_Song,
|
AirDefence.SAM_SA_2_TR_SNR_75_Fan_Song,
|
||||||
AirDefence.HQ_7_Self_Propelled_STR,
|
AirDefence.HQ_7_Self_Propelled_STR,
|
||||||
|
|
||||||
# Ships
|
# Ships
|
||||||
CVN_70_Carl_Vinson,
|
CVN_70_Carl_Vinson,
|
||||||
Oliver_Hazzard_Perry_class,
|
Oliver_Hazzard_Perry_class,
|
||||||
@ -70,5 +68,5 @@ UNITS_WITH_RADAR = [
|
|||||||
LHA_1_Tarawa,
|
LHA_1_Tarawa,
|
||||||
Type_052B_Destroyer,
|
Type_052B_Destroyer,
|
||||||
Type_054A_Frigate,
|
Type_054A_Frigate,
|
||||||
Type_052C_Destroyer
|
Type_052C_Destroyer,
|
||||||
]
|
]
|
||||||
@ -28,7 +28,8 @@ class Weapon:
|
|||||||
introduction_year = WEAPON_INTRODUCTION_YEARS.get(self)
|
introduction_year = WEAPON_INTRODUCTION_YEARS.get(self)
|
||||||
if introduction_year is None:
|
if introduction_year is None:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
f"No introduction year for {self}, assuming always available")
|
f"No introduction year for {self}, assuming always available"
|
||||||
|
)
|
||||||
return True
|
return True
|
||||||
return date >= datetime.date(introduction_year, 1, 1)
|
return date >= datetime.date(introduction_year, 1, 1)
|
||||||
|
|
||||||
@ -52,7 +53,7 @@ class Weapon:
|
|||||||
return cls(
|
return cls(
|
||||||
cast(str, weapon_data["clsid"]),
|
cast(str, weapon_data["clsid"]),
|
||||||
cast(str, weapon_data["name"]),
|
cast(str, weapon_data["name"]),
|
||||||
cast(int, weapon_data["weight"])
|
cast(int, weapon_data["weight"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -88,8 +89,11 @@ class Pylon:
|
|||||||
def for_aircraft(cls, aircraft: Type[FlyingType], number: int) -> Pylon:
|
def for_aircraft(cls, aircraft: Type[FlyingType], number: int) -> Pylon:
|
||||||
# In pydcs these are all arbitrary inner classes of the aircraft type.
|
# In pydcs these are all arbitrary inner classes of the aircraft type.
|
||||||
# The only way to identify them is by their name.
|
# The only way to identify them is by their name.
|
||||||
pylons = [v for v in aircraft.__dict__.values() if
|
pylons = [
|
||||||
inspect.isclass(v) and v.__name__.startswith("Pylon")]
|
v
|
||||||
|
for v in aircraft.__dict__.values()
|
||||||
|
if inspect.isclass(v) and v.__name__.startswith("Pylon")
|
||||||
|
]
|
||||||
|
|
||||||
# And that Pylon class has members with irrelevant names that have
|
# And that Pylon class has members with irrelevant names that have
|
||||||
# values of (pylon number, allowed weapon).
|
# values of (pylon number, allowed weapon).
|
||||||
@ -117,72 +121,60 @@ _WEAPON_FALLBACKS = [
|
|||||||
(Weapons.ADM_141A_, None),
|
(Weapons.ADM_141A_, None),
|
||||||
(Weapons.ADM_141A__, None),
|
(Weapons.ADM_141A__, None),
|
||||||
(Weapons.ADM_141B, None),
|
(Weapons.ADM_141B, None),
|
||||||
|
|
||||||
# AGM-114K Hellfire
|
# AGM-114K Hellfire
|
||||||
(Weapons.AGM114x2_OH_58, Weapons.M260_HYDRA), # assuming OH-58 and not MQ-9
|
(Weapons.AGM114x2_OH_58, Weapons.M260_HYDRA), # assuming OH-58 and not MQ-9
|
||||||
(Weapons.AGM_114K, None), # Only for RQ-1
|
(Weapons.AGM_114K, None), # Only for RQ-1
|
||||||
(Weapons.AGM_114K___4, Weapons.LAU_61___19_2_75__rockets_MK151_HE),
|
(Weapons.AGM_114K___4, Weapons.LAU_61___19_2_75__rockets_MK151_HE),
|
||||||
|
|
||||||
# AGM-119 Penguin
|
# AGM-119 Penguin
|
||||||
(Weapons.AGM_119B_Penguin, Weapons.Mk_82),
|
(Weapons.AGM_119B_Penguin, Weapons.Mk_82),
|
||||||
|
|
||||||
# AGM-122 Sidearm
|
# AGM-122 Sidearm
|
||||||
(Weapons.AGM_122, None), # No known aircraft carries this
|
(Weapons.AGM_122, None), # No known aircraft carries this
|
||||||
(Weapons.AGM_122_Sidearm, Weapons.GBU_12), # outer pylons harrier
|
(Weapons.AGM_122_Sidearm, Weapons.GBU_12), # outer pylons harrier
|
||||||
(Weapons.AGM_122_Sidearm_, Weapons.LAU_117_AGM_65E), # internal pylons harrier
|
(Weapons.AGM_122_Sidearm_, Weapons.LAU_117_AGM_65E), # internal pylons harrier
|
||||||
|
|
||||||
# AGM-154 JSOW
|
# AGM-154 JSOW
|
||||||
(Weapons.AGM_154A, Weapons.GBU_12),
|
(Weapons.AGM_154A, Weapons.GBU_12),
|
||||||
(Weapons.BRU_55___2_x_AGM_154A, Weapons.BRU_33___2_x_GBU_12),
|
(Weapons.BRU_55___2_x_AGM_154A, Weapons.BRU_33___2_x_GBU_12),
|
||||||
(Weapons.BRU_57___2_x_AGM_154A, None), # doesn't exist on any aircraft yet
|
(Weapons.BRU_57___2_x_AGM_154A, None), # doesn't exist on any aircraft yet
|
||||||
|
|
||||||
(Weapons.AGM_154B, Weapons.CBU_105),
|
(Weapons.AGM_154B, Weapons.CBU_105),
|
||||||
|
|
||||||
(Weapons.AGM_154C, Weapons.GBU_12),
|
(Weapons.AGM_154C, Weapons.GBU_12),
|
||||||
(Weapons.AGM_154C_4, Weapons.GBU_31_8),
|
(Weapons.AGM_154C_4, Weapons.GBU_31_8),
|
||||||
(Weapons.BRU_55___2_x_AGM_154C, Weapons.BRU_33___2_x_GBU_12),
|
(Weapons.BRU_55___2_x_AGM_154C, Weapons.BRU_33___2_x_GBU_12),
|
||||||
|
|
||||||
# AGM-45 Shrike
|
# AGM-45 Shrike
|
||||||
(Weapons.AGM_45A, None),
|
(Weapons.AGM_45A, None),
|
||||||
(Weapons.AGM_45B, Weapons.AGM_45A),
|
(Weapons.AGM_45B, Weapons.AGM_45A),
|
||||||
(Weapons.AGM_45B_, Weapons.AGM_45A),
|
(Weapons.AGM_45B_, Weapons.AGM_45A),
|
||||||
|
|
||||||
# AGM-62 Walleye
|
# AGM-62 Walleye
|
||||||
(Weapons.AGM_62, Weapons.Mk_84),
|
(Weapons.AGM_62, Weapons.Mk_84),
|
||||||
|
|
||||||
# AGM-65 Maverick
|
# AGM-65 Maverick
|
||||||
(Weapons.AGM_65D, None), # doesn't exist
|
(Weapons.AGM_65D, None), # doesn't exist
|
||||||
(Weapons.AGM_65E, None), # doesn't exist
|
(Weapons.AGM_65E, None), # doesn't exist
|
||||||
(Weapons.AGM_65K, None), # doesn't exist
|
(Weapons.AGM_65K, None), # doesn't exist
|
||||||
(Weapons.LAU_117_AGM_65A, None), # doesn't exist
|
(Weapons.LAU_117_AGM_65A, None), # doesn't exist
|
||||||
(Weapons.LAU_117_AGM_65B, None), # doesn't exist
|
(Weapons.LAU_117_AGM_65B, None), # doesn't exist
|
||||||
|
(
|
||||||
(Weapons.LAU_117_AGM_65D, Weapons.AGM_62), # Walleye is the predecessor to the maverick
|
Weapons.LAU_117_AGM_65D,
|
||||||
|
Weapons.AGM_62,
|
||||||
|
), # Walleye is the predecessor to the maverick
|
||||||
(Weapons.LAU_117_AGM_65E, None),
|
(Weapons.LAU_117_AGM_65E, None),
|
||||||
(Weapons.LAU_117_AGM_65F, Weapons.LAU_117_AGM_65D),
|
(Weapons.LAU_117_AGM_65F, Weapons.LAU_117_AGM_65D),
|
||||||
(Weapons.LAU_117_AGM_65G, Weapons.LAU_117_AGM_65D),
|
(Weapons.LAU_117_AGM_65G, Weapons.LAU_117_AGM_65D),
|
||||||
(Weapons.LAU_117_AGM_65H, Weapons.LAU_117_AGM_65D),
|
(Weapons.LAU_117_AGM_65H, Weapons.LAU_117_AGM_65D),
|
||||||
(Weapons.LAU_117_AGM_65K, Weapons.LAU_117_AGM_65D),
|
(Weapons.LAU_117_AGM_65K, Weapons.LAU_117_AGM_65D),
|
||||||
(Weapons.LAU_117_AGM_65L, None),
|
(Weapons.LAU_117_AGM_65L, None),
|
||||||
|
|
||||||
(Weapons.LAU_88_AGM_65D_2, None),
|
(Weapons.LAU_88_AGM_65D_2, None),
|
||||||
(Weapons.LAU_88_AGM_65D_2_, None),
|
(Weapons.LAU_88_AGM_65D_2_, None),
|
||||||
(Weapons.LAU_88_AGM_65D_3, None),
|
(Weapons.LAU_88_AGM_65D_3, None),
|
||||||
(Weapons.LAU_88_AGM_65D_ONE, None),
|
(Weapons.LAU_88_AGM_65D_ONE, None),
|
||||||
|
|
||||||
(Weapons.LAU_88_AGM_65E_2, Weapons.LAU_88_AGM_65D_2),
|
(Weapons.LAU_88_AGM_65E_2, Weapons.LAU_88_AGM_65D_2),
|
||||||
(Weapons.LAU_88_AGM_65E_2_, Weapons.LAU_88_AGM_65D_2_),
|
(Weapons.LAU_88_AGM_65E_2_, Weapons.LAU_88_AGM_65D_2_),
|
||||||
(Weapons.LAU_88_AGM_65E_3, Weapons.LAU_88_AGM_65D_3),
|
(Weapons.LAU_88_AGM_65E_3, Weapons.LAU_88_AGM_65D_3),
|
||||||
|
|
||||||
(Weapons.LAU_88_AGM_65H, Weapons.LAU_88_AGM_65D_2),
|
(Weapons.LAU_88_AGM_65H, Weapons.LAU_88_AGM_65D_2),
|
||||||
(Weapons.LAU_88_AGM_65H_2_L, Weapons.LAU_88_AGM_65D_2_),
|
(Weapons.LAU_88_AGM_65H_2_L, Weapons.LAU_88_AGM_65D_2_),
|
||||||
(Weapons.LAU_88_AGM_65H_2_R, Weapons.LAU_88_AGM_65D_2_),
|
(Weapons.LAU_88_AGM_65H_2_R, Weapons.LAU_88_AGM_65D_2_),
|
||||||
(Weapons.LAU_88_AGM_65H_3, Weapons.LAU_88_AGM_65D_3),
|
(Weapons.LAU_88_AGM_65H_3, Weapons.LAU_88_AGM_65D_3),
|
||||||
|
|
||||||
(Weapons.LAU_88_AGM_65K_2, Weapons.LAU_88_AGM_65D_2),
|
(Weapons.LAU_88_AGM_65K_2, Weapons.LAU_88_AGM_65D_2),
|
||||||
(Weapons.LAU_88_AGM_65K_2_, Weapons.LAU_88_AGM_65D_2_),
|
(Weapons.LAU_88_AGM_65K_2_, Weapons.LAU_88_AGM_65D_2_),
|
||||||
(Weapons.LAU_88_AGM_65K_3, Weapons.LAU_88_AGM_65D_3),
|
(Weapons.LAU_88_AGM_65K_3, Weapons.LAU_88_AGM_65D_3),
|
||||||
|
|
||||||
# AGM-84 Harpoon
|
# AGM-84 Harpoon
|
||||||
(Weapons.AGM_84, None), # doesn't exist
|
(Weapons.AGM_84, None), # doesn't exist
|
||||||
(Weapons.AGM_84A, Weapons.Mk_82),
|
(Weapons.AGM_84A, Weapons.Mk_82),
|
||||||
@ -190,39 +182,31 @@ _WEAPON_FALLBACKS = [
|
|||||||
(Weapons.AGM_84D, Weapons.AGM_62),
|
(Weapons.AGM_84D, Weapons.AGM_62),
|
||||||
(Weapons.AGM_84E, Weapons.LAU_117_AGM_65F),
|
(Weapons.AGM_84E, Weapons.LAU_117_AGM_65F),
|
||||||
(Weapons.AGM_84H, Weapons.AGM_84E),
|
(Weapons.AGM_84H, Weapons.AGM_84E),
|
||||||
|
|
||||||
# AGM-86 ALCM
|
# AGM-86 ALCM
|
||||||
(Weapons.AGM_86C, Weapons._27_Mk_82),
|
(Weapons.AGM_86C, Weapons._27_Mk_82),
|
||||||
(Weapons.AGM_86C_20, Weapons._27_Mk_82),
|
(Weapons.AGM_86C_20, Weapons._27_Mk_82),
|
||||||
(Weapons.AGM_86C_8, Weapons._27_Mk_82),
|
(Weapons.AGM_86C_8, Weapons._27_Mk_82),
|
||||||
(Weapons.MER_6_AGM_86C, Weapons.MER_12_Mk_82),
|
(Weapons.MER_6_AGM_86C, Weapons.MER_12_Mk_82),
|
||||||
|
|
||||||
# AGM-88 HARM
|
# AGM-88 HARM
|
||||||
(Weapons.AGM_88C, Weapons.AGM_65D),
|
(Weapons.AGM_88C, Weapons.AGM_65D),
|
||||||
(Weapons.AGM_88C_, Weapons.AGM_65D),
|
(Weapons.AGM_88C_, Weapons.AGM_65D),
|
||||||
|
|
||||||
# AIM-120 AMRAAM
|
# AIM-120 AMRAAM
|
||||||
(Weapons.AIM_120B, Weapons.AIM_7MH),
|
(Weapons.AIM_120B, Weapons.AIM_7MH),
|
||||||
(Weapons.LAU_115___AIM_120B, Weapons.LAU_115C_AIM_7MH),
|
(Weapons.LAU_115___AIM_120B, Weapons.LAU_115C_AIM_7MH),
|
||||||
(Weapons.LAU_115_2_LAU_127_AIM_120B, Weapons.LAU_115C_AIM_7MH),
|
(Weapons.LAU_115_2_LAU_127_AIM_120B, Weapons.LAU_115C_AIM_7MH),
|
||||||
|
|
||||||
(Weapons.AIM_120C, Weapons.AIM_120B),
|
(Weapons.AIM_120C, Weapons.AIM_120B),
|
||||||
(Weapons.LAU_115___AIM_120C, Weapons.LAU_115___AIM_120B),
|
(Weapons.LAU_115___AIM_120C, Weapons.LAU_115___AIM_120B),
|
||||||
(Weapons.LAU_115_2_LAU_127_AIM_120C, Weapons.LAU_115_2_LAU_127_AIM_120B),
|
(Weapons.LAU_115_2_LAU_127_AIM_120C, Weapons.LAU_115_2_LAU_127_AIM_120B),
|
||||||
|
|
||||||
# AIM-54 Phoenix
|
# AIM-54 Phoenix
|
||||||
(Weapons.AIM_54A_Mk47, None),
|
(Weapons.AIM_54A_Mk47, None),
|
||||||
(Weapons.AIM_54A_Mk47_, None),
|
(Weapons.AIM_54A_Mk47_, None),
|
||||||
(Weapons.AIM_54A_Mk47__, None),
|
(Weapons.AIM_54A_Mk47__, None),
|
||||||
|
|
||||||
(Weapons.AIM_54A_Mk60, Weapons.AIM_54A_Mk47),
|
(Weapons.AIM_54A_Mk60, Weapons.AIM_54A_Mk47),
|
||||||
(Weapons.AIM_54A_Mk60_, Weapons.AIM_54A_Mk47_),
|
(Weapons.AIM_54A_Mk60_, Weapons.AIM_54A_Mk47_),
|
||||||
(Weapons.AIM_54A_Mk60__, Weapons.AIM_54A_Mk47__),
|
(Weapons.AIM_54A_Mk60__, Weapons.AIM_54A_Mk47__),
|
||||||
|
|
||||||
(Weapons.AIM_54C_Mk47, Weapons.AIM_54A_Mk60),
|
(Weapons.AIM_54C_Mk47, Weapons.AIM_54A_Mk60),
|
||||||
(Weapons.AIM_54C_Mk47_, Weapons.AIM_54A_Mk60_),
|
(Weapons.AIM_54C_Mk47_, Weapons.AIM_54A_Mk60_),
|
||||||
(Weapons.AIM_54C_Mk47__, Weapons.AIM_54A_Mk60__),
|
(Weapons.AIM_54C_Mk47__, Weapons.AIM_54A_Mk60__),
|
||||||
|
|
||||||
# AIM-7 Sparrow
|
# AIM-7 Sparrow
|
||||||
(Weapons.AIM_7E, None),
|
(Weapons.AIM_7E, None),
|
||||||
(Weapons.AIM_7F, Weapons.AIM_7E),
|
(Weapons.AIM_7F, Weapons.AIM_7E),
|
||||||
@ -234,62 +218,61 @@ _WEAPON_FALLBACKS = [
|
|||||||
(Weapons.AIM_7MH, Weapons.AIM_7M),
|
(Weapons.AIM_7MH, Weapons.AIM_7M),
|
||||||
(Weapons.AIM_7MH_, Weapons.AIM_7M_),
|
(Weapons.AIM_7MH_, Weapons.AIM_7M_),
|
||||||
(Weapons.AIM_7MH__, Weapons.AIM_7M__),
|
(Weapons.AIM_7MH__, Weapons.AIM_7M__),
|
||||||
|
|
||||||
(Weapons.LAU_115C_AIM_7E, None),
|
(Weapons.LAU_115C_AIM_7E, None),
|
||||||
(Weapons.LAU_115C_AIM_7F, Weapons.LAU_115C_AIM_7E),
|
(Weapons.LAU_115C_AIM_7F, Weapons.LAU_115C_AIM_7E),
|
||||||
(Weapons.LAU_115___AIM_7M, Weapons.LAU_115C_AIM_7F),
|
(Weapons.LAU_115___AIM_7M, Weapons.LAU_115C_AIM_7F),
|
||||||
(Weapons.LAU_115C_AIM_7MH, Weapons.LAU_115___AIM_7M),
|
(Weapons.LAU_115C_AIM_7MH, Weapons.LAU_115___AIM_7M),
|
||||||
|
|
||||||
# AIM-9 Sidewinder
|
# AIM-9 Sidewinder
|
||||||
(Weapons.AIM_9L_Sidewinder_IR_AAM, None),
|
(Weapons.AIM_9L_Sidewinder_IR_AAM, None),
|
||||||
(Weapons.AIM_9M_Sidewinder_IR_AAM, Weapons.AIM_9P5_Sidewinder_IR_AAM),
|
(Weapons.AIM_9M_Sidewinder_IR_AAM, Weapons.AIM_9P5_Sidewinder_IR_AAM),
|
||||||
(Weapons.AIM_9P5_Sidewinder_IR_AAM, Weapons.AIM_9P_Sidewinder_IR_AAM),
|
(Weapons.AIM_9P5_Sidewinder_IR_AAM, Weapons.AIM_9P_Sidewinder_IR_AAM),
|
||||||
(Weapons.AIM_9P_Sidewinder_IR_AAM, Weapons.AIM_9L_Sidewinder_IR_AAM),
|
(Weapons.AIM_9P_Sidewinder_IR_AAM, Weapons.AIM_9L_Sidewinder_IR_AAM),
|
||||||
(Weapons.AIM_9X_Sidewinder_IR_AAM, Weapons.AIM_9P_Sidewinder_IR_AAM),
|
(Weapons.AIM_9X_Sidewinder_IR_AAM, Weapons.AIM_9P_Sidewinder_IR_AAM),
|
||||||
|
|
||||||
(Weapons.LAU_105_1_AIM_9L_L, None),
|
(Weapons.LAU_105_1_AIM_9L_L, None),
|
||||||
(Weapons.LAU_105_1_AIM_9L_R, None),
|
(Weapons.LAU_105_1_AIM_9L_R, None),
|
||||||
(Weapons.LAU_105_1_AIM_9M_L, Weapons.LAU_105_1_AIM_9L_L),
|
(Weapons.LAU_105_1_AIM_9M_L, Weapons.LAU_105_1_AIM_9L_L),
|
||||||
(Weapons.LAU_105_1_AIM_9M_R, Weapons.LAU_105_1_AIM_9L_R),
|
(Weapons.LAU_105_1_AIM_9M_R, Weapons.LAU_105_1_AIM_9L_R),
|
||||||
|
|
||||||
(Weapons.LAU_105_2_AIM_9L, None),
|
(Weapons.LAU_105_2_AIM_9L, None),
|
||||||
(Weapons.LAU_105_2_AIM_9P5, Weapons.LAU_105___2_AIM_9P_Sidewinder_IR_AAM),
|
(Weapons.LAU_105_2_AIM_9P5, Weapons.LAU_105___2_AIM_9P_Sidewinder_IR_AAM),
|
||||||
|
|
||||||
(Weapons.LAU_105___2_AIM_9M_Sidewinder_IR_AAM, Weapons.LAU_105_2_AIM_9L),
|
(Weapons.LAU_105___2_AIM_9M_Sidewinder_IR_AAM, Weapons.LAU_105_2_AIM_9L),
|
||||||
(Weapons.LAU_105___2_AIM_9P_Sidewinder_IR_AAM, Weapons.LAU_105___2_AIM_9M_Sidewinder_IR_AAM),
|
(
|
||||||
|
Weapons.LAU_105___2_AIM_9P_Sidewinder_IR_AAM,
|
||||||
|
Weapons.LAU_105___2_AIM_9M_Sidewinder_IR_AAM,
|
||||||
|
),
|
||||||
(Weapons.LAU_115_2_LAU_127_AIM_9L, None),
|
(Weapons.LAU_115_2_LAU_127_AIM_9L, None),
|
||||||
(Weapons.LAU_115_2_LAU_127_AIM_9M, Weapons.LAU_115_2_LAU_127_AIM_9L),
|
(Weapons.LAU_115_2_LAU_127_AIM_9M, Weapons.LAU_115_2_LAU_127_AIM_9L),
|
||||||
(Weapons.LAU_115_2_LAU_127_AIM_9X, Weapons.LAU_115_2_LAU_127_AIM_9M),
|
(Weapons.LAU_115_2_LAU_127_AIM_9X, Weapons.LAU_115_2_LAU_127_AIM_9M),
|
||||||
|
|
||||||
(Weapons.LAU_115_LAU_127_AIM_9L, None),
|
(Weapons.LAU_115_LAU_127_AIM_9L, None),
|
||||||
(Weapons.LAU_115_LAU_127_AIM_9M, Weapons.LAU_115_LAU_127_AIM_9L),
|
(Weapons.LAU_115_LAU_127_AIM_9M, Weapons.LAU_115_LAU_127_AIM_9L),
|
||||||
(Weapons.LAU_115_LAU_127_AIM_9X, Weapons.LAU_115_LAU_127_AIM_9M),
|
(Weapons.LAU_115_LAU_127_AIM_9X, Weapons.LAU_115_LAU_127_AIM_9M),
|
||||||
|
|
||||||
(Weapons.LAU_127_AIM_9L, None),
|
(Weapons.LAU_127_AIM_9L, None),
|
||||||
(Weapons.LAU_127_AIM_9M, Weapons.LAU_127_AIM_9L),
|
(Weapons.LAU_127_AIM_9M, Weapons.LAU_127_AIM_9L),
|
||||||
(Weapons.LAU_127_AIM_9X, Weapons.LAU_127_AIM_9M),
|
(Weapons.LAU_127_AIM_9X, Weapons.LAU_127_AIM_9M),
|
||||||
|
|
||||||
(Weapons.LAU_138_AIM_9L, None),
|
(Weapons.LAU_138_AIM_9L, None),
|
||||||
(Weapons.LAU_138_AIM_9M, Weapons.LAU_138_AIM_9L),
|
(Weapons.LAU_138_AIM_9M, Weapons.LAU_138_AIM_9L),
|
||||||
|
|
||||||
(Weapons.LAU_7_AIM_9L, None),
|
(Weapons.LAU_7_AIM_9L, None),
|
||||||
(Weapons.LAU_7_AIM_9M, Weapons.LAU_7_AIM_9L),
|
(Weapons.LAU_7_AIM_9M, Weapons.LAU_7_AIM_9L),
|
||||||
(Weapons.LAU_7_AIM_9M_Sidewinder_IR_AAM, Weapons.LAU_7_AIM_9P5_Sidewinder_IR_AAM),
|
(Weapons.LAU_7_AIM_9M_Sidewinder_IR_AAM, Weapons.LAU_7_AIM_9P5_Sidewinder_IR_AAM),
|
||||||
(Weapons.LAU_7_AIM_9P5_Sidewinder_IR_AAM, Weapons.LAU_7_AIM_9P_Sidewinder_IR_AAM),
|
(Weapons.LAU_7_AIM_9P5_Sidewinder_IR_AAM, Weapons.LAU_7_AIM_9P_Sidewinder_IR_AAM),
|
||||||
(Weapons.LAU_7_AIM_9P_Sidewinder_IR_AAM, Weapons.LAU_7_AIM_9L),
|
(Weapons.LAU_7_AIM_9P_Sidewinder_IR_AAM, Weapons.LAU_7_AIM_9L),
|
||||||
(Weapons.LAU_7_AIM_9X_Sidewinder_IR_AAM, Weapons.LAU_7_AIM_9M_Sidewinder_IR_AAM),
|
(Weapons.LAU_7_AIM_9X_Sidewinder_IR_AAM, Weapons.LAU_7_AIM_9M_Sidewinder_IR_AAM),
|
||||||
|
|
||||||
(Weapons.LAU_7___2_AIM_9L_Sidewinder_IR_AAM, None),
|
(Weapons.LAU_7___2_AIM_9L_Sidewinder_IR_AAM, None),
|
||||||
(Weapons.LAU_7___2_AIM_9M_Sidewinder_IR_AAM, Weapons.LAU_7___2_AIM_9P5_Sidewinder_IR_AAM),
|
(
|
||||||
(Weapons.LAU_7___2_AIM_9P5_Sidewinder_IR_AAM, Weapons.LAU_7___2_AIM_9P_Sidewinder_IR_AAM),
|
Weapons.LAU_7___2_AIM_9M_Sidewinder_IR_AAM,
|
||||||
(Weapons.LAU_7___2_AIM_9P_Sidewinder_IR_AAM, Weapons.LAU_7___2_AIM_9L_Sidewinder_IR_AAM),
|
Weapons.LAU_7___2_AIM_9P5_Sidewinder_IR_AAM,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Weapons.LAU_7___2_AIM_9P5_Sidewinder_IR_AAM,
|
||||||
|
Weapons.LAU_7___2_AIM_9P_Sidewinder_IR_AAM,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Weapons.LAU_7___2_AIM_9P_Sidewinder_IR_AAM,
|
||||||
|
Weapons.LAU_7___2_AIM_9L_Sidewinder_IR_AAM,
|
||||||
|
),
|
||||||
# ALQ ECM Pods
|
# ALQ ECM Pods
|
||||||
(Weapons.ALQ_131, None),
|
(Weapons.ALQ_131, None),
|
||||||
(Weapons.ALQ_184, Weapons.ALQ_131),
|
(Weapons.ALQ_184, Weapons.ALQ_131),
|
||||||
(Weapons.AN_ALQ_164_DECM_Pod, None),
|
(Weapons.AN_ALQ_164_DECM_Pod, None),
|
||||||
|
|
||||||
# TGP Pods
|
# TGP Pods
|
||||||
(Weapons.AN_AAQ_28_LITENING_, None),
|
(Weapons.AN_AAQ_28_LITENING_, None),
|
||||||
(Weapons.AN_AAQ_28_LITENING, Weapons.Lantirn_F_16),
|
(Weapons.AN_AAQ_28_LITENING, Weapons.Lantirn_F_16),
|
||||||
@ -300,17 +283,14 @@ _WEAPON_FALLBACKS = [
|
|||||||
(Weapons.Lantirn_F_16, None),
|
(Weapons.Lantirn_F_16, None),
|
||||||
(Weapons.Lantirn_Target_Pod, None),
|
(Weapons.Lantirn_Target_Pod, None),
|
||||||
(Weapons.Pavetack_F_111, None),
|
(Weapons.Pavetack_F_111, None),
|
||||||
|
|
||||||
# BLU-107
|
# BLU-107
|
||||||
(Weapons.BLU_107, None),
|
(Weapons.BLU_107, None),
|
||||||
(Weapons.MER_6_BLU_107, Weapons.MER_6_Mk_82),
|
(Weapons.MER_6_BLU_107, Weapons.MER_6_Mk_82),
|
||||||
|
|
||||||
# GBU-10 LGB
|
# GBU-10 LGB
|
||||||
(Weapons.DIS_GBU_10, Weapons.Mk_84),
|
(Weapons.DIS_GBU_10, Weapons.Mk_84),
|
||||||
(Weapons.GBU_10, Weapons.Mk_84),
|
(Weapons.GBU_10, Weapons.Mk_84),
|
||||||
(Weapons.GBU_10_, Weapons.Mk_84),
|
(Weapons.GBU_10_, Weapons.Mk_84),
|
||||||
(Weapons.GBU_10_2, Weapons.Mk_84),
|
(Weapons.GBU_10_2, Weapons.Mk_84),
|
||||||
|
|
||||||
# GBU-12 LGB
|
# GBU-12 LGB
|
||||||
(Weapons.AUF2_GBU_12_x_2, None),
|
(Weapons.AUF2_GBU_12_x_2, None),
|
||||||
(Weapons.BRU_33___2_x_GBU_12, Weapons.BRU_33___2_x_Mk_82_),
|
(Weapons.BRU_33___2_x_GBU_12, Weapons.BRU_33___2_x_Mk_82_),
|
||||||
@ -328,29 +308,23 @@ _WEAPON_FALLBACKS = [
|
|||||||
(Weapons._2_GBU_12, Weapons._2_Mk_82),
|
(Weapons._2_GBU_12, Weapons._2_Mk_82),
|
||||||
(Weapons._2_GBU_12_, Weapons._2_Mk_82_),
|
(Weapons._2_GBU_12_, Weapons._2_Mk_82_),
|
||||||
(Weapons._3_GBU_12, Weapons._3_Mk_82_),
|
(Weapons._3_GBU_12, Weapons._3_Mk_82_),
|
||||||
|
|
||||||
# GBU-15 LGB
|
# GBU-15 LGB
|
||||||
(Weapons.GBU_15, Weapons.Mk_84),
|
(Weapons.GBU_15, Weapons.Mk_84),
|
||||||
|
|
||||||
# GBU-16 LGB
|
# GBU-16 LGB
|
||||||
(Weapons.BRU_33___2_x_GBU_16, None),
|
(Weapons.BRU_33___2_x_GBU_16, None),
|
||||||
(Weapons.DIS_GBU_16, Weapons.Mk_83),
|
(Weapons.DIS_GBU_16, Weapons.Mk_83),
|
||||||
(Weapons.GBU_16, Weapons.Mk_83),
|
(Weapons.GBU_16, Weapons.Mk_83),
|
||||||
(Weapons.GBU_16_, Weapons.Mk_83_),
|
(Weapons.GBU_16_, Weapons.Mk_83_),
|
||||||
|
|
||||||
# GBU-24 LGB
|
# GBU-24 LGB
|
||||||
(Weapons.GBU_24, Weapons.GBU_10),
|
(Weapons.GBU_24, Weapons.GBU_10),
|
||||||
(Weapons.GBU_24_, Weapons.GBU_10_),
|
(Weapons.GBU_24_, Weapons.GBU_10_),
|
||||||
(Weapons.GBU_24__, Weapons.GBU_10_),
|
(Weapons.GBU_24__, Weapons.GBU_10_),
|
||||||
|
|
||||||
# GBU-27 LGB
|
# GBU-27 LGB
|
||||||
(Weapons.GBU_24, Weapons.GBU_10),
|
(Weapons.GBU_24, Weapons.GBU_10),
|
||||||
(Weapons.GBU_24_, Weapons.GBU_10_),
|
(Weapons.GBU_24_, Weapons.GBU_10_),
|
||||||
(Weapons.GBU_24__, Weapons.GBU_10_),
|
(Weapons.GBU_24__, Weapons.GBU_10_),
|
||||||
|
|
||||||
# GBU-28 LGB
|
# GBU-28 LGB
|
||||||
(Weapons.GBU_28, Weapons.GBU_15),
|
(Weapons.GBU_28, Weapons.GBU_15),
|
||||||
|
|
||||||
# GBU-31 JDAM
|
# GBU-31 JDAM
|
||||||
(Weapons.GBU_31V3B_8, Weapons.B_1B_Mk_84_8),
|
(Weapons.GBU_31V3B_8, Weapons.B_1B_Mk_84_8),
|
||||||
(Weapons.GBU_31_8, Weapons.B_1B_Mk_84_8),
|
(Weapons.GBU_31_8, Weapons.B_1B_Mk_84_8),
|
||||||
@ -358,10 +332,8 @@ _WEAPON_FALLBACKS = [
|
|||||||
(Weapons.GBU_31_V_2_B, Weapons.GBU_24_),
|
(Weapons.GBU_31_V_2_B, Weapons.GBU_24_),
|
||||||
(Weapons.GBU_31_V_3_B, Weapons.GBU_24_),
|
(Weapons.GBU_31_V_3_B, Weapons.GBU_24_),
|
||||||
(Weapons.GBU_31_V_4_B, Weapons.GBU_24_),
|
(Weapons.GBU_31_V_4_B, Weapons.GBU_24_),
|
||||||
|
|
||||||
# GBU-32 JDAM
|
# GBU-32 JDAM
|
||||||
(Weapons.GBU_32_V_2_B, Weapons.GBU_16),
|
(Weapons.GBU_32_V_2_B, Weapons.GBU_16),
|
||||||
|
|
||||||
# GBU-32 JDAM
|
# GBU-32 JDAM
|
||||||
(Weapons.BRU_55___2_x_GBU_38, Weapons.BRU_33___2_x_Mk_82_),
|
(Weapons.BRU_55___2_x_GBU_38, Weapons.BRU_33___2_x_Mk_82_),
|
||||||
(Weapons.BRU_57___2_x_GBU_38, None), # Doesn't exist
|
(Weapons.BRU_57___2_x_GBU_38, None), # Doesn't exist
|
||||||
@ -370,138 +342,117 @@ _WEAPON_FALLBACKS = [
|
|||||||
(Weapons._2_GBU_38, Weapons._2_Mk_82),
|
(Weapons._2_GBU_38, Weapons._2_Mk_82),
|
||||||
(Weapons._2_GBU_38_, Weapons._2_Mk_82_),
|
(Weapons._2_GBU_38_, Weapons._2_Mk_82_),
|
||||||
(Weapons._3_GBU_38, Weapons._3_Mk_82_),
|
(Weapons._3_GBU_38, Weapons._3_Mk_82_),
|
||||||
|
|
||||||
# GBU-54 LJDAM
|
# GBU-54 LJDAM
|
||||||
(Weapons.GBU_54_V_1_B, Weapons.GBU_38),
|
(Weapons.GBU_54_V_1_B, Weapons.GBU_38),
|
||||||
(Weapons._2_GBU_54_V_1_B, Weapons._2_GBU_38),
|
(Weapons._2_GBU_54_V_1_B, Weapons._2_GBU_38),
|
||||||
(Weapons._2_GBU_54_V_1_B_, Weapons._2_GBU_38_),
|
(Weapons._2_GBU_54_V_1_B_, Weapons._2_GBU_38_),
|
||||||
(Weapons._3_GBU_54_V_1_B, Weapons._3_GBU_38),
|
(Weapons._3_GBU_54_V_1_B, Weapons._3_GBU_38),
|
||||||
|
|
||||||
# CBU-52
|
# CBU-52
|
||||||
(Weapons.CBU_52B, None),
|
(Weapons.CBU_52B, None),
|
||||||
|
|
||||||
# CBU-87 CEM
|
# CBU-87 CEM
|
||||||
(Weapons.CBU_87, Weapons.Mk_82),
|
(Weapons.CBU_87, Weapons.Mk_82),
|
||||||
(Weapons.TER_9A___2_x_CBU_87, Weapons.TER_9A___2_x_Mk_82),
|
(Weapons.TER_9A___2_x_CBU_87, Weapons.TER_9A___2_x_Mk_82),
|
||||||
(Weapons.TER_9A___2_x_CBU_87_, Weapons.TER_9A___2_x_Mk_82_),
|
(Weapons.TER_9A___2_x_CBU_87_, Weapons.TER_9A___2_x_Mk_82_),
|
||||||
(Weapons.TER_9A___3_x_CBU_87, Weapons.TER_9A___3_x_Mk_82),
|
(Weapons.TER_9A___3_x_CBU_87, Weapons.TER_9A___3_x_Mk_82),
|
||||||
|
|
||||||
# CBU-97
|
# CBU-97
|
||||||
(Weapons.CBU_97, Weapons.Mk_82),
|
(Weapons.CBU_97, Weapons.Mk_82),
|
||||||
(Weapons.TER_9A___2_x_CBU_97, Weapons.TER_9A___2_x_Mk_82),
|
(Weapons.TER_9A___2_x_CBU_97, Weapons.TER_9A___2_x_Mk_82),
|
||||||
(Weapons.TER_9A___2_x_CBU_97_, Weapons.TER_9A___2_x_Mk_82_),
|
(Weapons.TER_9A___2_x_CBU_97_, Weapons.TER_9A___2_x_Mk_82_),
|
||||||
(Weapons.TER_9A___3_x_CBU_97, Weapons.TER_9A___3_x_Mk_82),
|
(Weapons.TER_9A___3_x_CBU_97, Weapons.TER_9A___3_x_Mk_82),
|
||||||
|
|
||||||
# CBU-99 (It's a bomb made in 1968, I'm not bothering right now with backups)
|
# CBU-99 (It's a bomb made in 1968, I'm not bothering right now with backups)
|
||||||
|
|
||||||
# CBU-103
|
# CBU-103
|
||||||
(Weapons.BRU_57___2_x_CBU_103, None), # doesn't exist...
|
(Weapons.BRU_57___2_x_CBU_103, None), # doesn't exist...
|
||||||
(Weapons.CBU_103, Weapons.CBU_87),
|
(Weapons.CBU_103, Weapons.CBU_87),
|
||||||
|
|
||||||
# CBU-105
|
# CBU-105
|
||||||
(Weapons.BRU_57___2_x_CBU_105, None), # doesn't exist...
|
(Weapons.BRU_57___2_x_CBU_105, None), # doesn't exist...
|
||||||
(Weapons.CBU_105, Weapons.CBU_97),
|
(Weapons.CBU_105, Weapons.CBU_97),
|
||||||
|
(
|
||||||
(Weapons.LAU_131_pod___7_x_2_75__Hydra___Laser_Guided_Rkts_M151___HE_APKWS, Weapons.LAU_131___7_2_75__rockets_M151__HE_),
|
Weapons.LAU_131_pod___7_x_2_75__Hydra___Laser_Guided_Rkts_M151___HE_APKWS,
|
||||||
(Weapons.LAU_131_pod___7_x_2_75__Hydra___Laser_Guided_Rkts_M282___MPP_APKWS, Weapons.LAU_131___7_2_75__rockets_M151__HE_),
|
Weapons.LAU_131___7_2_75__rockets_M151__HE_,
|
||||||
(Weapons._3_x_LAU_131_pods___21_x_2_75__Hydra___Laser_Guided_M151___HE_APKWS, Weapons.LAU_68_3___7_2_75__rockets_M151__HE_),
|
),
|
||||||
(Weapons._3_x_LAU_131_pods___21_x_2_75__Hydra___Laser_Guided_M282___MPP_APKWS, Weapons.LAU_68_3___7_2_75__rockets_M151__HE_),
|
(
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75__Hydra___Laser_Guided_Rkts_M282___MPP_APKWS,
|
||||||
|
Weapons.LAU_131___7_2_75__rockets_M151__HE_,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Weapons._3_x_LAU_131_pods___21_x_2_75__Hydra___Laser_Guided_M151___HE_APKWS,
|
||||||
|
Weapons.LAU_68_3___7_2_75__rockets_M151__HE_,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Weapons._3_x_LAU_131_pods___21_x_2_75__Hydra___Laser_Guided_M282___MPP_APKWS,
|
||||||
|
Weapons.LAU_68_3___7_2_75__rockets_M151__HE_,
|
||||||
|
),
|
||||||
# Russia
|
# Russia
|
||||||
# KAB-1500
|
# KAB-1500
|
||||||
(Weapons.KAB_1500Kr, None),
|
(Weapons.KAB_1500Kr, None),
|
||||||
(Weapons.KAB_1500LG_Pr, Weapons.KAB_1500Kr),
|
(Weapons.KAB_1500LG_Pr, Weapons.KAB_1500Kr),
|
||||||
(Weapons.KAB_1500L, Weapons.KAB_1500LG_Pr),
|
(Weapons.KAB_1500L, Weapons.KAB_1500LG_Pr),
|
||||||
|
|
||||||
# KAB-500
|
# KAB-500
|
||||||
(Weapons.KAB_500kr, Weapons.FAB_500_M62),
|
(Weapons.KAB_500kr, Weapons.FAB_500_M62),
|
||||||
(Weapons.KAB_500L, Weapons.KAB_500kr),
|
(Weapons.KAB_500L, Weapons.KAB_500kr),
|
||||||
(Weapons.KAB_500S, Weapons.KAB_500L),
|
(Weapons.KAB_500S, Weapons.KAB_500L),
|
||||||
|
|
||||||
# KH Series
|
# KH Series
|
||||||
(Weapons.Kh_22N, None),
|
(Weapons.Kh_22N, None),
|
||||||
(Weapons.Kh_23L, None),
|
(Weapons.Kh_23L, None),
|
||||||
|
|
||||||
(Weapons.Kh_25ML, None),
|
(Weapons.Kh_25ML, None),
|
||||||
(Weapons.Kh_25ML_, None),
|
(Weapons.Kh_25ML_, None),
|
||||||
(Weapons.Kh_25ML__, None),
|
(Weapons.Kh_25ML__, None),
|
||||||
|
|
||||||
(Weapons.Kh_25MP, None),
|
(Weapons.Kh_25MP, None),
|
||||||
(Weapons.Kh_25MPU, Weapons.Kh_25MP),
|
(Weapons.Kh_25MPU, Weapons.Kh_25MP),
|
||||||
|
|
||||||
(Weapons.Kh_25MR, None),
|
(Weapons.Kh_25MR, None),
|
||||||
(Weapons.Kh_25MR_, None),
|
(Weapons.Kh_25MR_, None),
|
||||||
|
|
||||||
(Weapons.Kh_28__AS_9_Kyle_, None),
|
(Weapons.Kh_28__AS_9_Kyle_, None),
|
||||||
|
|
||||||
(Weapons.Kh_29L, Weapons.Kh_25ML),
|
(Weapons.Kh_29L, Weapons.Kh_25ML),
|
||||||
(Weapons.Kh_29L_, Weapons.Kh_25ML_),
|
(Weapons.Kh_29L_, Weapons.Kh_25ML_),
|
||||||
(Weapons.Kh_29L__, Weapons.Kh_25ML__),
|
(Weapons.Kh_29L__, Weapons.Kh_25ML__),
|
||||||
(Weapons.Kh_29T, Weapons.Kh_25MR),
|
(Weapons.Kh_29T, Weapons.Kh_25MR),
|
||||||
(Weapons.Kh_29T_, Weapons.Kh_25MR_),
|
(Weapons.Kh_29T_, Weapons.Kh_25MR_),
|
||||||
(Weapons.Kh_29T_, Weapons.Kh_25MR_),
|
(Weapons.Kh_29T_, Weapons.Kh_25MR_),
|
||||||
|
|
||||||
(Weapons.Kh_31A, None),
|
(Weapons.Kh_31A, None),
|
||||||
(Weapons.Kh_31A_, None),
|
(Weapons.Kh_31A_, None),
|
||||||
(Weapons.Kh_31A__, None),
|
(Weapons.Kh_31A__, None),
|
||||||
(Weapons.Kh_31P, Weapons.Kh_25MP),
|
(Weapons.Kh_31P, Weapons.Kh_25MP),
|
||||||
(Weapons.Kh_31P_, Weapons.Kh_25MP),
|
(Weapons.Kh_31P_, Weapons.Kh_25MP),
|
||||||
(Weapons.Kh_31P__, Weapons.Kh_25MP),
|
(Weapons.Kh_31P__, Weapons.Kh_25MP),
|
||||||
|
|
||||||
(Weapons.Kh_35, Weapons.Kh_31A),
|
(Weapons.Kh_35, Weapons.Kh_31A),
|
||||||
(Weapons.Kh_35_, Weapons.Kh_31A_),
|
(Weapons.Kh_35_, Weapons.Kh_31A_),
|
||||||
(Weapons.Kh_35_6, None),
|
(Weapons.Kh_35_6, None),
|
||||||
|
|
||||||
(Weapons.Kh_41, None),
|
(Weapons.Kh_41, None),
|
||||||
|
|
||||||
(Weapons.Kh_58U, Weapons.Kh_31P),
|
(Weapons.Kh_58U, Weapons.Kh_31P),
|
||||||
(Weapons.Kh_58U_, Weapons.Kh_31P_),
|
(Weapons.Kh_58U_, Weapons.Kh_31P_),
|
||||||
|
|
||||||
(Weapons.Kh_59M, Weapons.Kh_31A),
|
(Weapons.Kh_59M, Weapons.Kh_31A),
|
||||||
|
|
||||||
(Weapons.Kh_65, None),
|
(Weapons.Kh_65, None),
|
||||||
(Weapons.Kh_65_6, None),
|
(Weapons.Kh_65_6, None),
|
||||||
(Weapons.Kh_65_8, None),
|
(Weapons.Kh_65_8, None),
|
||||||
|
|
||||||
(Weapons.Kh_66_Grom__21__APU_68, None),
|
(Weapons.Kh_66_Grom__21__APU_68, None),
|
||||||
|
|
||||||
# ECM
|
# ECM
|
||||||
(Weapons.L175V_Khibiny_ECM_pod, None),
|
(Weapons.L175V_Khibiny_ECM_pod, None),
|
||||||
|
|
||||||
# R-13
|
# R-13
|
||||||
(Weapons.R_13M, None),
|
(Weapons.R_13M, None),
|
||||||
(Weapons.R_13M1, Weapons.R_13M),
|
(Weapons.R_13M1, Weapons.R_13M),
|
||||||
|
|
||||||
# R-24
|
# R-24
|
||||||
(Weapons.R_24R, None),
|
(Weapons.R_24R, None),
|
||||||
(Weapons.R_24T, None),
|
(Weapons.R_24T, None),
|
||||||
|
|
||||||
# R-27
|
# R-27
|
||||||
(Weapons.R_27T, Weapons.R_24T),
|
(Weapons.R_27T, Weapons.R_24T),
|
||||||
(Weapons.R_27R, Weapons.R_24R),
|
(Weapons.R_27R, Weapons.R_24R),
|
||||||
(Weapons.R_27ER, Weapons.R_27R),
|
(Weapons.R_27ER, Weapons.R_27R),
|
||||||
(Weapons.R_27ET, Weapons.R_27T),
|
(Weapons.R_27ET, Weapons.R_27T),
|
||||||
|
|
||||||
# R-33
|
# R-33
|
||||||
(Weapons.R_33, None),
|
(Weapons.R_33, None),
|
||||||
|
|
||||||
# R-3
|
# R-3
|
||||||
(Weapons.R_3S, Weapons.R_13M),
|
(Weapons.R_3S, Weapons.R_13M),
|
||||||
(Weapons.R_3R, Weapons.R_3S),
|
(Weapons.R_3R, Weapons.R_3S),
|
||||||
|
|
||||||
# R-40
|
# R-40
|
||||||
(Weapons.R_40R, None),
|
(Weapons.R_40R, None),
|
||||||
(Weapons.R_40T, None),
|
(Weapons.R_40T, None),
|
||||||
|
|
||||||
# R-55
|
# R-55
|
||||||
(Weapons.R_55, None),
|
(Weapons.R_55, None),
|
||||||
(Weapons.RS2US, None),
|
(Weapons.RS2US, None),
|
||||||
|
|
||||||
# R-60
|
# R-60
|
||||||
(Weapons.R_60, Weapons.R_13M1),
|
(Weapons.R_60, Weapons.R_13M1),
|
||||||
(Weapons.R_60_x_2, Weapons.R_13M1),
|
(Weapons.R_60_x_2, Weapons.R_13M1),
|
||||||
(Weapons.R_60_x_2_, Weapons.R_13M1),
|
(Weapons.R_60_x_2_, Weapons.R_13M1),
|
||||||
|
|
||||||
(Weapons.APU_60_1_R_60M, Weapons.R_3S),
|
(Weapons.APU_60_1_R_60M, Weapons.R_3S),
|
||||||
(Weapons.R_60M, Weapons.R_60),
|
(Weapons.R_60M, Weapons.R_60),
|
||||||
(Weapons.R_60M_, Weapons.R_60),
|
(Weapons.R_60M_, Weapons.R_60),
|
||||||
@ -509,44 +460,39 @@ _WEAPON_FALLBACKS = [
|
|||||||
(Weapons.R_60M_2_, Weapons.R_60M),
|
(Weapons.R_60M_2_, Weapons.R_60M),
|
||||||
(Weapons.R_60M_x_2, Weapons.R_60M),
|
(Weapons.R_60M_x_2, Weapons.R_60M),
|
||||||
(Weapons.R_60M_x_2_, Weapons.R_60M),
|
(Weapons.R_60M_x_2_, Weapons.R_60M),
|
||||||
|
|
||||||
# R-73
|
# R-73
|
||||||
(Weapons.R_73, Weapons.R_60M),
|
(Weapons.R_73, Weapons.R_60M),
|
||||||
(Weapons.R_73_, None),
|
(Weapons.R_73_, None),
|
||||||
|
|
||||||
# R-77
|
# R-77
|
||||||
(Weapons.R_77, Weapons.R_27ER),
|
(Weapons.R_77, Weapons.R_27ER),
|
||||||
(Weapons.R_77_, None),
|
(Weapons.R_77_, None),
|
||||||
|
|
||||||
# UK
|
# UK
|
||||||
# ALARM
|
# ALARM
|
||||||
(Weapons.ALARM, None),
|
(Weapons.ALARM, None),
|
||||||
(Weapons.ALARM_2, None),
|
(Weapons.ALARM_2, None),
|
||||||
|
|
||||||
# France
|
# France
|
||||||
# BLG-66 Belouga
|
# BLG-66 Belouga
|
||||||
(Weapons.AUF2_BLG_66_AC_x_2, Weapons.AUF2_MK_82_x_2),
|
(Weapons.AUF2_BLG_66_AC_x_2, Weapons.AUF2_MK_82_x_2),
|
||||||
(Weapons.BLG_66_AC_Belouga, Weapons.Mk_82),
|
(Weapons.BLG_66_AC_Belouga, Weapons.Mk_82),
|
||||||
(Weapons.BLG_66_AC_Belouga_, Weapons.Mk_82),
|
(Weapons.BLG_66_AC_Belouga_, Weapons.Mk_82),
|
||||||
|
|
||||||
# HOT-3
|
# HOT-3
|
||||||
(Weapons.HOT3, None),
|
(Weapons.HOT3, None),
|
||||||
(Weapons.HOT3_, None),
|
(Weapons.HOT3_, None),
|
||||||
|
|
||||||
# Magic 2
|
# Magic 2
|
||||||
(Weapons.Matra_Magic_II, None),
|
(Weapons.Matra_Magic_II, None),
|
||||||
(Weapons.R_550_Magic_2, None),
|
(Weapons.R_550_Magic_2, None),
|
||||||
|
|
||||||
# Super 530D
|
# Super 530D
|
||||||
(Weapons.Matra_Super_530D, Weapons.Matra_Magic_II),
|
(Weapons.Matra_Super_530D, Weapons.Matra_Magic_II),
|
||||||
(Weapons.Super_530D, None)
|
(Weapons.Super_530D, None),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
WEAPON_FALLBACK_MAP: Dict[Weapon, Optional[Weapon]] = defaultdict(
|
WEAPON_FALLBACK_MAP: Dict[Weapon, Optional[Weapon]] = defaultdict(
|
||||||
lambda: cast(Optional[Weapon], None),
|
lambda: cast(Optional[Weapon], None),
|
||||||
((Weapon.from_pydcs(a), b if b is None else Weapon.from_pydcs(b))
|
(
|
||||||
for a, b in _WEAPON_FALLBACKS))
|
(Weapon.from_pydcs(a), b if b is None else Weapon.from_pydcs(b))
|
||||||
|
for a, b in _WEAPON_FALLBACKS
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
WEAPON_INTRODUCTION_YEARS = {
|
WEAPON_INTRODUCTION_YEARS = {
|
||||||
@ -556,44 +502,34 @@ WEAPON_INTRODUCTION_YEARS = {
|
|||||||
Weapon.from_pydcs(Weapons.ADM_141A_): 1987,
|
Weapon.from_pydcs(Weapons.ADM_141A_): 1987,
|
||||||
Weapon.from_pydcs(Weapons.ADM_141A__): 1987,
|
Weapon.from_pydcs(Weapons.ADM_141A__): 1987,
|
||||||
Weapon.from_pydcs(Weapons.ADM_141B): 1987,
|
Weapon.from_pydcs(Weapons.ADM_141B): 1987,
|
||||||
|
|
||||||
# AGM-114K Hellfire
|
# AGM-114K Hellfire
|
||||||
Weapon.from_pydcs(Weapons.AGM114x2_OH_58): 1993,
|
Weapon.from_pydcs(Weapons.AGM114x2_OH_58): 1993,
|
||||||
Weapon.from_pydcs(Weapons.AGM_114K): 1993,
|
Weapon.from_pydcs(Weapons.AGM_114K): 1993,
|
||||||
Weapon.from_pydcs(Weapons.AGM_114K___4): 1993,
|
Weapon.from_pydcs(Weapons.AGM_114K___4): 1993,
|
||||||
|
|
||||||
# AGM-119 Penguin
|
# AGM-119 Penguin
|
||||||
Weapon.from_pydcs(Weapons.AGM_119B_Penguin): 1972,
|
Weapon.from_pydcs(Weapons.AGM_119B_Penguin): 1972,
|
||||||
|
|
||||||
# AGM-122 Sidearm
|
# AGM-122 Sidearm
|
||||||
Weapon.from_pydcs(Weapons.AGM_122): 1986,
|
Weapon.from_pydcs(Weapons.AGM_122): 1986,
|
||||||
Weapon.from_pydcs(Weapons.AGM_122_Sidearm): 1986,
|
Weapon.from_pydcs(Weapons.AGM_122_Sidearm): 1986,
|
||||||
Weapon.from_pydcs(Weapons.AGM_122_Sidearm_): 1986,
|
Weapon.from_pydcs(Weapons.AGM_122_Sidearm_): 1986,
|
||||||
|
|
||||||
# AGM-154 JSOW
|
# AGM-154 JSOW
|
||||||
Weapon.from_pydcs(Weapons.AGM_154A): 1998,
|
Weapon.from_pydcs(Weapons.AGM_154A): 1998,
|
||||||
Weapon.from_pydcs(Weapons.BRU_55___2_x_AGM_154A): 1998,
|
Weapon.from_pydcs(Weapons.BRU_55___2_x_AGM_154A): 1998,
|
||||||
Weapon.from_pydcs(Weapons.BRU_57___2_x_AGM_154A): 1998,
|
Weapon.from_pydcs(Weapons.BRU_57___2_x_AGM_154A): 1998,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.AGM_154B): 2005,
|
Weapon.from_pydcs(Weapons.AGM_154B): 2005,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.AGM_154C): 2005,
|
Weapon.from_pydcs(Weapons.AGM_154C): 2005,
|
||||||
Weapon.from_pydcs(Weapons.AGM_154C_4): 2005,
|
Weapon.from_pydcs(Weapons.AGM_154C_4): 2005,
|
||||||
Weapon.from_pydcs(Weapons.BRU_55___2_x_AGM_154C): 2005,
|
Weapon.from_pydcs(Weapons.BRU_55___2_x_AGM_154C): 2005,
|
||||||
|
|
||||||
# AGM-45 Shrike
|
# AGM-45 Shrike
|
||||||
Weapon.from_pydcs(Weapons.AGM_45A): 1965,
|
Weapon.from_pydcs(Weapons.AGM_45A): 1965,
|
||||||
Weapon.from_pydcs(Weapons.AGM_45B): 1970,
|
Weapon.from_pydcs(Weapons.AGM_45B): 1970,
|
||||||
Weapon.from_pydcs(Weapons.AGM_45B_): 1970,
|
Weapon.from_pydcs(Weapons.AGM_45B_): 1970,
|
||||||
|
|
||||||
# AGM-62 Walleye
|
# AGM-62 Walleye
|
||||||
Weapon.from_pydcs(Weapons.AGM_62): 1972,
|
Weapon.from_pydcs(Weapons.AGM_62): 1972,
|
||||||
|
|
||||||
# AGM-65 Maverick
|
# AGM-65 Maverick
|
||||||
Weapon.from_pydcs(Weapons.AGM_65D): 1983,
|
Weapon.from_pydcs(Weapons.AGM_65D): 1983,
|
||||||
Weapon.from_pydcs(Weapons.AGM_65E): 1985,
|
Weapon.from_pydcs(Weapons.AGM_65E): 1985,
|
||||||
Weapon.from_pydcs(Weapons.AGM_65K): 2007,
|
Weapon.from_pydcs(Weapons.AGM_65K): 2007,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.LAU_117_AGM_65A): 1972,
|
Weapon.from_pydcs(Weapons.LAU_117_AGM_65A): 1972,
|
||||||
Weapon.from_pydcs(Weapons.LAU_117_AGM_65B): 1972,
|
Weapon.from_pydcs(Weapons.LAU_117_AGM_65B): 1972,
|
||||||
Weapon.from_pydcs(Weapons.LAU_117_AGM_65D): 1986,
|
Weapon.from_pydcs(Weapons.LAU_117_AGM_65D): 1986,
|
||||||
@ -603,25 +539,20 @@ WEAPON_INTRODUCTION_YEARS = {
|
|||||||
Weapon.from_pydcs(Weapons.LAU_117_AGM_65H): 2002,
|
Weapon.from_pydcs(Weapons.LAU_117_AGM_65H): 2002,
|
||||||
Weapon.from_pydcs(Weapons.LAU_117_AGM_65K): 2002,
|
Weapon.from_pydcs(Weapons.LAU_117_AGM_65K): 2002,
|
||||||
Weapon.from_pydcs(Weapons.LAU_117_AGM_65L): 1985,
|
Weapon.from_pydcs(Weapons.LAU_117_AGM_65L): 1985,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.LAU_88_AGM_65D_2): 1983,
|
Weapon.from_pydcs(Weapons.LAU_88_AGM_65D_2): 1983,
|
||||||
Weapon.from_pydcs(Weapons.LAU_88_AGM_65D_2_): 1983,
|
Weapon.from_pydcs(Weapons.LAU_88_AGM_65D_2_): 1983,
|
||||||
Weapon.from_pydcs(Weapons.LAU_88_AGM_65D_3): 1983,
|
Weapon.from_pydcs(Weapons.LAU_88_AGM_65D_3): 1983,
|
||||||
Weapon.from_pydcs(Weapons.LAU_88_AGM_65D_ONE): 1983,
|
Weapon.from_pydcs(Weapons.LAU_88_AGM_65D_ONE): 1983,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.LAU_88_AGM_65E_2): 1985,
|
Weapon.from_pydcs(Weapons.LAU_88_AGM_65E_2): 1985,
|
||||||
Weapon.from_pydcs(Weapons.LAU_88_AGM_65E_2_): 1985,
|
Weapon.from_pydcs(Weapons.LAU_88_AGM_65E_2_): 1985,
|
||||||
Weapon.from_pydcs(Weapons.LAU_88_AGM_65E_3): 1985,
|
Weapon.from_pydcs(Weapons.LAU_88_AGM_65E_3): 1985,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.LAU_88_AGM_65H): 2007,
|
Weapon.from_pydcs(Weapons.LAU_88_AGM_65H): 2007,
|
||||||
Weapon.from_pydcs(Weapons.LAU_88_AGM_65H_2_L): 2007,
|
Weapon.from_pydcs(Weapons.LAU_88_AGM_65H_2_L): 2007,
|
||||||
Weapon.from_pydcs(Weapons.LAU_88_AGM_65H_2_R): 2007,
|
Weapon.from_pydcs(Weapons.LAU_88_AGM_65H_2_R): 2007,
|
||||||
Weapon.from_pydcs(Weapons.LAU_88_AGM_65H_3): 2007,
|
Weapon.from_pydcs(Weapons.LAU_88_AGM_65H_3): 2007,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.LAU_88_AGM_65K_2): 2007,
|
Weapon.from_pydcs(Weapons.LAU_88_AGM_65K_2): 2007,
|
||||||
Weapon.from_pydcs(Weapons.LAU_88_AGM_65K_2_): 2007,
|
Weapon.from_pydcs(Weapons.LAU_88_AGM_65K_2_): 2007,
|
||||||
Weapon.from_pydcs(Weapons.LAU_88_AGM_65K_3): 2007,
|
Weapon.from_pydcs(Weapons.LAU_88_AGM_65K_3): 2007,
|
||||||
|
|
||||||
# AGM-84 Harpoon
|
# AGM-84 Harpoon
|
||||||
Weapon.from_pydcs(Weapons.AGM_84): 1979,
|
Weapon.from_pydcs(Weapons.AGM_84): 1979,
|
||||||
Weapon.from_pydcs(Weapons.AGM_84A): 1979,
|
Weapon.from_pydcs(Weapons.AGM_84A): 1979,
|
||||||
@ -630,41 +561,33 @@ WEAPON_INTRODUCTION_YEARS = {
|
|||||||
Weapon.from_pydcs(Weapons.AGM_84E): 1990,
|
Weapon.from_pydcs(Weapons.AGM_84E): 1990,
|
||||||
Weapon.from_pydcs(Weapons.AGM_84E_SLAM): 1990,
|
Weapon.from_pydcs(Weapons.AGM_84E_SLAM): 1990,
|
||||||
Weapon.from_pydcs(Weapons.AGM_84H): 1998,
|
Weapon.from_pydcs(Weapons.AGM_84H): 1998,
|
||||||
|
|
||||||
# AGM-86 ALCM
|
# AGM-86 ALCM
|
||||||
Weapon.from_pydcs(Weapons.AGM_86C): 1986,
|
Weapon.from_pydcs(Weapons.AGM_86C): 1986,
|
||||||
Weapon.from_pydcs(Weapons.AGM_86C_20): 1986,
|
Weapon.from_pydcs(Weapons.AGM_86C_20): 1986,
|
||||||
Weapon.from_pydcs(Weapons.AGM_86C_8): 1986,
|
Weapon.from_pydcs(Weapons.AGM_86C_8): 1986,
|
||||||
Weapon.from_pydcs(Weapons.MER_6_AGM_86C): 1986,
|
Weapon.from_pydcs(Weapons.MER_6_AGM_86C): 1986,
|
||||||
|
|
||||||
# AGM-88 HARM
|
# AGM-88 HARM
|
||||||
Weapon.from_pydcs(Weapons.AGM_88C): 1983,
|
Weapon.from_pydcs(Weapons.AGM_88C): 1983,
|
||||||
Weapon.from_pydcs(Weapons.AGM_88C_): 1983,
|
Weapon.from_pydcs(Weapons.AGM_88C_): 1983,
|
||||||
# for future reference: 1983 is the A model IOC. B model in 1986 and C model in 1994.
|
# for future reference: 1983 is the A model IOC. B model in 1986 and C model in 1994.
|
||||||
|
|
||||||
# AIM-120 AMRAAM
|
# AIM-120 AMRAAM
|
||||||
Weapon.from_pydcs(Weapons.AIM_120B): 1994,
|
Weapon.from_pydcs(Weapons.AIM_120B): 1994,
|
||||||
Weapon.from_pydcs(Weapons.AIM_120C): 1996,
|
Weapon.from_pydcs(Weapons.AIM_120C): 1996,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.LAU_115_2_LAU_127_AIM_120B): 1994,
|
Weapon.from_pydcs(Weapons.LAU_115_2_LAU_127_AIM_120B): 1994,
|
||||||
Weapon.from_pydcs(Weapons.LAU_115___AIM_120B): 1994,
|
Weapon.from_pydcs(Weapons.LAU_115___AIM_120B): 1994,
|
||||||
Weapon.from_pydcs(Weapons.LAU_115_2_LAU_127_AIM_120C): 1996,
|
Weapon.from_pydcs(Weapons.LAU_115_2_LAU_127_AIM_120C): 1996,
|
||||||
Weapon.from_pydcs(Weapons.LAU_115___AIM_120C): 1996,
|
Weapon.from_pydcs(Weapons.LAU_115___AIM_120C): 1996,
|
||||||
|
|
||||||
# AIM-54 Phoenix
|
# AIM-54 Phoenix
|
||||||
Weapon.from_pydcs(Weapons.AIM_54A_Mk47): 1974,
|
Weapon.from_pydcs(Weapons.AIM_54A_Mk47): 1974,
|
||||||
Weapon.from_pydcs(Weapons.AIM_54A_Mk47_): 1974,
|
Weapon.from_pydcs(Weapons.AIM_54A_Mk47_): 1974,
|
||||||
Weapon.from_pydcs(Weapons.AIM_54A_Mk47__): 1974,
|
Weapon.from_pydcs(Weapons.AIM_54A_Mk47__): 1974,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.AIM_54A_Mk60): 1974,
|
Weapon.from_pydcs(Weapons.AIM_54A_Mk60): 1974,
|
||||||
Weapon.from_pydcs(Weapons.AIM_54A_Mk60_): 1974,
|
Weapon.from_pydcs(Weapons.AIM_54A_Mk60_): 1974,
|
||||||
Weapon.from_pydcs(Weapons.AIM_54A_Mk60__): 1974,
|
Weapon.from_pydcs(Weapons.AIM_54A_Mk60__): 1974,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.AIM_54C): 1974,
|
Weapon.from_pydcs(Weapons.AIM_54C): 1974,
|
||||||
Weapon.from_pydcs(Weapons.AIM_54C_Mk47): 1974,
|
Weapon.from_pydcs(Weapons.AIM_54C_Mk47): 1974,
|
||||||
Weapon.from_pydcs(Weapons.AIM_54C_Mk47_): 1974,
|
Weapon.from_pydcs(Weapons.AIM_54C_Mk47_): 1974,
|
||||||
Weapon.from_pydcs(Weapons.AIM_54C_Mk47__): 1974,
|
Weapon.from_pydcs(Weapons.AIM_54C_Mk47__): 1974,
|
||||||
|
|
||||||
# AIM-7 Sparrow
|
# AIM-7 Sparrow
|
||||||
Weapon.from_pydcs(Weapons.AIM_7E): 1963,
|
Weapon.from_pydcs(Weapons.AIM_7E): 1963,
|
||||||
Weapon.from_pydcs(Weapons.AIM_7F): 1976,
|
Weapon.from_pydcs(Weapons.AIM_7F): 1976,
|
||||||
@ -676,62 +599,49 @@ WEAPON_INTRODUCTION_YEARS = {
|
|||||||
Weapon.from_pydcs(Weapons.AIM_7MH): 1987,
|
Weapon.from_pydcs(Weapons.AIM_7MH): 1987,
|
||||||
Weapon.from_pydcs(Weapons.AIM_7MH_): 1987,
|
Weapon.from_pydcs(Weapons.AIM_7MH_): 1987,
|
||||||
Weapon.from_pydcs(Weapons.AIM_7MH__): 1987,
|
Weapon.from_pydcs(Weapons.AIM_7MH__): 1987,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.LAU_115C_AIM_7E): 1963,
|
Weapon.from_pydcs(Weapons.LAU_115C_AIM_7E): 1963,
|
||||||
Weapon.from_pydcs(Weapons.LAU_115C_AIM_7F): 1976,
|
Weapon.from_pydcs(Weapons.LAU_115C_AIM_7F): 1976,
|
||||||
Weapon.from_pydcs(Weapons.LAU_115___AIM_7M): 1982,
|
Weapon.from_pydcs(Weapons.LAU_115___AIM_7M): 1982,
|
||||||
Weapon.from_pydcs(Weapons.LAU_115C_AIM_7MH): 1987,
|
Weapon.from_pydcs(Weapons.LAU_115C_AIM_7MH): 1987,
|
||||||
|
|
||||||
# AIM-9 Sidewinder
|
# AIM-9 Sidewinder
|
||||||
Weapon.from_pydcs(Weapons.AIM_9L_Sidewinder_IR_AAM): 1977,
|
Weapon.from_pydcs(Weapons.AIM_9L_Sidewinder_IR_AAM): 1977,
|
||||||
Weapon.from_pydcs(Weapons.AIM_9M_Sidewinder_IR_AAM): 1982,
|
Weapon.from_pydcs(Weapons.AIM_9M_Sidewinder_IR_AAM): 1982,
|
||||||
Weapon.from_pydcs(Weapons.AIM_9P5_Sidewinder_IR_AAM): 1980,
|
Weapon.from_pydcs(Weapons.AIM_9P5_Sidewinder_IR_AAM): 1980,
|
||||||
Weapon.from_pydcs(Weapons.AIM_9P_Sidewinder_IR_AAM): 1978,
|
Weapon.from_pydcs(Weapons.AIM_9P_Sidewinder_IR_AAM): 1978,
|
||||||
Weapon.from_pydcs(Weapons.AIM_9X_Sidewinder_IR_AAM): 2003,
|
Weapon.from_pydcs(Weapons.AIM_9X_Sidewinder_IR_AAM): 2003,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.LAU_105_1_AIM_9L_L): 1977,
|
Weapon.from_pydcs(Weapons.LAU_105_1_AIM_9L_L): 1977,
|
||||||
Weapon.from_pydcs(Weapons.LAU_105_1_AIM_9L_R): 1977,
|
Weapon.from_pydcs(Weapons.LAU_105_1_AIM_9L_R): 1977,
|
||||||
Weapon.from_pydcs(Weapons.LAU_105_1_AIM_9M_L): 1982,
|
Weapon.from_pydcs(Weapons.LAU_105_1_AIM_9M_L): 1982,
|
||||||
Weapon.from_pydcs(Weapons.LAU_105_1_AIM_9M_R): 1982,
|
Weapon.from_pydcs(Weapons.LAU_105_1_AIM_9M_R): 1982,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.LAU_105_2_AIM_9L): 1977,
|
Weapon.from_pydcs(Weapons.LAU_105_2_AIM_9L): 1977,
|
||||||
Weapon.from_pydcs(Weapons.LAU_105_2_AIM_9P5): 1980,
|
Weapon.from_pydcs(Weapons.LAU_105_2_AIM_9P5): 1980,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.LAU_105___2_AIM_9M_Sidewinder_IR_AAM): 1982,
|
Weapon.from_pydcs(Weapons.LAU_105___2_AIM_9M_Sidewinder_IR_AAM): 1982,
|
||||||
Weapon.from_pydcs(Weapons.LAU_105___2_AIM_9P_Sidewinder_IR_AAM): 1978,
|
Weapon.from_pydcs(Weapons.LAU_105___2_AIM_9P_Sidewinder_IR_AAM): 1978,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.LAU_115_2_LAU_127_AIM_9L): 1977,
|
Weapon.from_pydcs(Weapons.LAU_115_2_LAU_127_AIM_9L): 1977,
|
||||||
Weapon.from_pydcs(Weapons.LAU_115_2_LAU_127_AIM_9M): 1982,
|
Weapon.from_pydcs(Weapons.LAU_115_2_LAU_127_AIM_9M): 1982,
|
||||||
Weapon.from_pydcs(Weapons.LAU_115_2_LAU_127_AIM_9X): 2003,
|
Weapon.from_pydcs(Weapons.LAU_115_2_LAU_127_AIM_9X): 2003,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.LAU_115_LAU_127_AIM_9L): 1977,
|
Weapon.from_pydcs(Weapons.LAU_115_LAU_127_AIM_9L): 1977,
|
||||||
Weapon.from_pydcs(Weapons.LAU_115_LAU_127_AIM_9M): 1982,
|
Weapon.from_pydcs(Weapons.LAU_115_LAU_127_AIM_9M): 1982,
|
||||||
Weapon.from_pydcs(Weapons.LAU_115_LAU_127_AIM_9X): 2003,
|
Weapon.from_pydcs(Weapons.LAU_115_LAU_127_AIM_9X): 2003,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.LAU_127_AIM_9L): 1977,
|
Weapon.from_pydcs(Weapons.LAU_127_AIM_9L): 1977,
|
||||||
Weapon.from_pydcs(Weapons.LAU_127_AIM_9M): 1982,
|
Weapon.from_pydcs(Weapons.LAU_127_AIM_9M): 1982,
|
||||||
Weapon.from_pydcs(Weapons.LAU_127_AIM_9X): 2003,
|
Weapon.from_pydcs(Weapons.LAU_127_AIM_9X): 2003,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.LAU_138_AIM_9L): 1977,
|
Weapon.from_pydcs(Weapons.LAU_138_AIM_9L): 1977,
|
||||||
Weapon.from_pydcs(Weapons.LAU_138_AIM_9M): 1982,
|
Weapon.from_pydcs(Weapons.LAU_138_AIM_9M): 1982,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.LAU_7_AIM_9L): 1977,
|
Weapon.from_pydcs(Weapons.LAU_7_AIM_9L): 1977,
|
||||||
Weapon.from_pydcs(Weapons.LAU_7_AIM_9M): 1982,
|
Weapon.from_pydcs(Weapons.LAU_7_AIM_9M): 1982,
|
||||||
Weapon.from_pydcs(Weapons.LAU_7_AIM_9M_Sidewinder_IR_AAM): 1982,
|
Weapon.from_pydcs(Weapons.LAU_7_AIM_9M_Sidewinder_IR_AAM): 1982,
|
||||||
Weapon.from_pydcs(Weapons.LAU_7_AIM_9P5_Sidewinder_IR_AAM): 1980,
|
Weapon.from_pydcs(Weapons.LAU_7_AIM_9P5_Sidewinder_IR_AAM): 1980,
|
||||||
Weapon.from_pydcs(Weapons.LAU_7_AIM_9P_Sidewinder_IR_AAM): 1978,
|
Weapon.from_pydcs(Weapons.LAU_7_AIM_9P_Sidewinder_IR_AAM): 1978,
|
||||||
Weapon.from_pydcs(Weapons.LAU_7_AIM_9X_Sidewinder_IR_AAM): 2003,
|
Weapon.from_pydcs(Weapons.LAU_7_AIM_9X_Sidewinder_IR_AAM): 2003,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.LAU_7___2_AIM_9L_Sidewinder_IR_AAM): 1977,
|
Weapon.from_pydcs(Weapons.LAU_7___2_AIM_9L_Sidewinder_IR_AAM): 1977,
|
||||||
Weapon.from_pydcs(Weapons.LAU_7___2_AIM_9M_Sidewinder_IR_AAM): 1982,
|
Weapon.from_pydcs(Weapons.LAU_7___2_AIM_9M_Sidewinder_IR_AAM): 1982,
|
||||||
Weapon.from_pydcs(Weapons.LAU_7___2_AIM_9P5_Sidewinder_IR_AAM): 1980,
|
Weapon.from_pydcs(Weapons.LAU_7___2_AIM_9P5_Sidewinder_IR_AAM): 1980,
|
||||||
Weapon.from_pydcs(Weapons.LAU_7___2_AIM_9P_Sidewinder_IR_AAM): 1978,
|
Weapon.from_pydcs(Weapons.LAU_7___2_AIM_9P_Sidewinder_IR_AAM): 1978,
|
||||||
|
|
||||||
# ALQ ECM Pods
|
# ALQ ECM Pods
|
||||||
Weapon.from_pydcs(Weapons.ALQ_131): 1970,
|
Weapon.from_pydcs(Weapons.ALQ_131): 1970,
|
||||||
Weapon.from_pydcs(Weapons.ALQ_184): 1989,
|
Weapon.from_pydcs(Weapons.ALQ_184): 1989,
|
||||||
Weapon.from_pydcs(Weapons.AN_ALQ_164_DECM_Pod): 1984,
|
Weapon.from_pydcs(Weapons.AN_ALQ_164_DECM_Pod): 1984,
|
||||||
|
|
||||||
# TGP Pods
|
# TGP Pods
|
||||||
Weapon.from_pydcs(Weapons.AN_AAQ_28_LITENING): 1995,
|
Weapon.from_pydcs(Weapons.AN_AAQ_28_LITENING): 1995,
|
||||||
Weapon.from_pydcs(Weapons.AN_AAQ_28_LITENING_): 1995,
|
Weapon.from_pydcs(Weapons.AN_AAQ_28_LITENING_): 1995,
|
||||||
@ -742,17 +652,14 @@ WEAPON_INTRODUCTION_YEARS = {
|
|||||||
Weapon.from_pydcs(Weapons.Lantirn_F_16): 1985,
|
Weapon.from_pydcs(Weapons.Lantirn_F_16): 1985,
|
||||||
Weapon.from_pydcs(Weapons.Lantirn_Target_Pod): 1985,
|
Weapon.from_pydcs(Weapons.Lantirn_Target_Pod): 1985,
|
||||||
Weapon.from_pydcs(Weapons.Pavetack_F_111): 1982,
|
Weapon.from_pydcs(Weapons.Pavetack_F_111): 1982,
|
||||||
|
|
||||||
# BLU-107
|
# BLU-107
|
||||||
Weapon.from_pydcs(Weapons.BLU_107): 1983,
|
Weapon.from_pydcs(Weapons.BLU_107): 1983,
|
||||||
Weapon.from_pydcs(Weapons.MER_6_BLU_107): 1983,
|
Weapon.from_pydcs(Weapons.MER_6_BLU_107): 1983,
|
||||||
|
|
||||||
# GBU-10 LGB
|
# GBU-10 LGB
|
||||||
Weapon.from_pydcs(Weapons.DIS_GBU_10): 1976,
|
Weapon.from_pydcs(Weapons.DIS_GBU_10): 1976,
|
||||||
Weapon.from_pydcs(Weapons.GBU_10): 1976,
|
Weapon.from_pydcs(Weapons.GBU_10): 1976,
|
||||||
Weapon.from_pydcs(Weapons.GBU_10_): 1976,
|
Weapon.from_pydcs(Weapons.GBU_10_): 1976,
|
||||||
Weapon.from_pydcs(Weapons.GBU_10_2): 1976,
|
Weapon.from_pydcs(Weapons.GBU_10_2): 1976,
|
||||||
|
|
||||||
# GBU-12 LGB
|
# GBU-12 LGB
|
||||||
Weapon.from_pydcs(Weapons.AUF2_GBU_12_x_2): 1976,
|
Weapon.from_pydcs(Weapons.AUF2_GBU_12_x_2): 1976,
|
||||||
Weapon.from_pydcs(Weapons.BRU_33___2_x_GBU_12): 1976,
|
Weapon.from_pydcs(Weapons.BRU_33___2_x_GBU_12): 1976,
|
||||||
@ -770,10 +677,8 @@ WEAPON_INTRODUCTION_YEARS = {
|
|||||||
Weapon.from_pydcs(Weapons._2_GBU_12): 1976,
|
Weapon.from_pydcs(Weapons._2_GBU_12): 1976,
|
||||||
Weapon.from_pydcs(Weapons._2_GBU_12_): 1976,
|
Weapon.from_pydcs(Weapons._2_GBU_12_): 1976,
|
||||||
Weapon.from_pydcs(Weapons._3_GBU_12): 1976,
|
Weapon.from_pydcs(Weapons._3_GBU_12): 1976,
|
||||||
|
|
||||||
# GBU-15 LGB
|
# GBU-15 LGB
|
||||||
Weapon.from_pydcs(Weapons.GBU_15): 1975,
|
Weapon.from_pydcs(Weapons.GBU_15): 1975,
|
||||||
|
|
||||||
# GBU-16 LGB
|
# GBU-16 LGB
|
||||||
Weapon.from_pydcs(Weapons.BRU_33___2_x_GBU_16): 1976,
|
Weapon.from_pydcs(Weapons.BRU_33___2_x_GBU_16): 1976,
|
||||||
Weapon.from_pydcs(Weapons.DIS_GBU_16): 1976,
|
Weapon.from_pydcs(Weapons.DIS_GBU_16): 1976,
|
||||||
@ -783,20 +688,16 @@ WEAPON_INTRODUCTION_YEARS = {
|
|||||||
Weapon.from_pydcs(Weapons._2_GBU_16_): 1976,
|
Weapon.from_pydcs(Weapons._2_GBU_16_): 1976,
|
||||||
Weapon.from_pydcs(Weapons._3_GBU_16): 1976,
|
Weapon.from_pydcs(Weapons._3_GBU_16): 1976,
|
||||||
Weapon.from_pydcs(Weapons._3_GBU_16_): 1976,
|
Weapon.from_pydcs(Weapons._3_GBU_16_): 1976,
|
||||||
|
|
||||||
# GBU-24 LGB
|
# GBU-24 LGB
|
||||||
Weapon.from_pydcs(Weapons.GBU_24): 1986,
|
Weapon.from_pydcs(Weapons.GBU_24): 1986,
|
||||||
Weapon.from_pydcs(Weapons.GBU_24_): 1986,
|
Weapon.from_pydcs(Weapons.GBU_24_): 1986,
|
||||||
Weapon.from_pydcs(Weapons.GBU_24__): 1986,
|
Weapon.from_pydcs(Weapons.GBU_24__): 1986,
|
||||||
|
|
||||||
# GBU-27 LGB
|
# GBU-27 LGB
|
||||||
Weapon.from_pydcs(Weapons.GBU_27): 1991,
|
Weapon.from_pydcs(Weapons.GBU_27): 1991,
|
||||||
Weapon.from_pydcs(Weapons.GBU_27_2): 1991,
|
Weapon.from_pydcs(Weapons.GBU_27_2): 1991,
|
||||||
Weapon.from_pydcs(Weapons.GBU_27_4): 1991,
|
Weapon.from_pydcs(Weapons.GBU_27_4): 1991,
|
||||||
|
|
||||||
# GBU-28
|
# GBU-28
|
||||||
Weapon.from_pydcs(Weapons.GBU_28): 1991,
|
Weapon.from_pydcs(Weapons.GBU_28): 1991,
|
||||||
|
|
||||||
# GBU-31 JDAM
|
# GBU-31 JDAM
|
||||||
Weapon.from_pydcs(Weapons.GBU_31V3B_8): 2001,
|
Weapon.from_pydcs(Weapons.GBU_31V3B_8): 2001,
|
||||||
Weapon.from_pydcs(Weapons.GBU_31_8): 2001,
|
Weapon.from_pydcs(Weapons.GBU_31_8): 2001,
|
||||||
@ -804,10 +705,8 @@ WEAPON_INTRODUCTION_YEARS = {
|
|||||||
Weapon.from_pydcs(Weapons.GBU_31_V_2_B): 2001,
|
Weapon.from_pydcs(Weapons.GBU_31_V_2_B): 2001,
|
||||||
Weapon.from_pydcs(Weapons.GBU_31_V_3_B): 2001,
|
Weapon.from_pydcs(Weapons.GBU_31_V_3_B): 2001,
|
||||||
Weapon.from_pydcs(Weapons.GBU_31_V_4_B): 2001,
|
Weapon.from_pydcs(Weapons.GBU_31_V_4_B): 2001,
|
||||||
|
|
||||||
# GBU-32 JDAM
|
# GBU-32 JDAM
|
||||||
Weapon.from_pydcs(Weapons.GBU_32_V_2_B): 2002,
|
Weapon.from_pydcs(Weapons.GBU_32_V_2_B): 2002,
|
||||||
|
|
||||||
# GBU-38 JDAM
|
# GBU-38 JDAM
|
||||||
Weapon.from_pydcs(Weapons.BRU_55___2_x_GBU_38): 2005,
|
Weapon.from_pydcs(Weapons.BRU_55___2_x_GBU_38): 2005,
|
||||||
Weapon.from_pydcs(Weapons.BRU_57___2_x_GBU_38): 2005,
|
Weapon.from_pydcs(Weapons.BRU_57___2_x_GBU_38): 2005,
|
||||||
@ -816,53 +715,40 @@ WEAPON_INTRODUCTION_YEARS = {
|
|||||||
Weapon.from_pydcs(Weapons._2_GBU_38): 2005,
|
Weapon.from_pydcs(Weapons._2_GBU_38): 2005,
|
||||||
Weapon.from_pydcs(Weapons._2_GBU_38_): 2005,
|
Weapon.from_pydcs(Weapons._2_GBU_38_): 2005,
|
||||||
Weapon.from_pydcs(Weapons._3_GBU_38): 2005,
|
Weapon.from_pydcs(Weapons._3_GBU_38): 2005,
|
||||||
|
|
||||||
# GBU-54 LJDAM
|
# GBU-54 LJDAM
|
||||||
Weapon.from_pydcs(Weapons.GBU_54_V_1_B): 2008,
|
Weapon.from_pydcs(Weapons.GBU_54_V_1_B): 2008,
|
||||||
Weapon.from_pydcs(Weapons._2_GBU_54_V_1_B): 2008,
|
Weapon.from_pydcs(Weapons._2_GBU_54_V_1_B): 2008,
|
||||||
Weapon.from_pydcs(Weapons._2_GBU_54_V_1_B_): 2008,
|
Weapon.from_pydcs(Weapons._2_GBU_54_V_1_B_): 2008,
|
||||||
Weapon.from_pydcs(Weapons._3_GBU_54_V_1_B): 2008,
|
Weapon.from_pydcs(Weapons._3_GBU_54_V_1_B): 2008,
|
||||||
|
|
||||||
# CBU-52
|
# CBU-52
|
||||||
Weapon.from_pydcs(Weapons.CBU_52B): 1970,
|
Weapon.from_pydcs(Weapons.CBU_52B): 1970,
|
||||||
|
|
||||||
# CBU-87 CEM
|
# CBU-87 CEM
|
||||||
Weapon.from_pydcs(Weapons.CBU_87): 1986,
|
Weapon.from_pydcs(Weapons.CBU_87): 1986,
|
||||||
Weapon.from_pydcs(Weapons.TER_9A___2_x_CBU_87): 1986,
|
Weapon.from_pydcs(Weapons.TER_9A___2_x_CBU_87): 1986,
|
||||||
Weapon.from_pydcs(Weapons.TER_9A___2_x_CBU_87_): 1986,
|
Weapon.from_pydcs(Weapons.TER_9A___2_x_CBU_87_): 1986,
|
||||||
Weapon.from_pydcs(Weapons.TER_9A___3_x_CBU_87): 1986,
|
Weapon.from_pydcs(Weapons.TER_9A___3_x_CBU_87): 1986,
|
||||||
|
|
||||||
# CBU-97
|
# CBU-97
|
||||||
Weapon.from_pydcs(Weapons.CBU_97): 1992,
|
Weapon.from_pydcs(Weapons.CBU_97): 1992,
|
||||||
Weapon.from_pydcs(Weapons.TER_9A___2_x_CBU_97): 1992,
|
Weapon.from_pydcs(Weapons.TER_9A___2_x_CBU_97): 1992,
|
||||||
Weapon.from_pydcs(Weapons.TER_9A___2_x_CBU_97_): 1992,
|
Weapon.from_pydcs(Weapons.TER_9A___2_x_CBU_97_): 1992,
|
||||||
Weapon.from_pydcs(Weapons.TER_9A___3_x_CBU_97): 1992,
|
Weapon.from_pydcs(Weapons.TER_9A___3_x_CBU_97): 1992,
|
||||||
|
|
||||||
# CBU-99
|
# CBU-99
|
||||||
Weapon.from_pydcs(Weapons.BRU_33___2_x_CBU_99): 1968,
|
Weapon.from_pydcs(Weapons.BRU_33___2_x_CBU_99): 1968,
|
||||||
Weapon.from_pydcs(Weapons.CBU_99): 1968,
|
Weapon.from_pydcs(Weapons.CBU_99): 1968,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.BRU_33___2_x_Mk_20_Rockeye): 1968,
|
Weapon.from_pydcs(Weapons.BRU_33___2_x_Mk_20_Rockeye): 1968,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.DIS_MK_20): 1968,
|
Weapon.from_pydcs(Weapons.DIS_MK_20): 1968,
|
||||||
Weapon.from_pydcs(Weapons.DIS_MK_20_DUAL_L): 1968,
|
Weapon.from_pydcs(Weapons.DIS_MK_20_DUAL_L): 1968,
|
||||||
Weapon.from_pydcs(Weapons.DIS_MK_20_DUAL_R): 1968,
|
Weapon.from_pydcs(Weapons.DIS_MK_20_DUAL_R): 1968,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.HSAB_9_Mk_20_Rockeye): 1968,
|
Weapon.from_pydcs(Weapons.HSAB_9_Mk_20_Rockeye): 1968,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.MAK79_2_MK_20): 1968,
|
Weapon.from_pydcs(Weapons.MAK79_2_MK_20): 1968,
|
||||||
Weapon.from_pydcs(Weapons.MAK79_2_MK_20_): 1968,
|
Weapon.from_pydcs(Weapons.MAK79_2_MK_20_): 1968,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.MAK79_MK_20): 1968,
|
Weapon.from_pydcs(Weapons.MAK79_MK_20): 1968,
|
||||||
Weapon.from_pydcs(Weapons.MAK79_MK_20_): 1968,
|
Weapon.from_pydcs(Weapons.MAK79_MK_20_): 1968,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.MER_6_Mk_20_Rockeye): 1968,
|
Weapon.from_pydcs(Weapons.MER_6_Mk_20_Rockeye): 1968,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.Mk_20): 1968,
|
Weapon.from_pydcs(Weapons.Mk_20): 1968,
|
||||||
Weapon.from_pydcs(Weapons.Mk_20_): 1968,
|
Weapon.from_pydcs(Weapons.Mk_20_): 1968,
|
||||||
Weapon.from_pydcs(Weapons.Mk_20_18): 1968,
|
Weapon.from_pydcs(Weapons.Mk_20_18): 1968,
|
||||||
Weapon.from_pydcs(Weapons.Mk_20_Rockeye__6): 1968,
|
Weapon.from_pydcs(Weapons.Mk_20_Rockeye__6): 1968,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons._2_MK_20): 1968,
|
Weapon.from_pydcs(Weapons._2_MK_20): 1968,
|
||||||
Weapon.from_pydcs(Weapons._2_MK_20_): 1968,
|
Weapon.from_pydcs(Weapons._2_MK_20_): 1968,
|
||||||
Weapon.from_pydcs(Weapons._2_MK_20__): 1968,
|
Weapon.from_pydcs(Weapons._2_MK_20__): 1968,
|
||||||
@ -872,123 +758,100 @@ WEAPON_INTRODUCTION_YEARS = {
|
|||||||
Weapon.from_pydcs(Weapons._2_Mk_20_Rockeye): 1968,
|
Weapon.from_pydcs(Weapons._2_Mk_20_Rockeye): 1968,
|
||||||
Weapon.from_pydcs(Weapons._2_Mk_20_Rockeye_): 1968,
|
Weapon.from_pydcs(Weapons._2_Mk_20_Rockeye_): 1968,
|
||||||
Weapon.from_pydcs(Weapons._2_Mk_20_Rockeye__): 1968,
|
Weapon.from_pydcs(Weapons._2_Mk_20_Rockeye__): 1968,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons._3_Mk_20_Rockeye): 1968,
|
Weapon.from_pydcs(Weapons._3_Mk_20_Rockeye): 1968,
|
||||||
Weapon.from_pydcs(Weapons._3_Mk_20_Rockeye_): 1968,
|
Weapon.from_pydcs(Weapons._3_Mk_20_Rockeye_): 1968,
|
||||||
|
|
||||||
# CBU-103
|
# CBU-103
|
||||||
Weapon.from_pydcs(Weapons.BRU_57___2_x_CBU_103): 2000,
|
Weapon.from_pydcs(Weapons.BRU_57___2_x_CBU_103): 2000,
|
||||||
Weapon.from_pydcs(Weapons.CBU_103): 2000,
|
Weapon.from_pydcs(Weapons.CBU_103): 2000,
|
||||||
|
|
||||||
# CBU-105
|
# CBU-105
|
||||||
Weapon.from_pydcs(Weapons.BRU_57___2_x_CBU_105): 2000,
|
Weapon.from_pydcs(Weapons.BRU_57___2_x_CBU_105): 2000,
|
||||||
Weapon.from_pydcs(Weapons.CBU_105): 2000,
|
Weapon.from_pydcs(Weapons.CBU_105): 2000,
|
||||||
|
|
||||||
# APKWS
|
# APKWS
|
||||||
Weapon.from_pydcs(Weapons.LAU_131_pod___7_x_2_75__Hydra___Laser_Guided_Rkts_M151___HE_APKWS): 2016,
|
Weapon.from_pydcs(
|
||||||
Weapon.from_pydcs(Weapons.LAU_131_pod___7_x_2_75__Hydra___Laser_Guided_Rkts_M282___MPP_APKWS): 2016,
|
Weapons.LAU_131_pod___7_x_2_75__Hydra___Laser_Guided_Rkts_M151___HE_APKWS
|
||||||
Weapon.from_pydcs(Weapons._3_x_LAU_131_pods___21_x_2_75__Hydra___Laser_Guided_M151___HE_APKWS): 2016,
|
): 2016,
|
||||||
Weapon.from_pydcs(Weapons._3_x_LAU_131_pods___21_x_2_75__Hydra___Laser_Guided_M282___MPP_APKWS): 2016,
|
Weapon.from_pydcs(
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75__Hydra___Laser_Guided_Rkts_M282___MPP_APKWS
|
||||||
|
): 2016,
|
||||||
|
Weapon.from_pydcs(
|
||||||
|
Weapons._3_x_LAU_131_pods___21_x_2_75__Hydra___Laser_Guided_M151___HE_APKWS
|
||||||
|
): 2016,
|
||||||
|
Weapon.from_pydcs(
|
||||||
|
Weapons._3_x_LAU_131_pods___21_x_2_75__Hydra___Laser_Guided_M282___MPP_APKWS
|
||||||
|
): 2016,
|
||||||
# Russia
|
# Russia
|
||||||
|
|
||||||
# KAB-1500
|
# KAB-1500
|
||||||
Weapon.from_pydcs(Weapons.KAB_1500Kr): 1985,
|
Weapon.from_pydcs(Weapons.KAB_1500Kr): 1985,
|
||||||
Weapon.from_pydcs(Weapons.KAB_1500L): 1995,
|
Weapon.from_pydcs(Weapons.KAB_1500L): 1995,
|
||||||
Weapon.from_pydcs(Weapons.KAB_1500LG_Pr): 1990,
|
Weapon.from_pydcs(Weapons.KAB_1500LG_Pr): 1990,
|
||||||
|
|
||||||
# KAB-500
|
# KAB-500
|
||||||
Weapon.from_pydcs(Weapons.KAB_500kr): 1980,
|
Weapon.from_pydcs(Weapons.KAB_500kr): 1980,
|
||||||
Weapon.from_pydcs(Weapons.KAB_500L): 1995,
|
Weapon.from_pydcs(Weapons.KAB_500L): 1995,
|
||||||
Weapon.from_pydcs(Weapons.KAB_500S): 2000,
|
Weapon.from_pydcs(Weapons.KAB_500S): 2000,
|
||||||
|
|
||||||
# Kh Series
|
# Kh Series
|
||||||
Weapon.from_pydcs(Weapons.Kh_22N): 1962,
|
Weapon.from_pydcs(Weapons.Kh_22N): 1962,
|
||||||
Weapon.from_pydcs(Weapons.Kh_23L): 1975,
|
Weapon.from_pydcs(Weapons.Kh_23L): 1975,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.Kh_25ML): 1975,
|
Weapon.from_pydcs(Weapons.Kh_25ML): 1975,
|
||||||
Weapon.from_pydcs(Weapons.Kh_25ML_): 1975,
|
Weapon.from_pydcs(Weapons.Kh_25ML_): 1975,
|
||||||
Weapon.from_pydcs(Weapons.Kh_25ML__): 1975,
|
Weapon.from_pydcs(Weapons.Kh_25ML__): 1975,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.Kh_25MP): 1975,
|
Weapon.from_pydcs(Weapons.Kh_25MP): 1975,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.Kh_25MPU): 1980,
|
Weapon.from_pydcs(Weapons.Kh_25MPU): 1980,
|
||||||
Weapon.from_pydcs(Weapons.Kh_25MPU_): 1980,
|
Weapon.from_pydcs(Weapons.Kh_25MPU_): 1980,
|
||||||
Weapon.from_pydcs(Weapons.Kh_25MPU__): 1980,
|
Weapon.from_pydcs(Weapons.Kh_25MPU__): 1980,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.Kh_25MR): 1975,
|
Weapon.from_pydcs(Weapons.Kh_25MR): 1975,
|
||||||
Weapon.from_pydcs(Weapons.Kh_25MR_): 1975,
|
Weapon.from_pydcs(Weapons.Kh_25MR_): 1975,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.Kh_28__AS_9_Kyle_): 1973,
|
Weapon.from_pydcs(Weapons.Kh_28__AS_9_Kyle_): 1973,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.Kh_29L): 1980,
|
Weapon.from_pydcs(Weapons.Kh_29L): 1980,
|
||||||
Weapon.from_pydcs(Weapons.Kh_29L_): 1980,
|
Weapon.from_pydcs(Weapons.Kh_29L_): 1980,
|
||||||
Weapon.from_pydcs(Weapons.Kh_29L__): 1980,
|
Weapon.from_pydcs(Weapons.Kh_29L__): 1980,
|
||||||
Weapon.from_pydcs(Weapons.Kh_29T): 1980,
|
Weapon.from_pydcs(Weapons.Kh_29T): 1980,
|
||||||
Weapon.from_pydcs(Weapons.Kh_29T_): 1980,
|
Weapon.from_pydcs(Weapons.Kh_29T_): 1980,
|
||||||
Weapon.from_pydcs(Weapons.Kh_29T__): 1980,
|
Weapon.from_pydcs(Weapons.Kh_29T__): 1980,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.Kh_31A): 1980,
|
Weapon.from_pydcs(Weapons.Kh_31A): 1980,
|
||||||
Weapon.from_pydcs(Weapons.Kh_31A_): 1980,
|
Weapon.from_pydcs(Weapons.Kh_31A_): 1980,
|
||||||
Weapon.from_pydcs(Weapons.Kh_31A__): 1980,
|
Weapon.from_pydcs(Weapons.Kh_31A__): 1980,
|
||||||
Weapon.from_pydcs(Weapons.Kh_31P): 1980,
|
Weapon.from_pydcs(Weapons.Kh_31P): 1980,
|
||||||
Weapon.from_pydcs(Weapons.Kh_31P_): 1980,
|
Weapon.from_pydcs(Weapons.Kh_31P_): 1980,
|
||||||
Weapon.from_pydcs(Weapons.Kh_31P__): 1980,
|
Weapon.from_pydcs(Weapons.Kh_31P__): 1980,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.Kh_35): 2003,
|
Weapon.from_pydcs(Weapons.Kh_35): 2003,
|
||||||
Weapon.from_pydcs(Weapons.Kh_35_): 2003,
|
Weapon.from_pydcs(Weapons.Kh_35_): 2003,
|
||||||
Weapon.from_pydcs(Weapons.Kh_35_6): 2003,
|
Weapon.from_pydcs(Weapons.Kh_35_6): 2003,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.Kh_41): 1984,
|
Weapon.from_pydcs(Weapons.Kh_41): 1984,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.Kh_58U): 1985,
|
Weapon.from_pydcs(Weapons.Kh_58U): 1985,
|
||||||
Weapon.from_pydcs(Weapons.Kh_58U_): 1985,
|
Weapon.from_pydcs(Weapons.Kh_58U_): 1985,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.Kh_59M): 1990,
|
Weapon.from_pydcs(Weapons.Kh_59M): 1990,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.Kh_65): 1992,
|
Weapon.from_pydcs(Weapons.Kh_65): 1992,
|
||||||
Weapon.from_pydcs(Weapons.Kh_65_6): 1992,
|
Weapon.from_pydcs(Weapons.Kh_65_6): 1992,
|
||||||
Weapon.from_pydcs(Weapons.Kh_65_8): 1992,
|
Weapon.from_pydcs(Weapons.Kh_65_8): 1992,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.Kh_66_Grom__21__APU_68): 1968,
|
Weapon.from_pydcs(Weapons.Kh_66_Grom__21__APU_68): 1968,
|
||||||
|
|
||||||
# ECM
|
# ECM
|
||||||
Weapon.from_pydcs(Weapons.L175V_Khibiny_ECM_pod): 1982,
|
Weapon.from_pydcs(Weapons.L175V_Khibiny_ECM_pod): 1982,
|
||||||
|
|
||||||
# R-13
|
# R-13
|
||||||
Weapon.from_pydcs(Weapons.R_13M): 1961,
|
Weapon.from_pydcs(Weapons.R_13M): 1961,
|
||||||
Weapon.from_pydcs(Weapons.R_13M1): 1965,
|
Weapon.from_pydcs(Weapons.R_13M1): 1965,
|
||||||
|
|
||||||
# R-24
|
# R-24
|
||||||
Weapon.from_pydcs(Weapons.R_24R): 1981,
|
Weapon.from_pydcs(Weapons.R_24R): 1981,
|
||||||
Weapon.from_pydcs(Weapons.R_24T): 1981,
|
Weapon.from_pydcs(Weapons.R_24T): 1981,
|
||||||
|
|
||||||
# R-27
|
# R-27
|
||||||
Weapon.from_pydcs(Weapons.R_27ER): 1983,
|
Weapon.from_pydcs(Weapons.R_27ER): 1983,
|
||||||
Weapon.from_pydcs(Weapons.R_27ET): 1986,
|
Weapon.from_pydcs(Weapons.R_27ET): 1986,
|
||||||
Weapon.from_pydcs(Weapons.R_27R): 1983,
|
Weapon.from_pydcs(Weapons.R_27R): 1983,
|
||||||
Weapon.from_pydcs(Weapons.R_27T): 1983,
|
Weapon.from_pydcs(Weapons.R_27T): 1983,
|
||||||
|
|
||||||
# R-33
|
# R-33
|
||||||
Weapon.from_pydcs(Weapons.R_33): 1981,
|
Weapon.from_pydcs(Weapons.R_33): 1981,
|
||||||
|
|
||||||
# R-3
|
# R-3
|
||||||
Weapon.from_pydcs(Weapons.R_3R): 1966,
|
Weapon.from_pydcs(Weapons.R_3R): 1966,
|
||||||
Weapon.from_pydcs(Weapons.R_3S): 1962,
|
Weapon.from_pydcs(Weapons.R_3S): 1962,
|
||||||
|
|
||||||
# R-40
|
# R-40
|
||||||
Weapon.from_pydcs(Weapons.R_40R): 1976,
|
Weapon.from_pydcs(Weapons.R_40R): 1976,
|
||||||
Weapon.from_pydcs(Weapons.R_40T): 1976,
|
Weapon.from_pydcs(Weapons.R_40T): 1976,
|
||||||
|
|
||||||
# R-55
|
# R-55
|
||||||
Weapon.from_pydcs(Weapons.R_55): 1957,
|
Weapon.from_pydcs(Weapons.R_55): 1957,
|
||||||
Weapon.from_pydcs(Weapons.RS2US): 1957,
|
Weapon.from_pydcs(Weapons.RS2US): 1957,
|
||||||
|
|
||||||
# R-60
|
# R-60
|
||||||
Weapon.from_pydcs(Weapons.R_60): 1973,
|
Weapon.from_pydcs(Weapons.R_60): 1973,
|
||||||
Weapon.from_pydcs(Weapons.R_60_x_2): 1973,
|
Weapon.from_pydcs(Weapons.R_60_x_2): 1973,
|
||||||
Weapon.from_pydcs(Weapons.R_60_x_2_): 1973,
|
Weapon.from_pydcs(Weapons.R_60_x_2_): 1973,
|
||||||
|
|
||||||
Weapon.from_pydcs(Weapons.APU_60_1_R_60M): 1982,
|
Weapon.from_pydcs(Weapons.APU_60_1_R_60M): 1982,
|
||||||
Weapon.from_pydcs(Weapons.R_60M): 1982,
|
Weapon.from_pydcs(Weapons.R_60M): 1982,
|
||||||
Weapon.from_pydcs(Weapons.R_60M_): 1982,
|
Weapon.from_pydcs(Weapons.R_60M_): 1982,
|
||||||
@ -996,36 +859,28 @@ WEAPON_INTRODUCTION_YEARS = {
|
|||||||
Weapon.from_pydcs(Weapons.R_60M_2_): 1982,
|
Weapon.from_pydcs(Weapons.R_60M_2_): 1982,
|
||||||
Weapon.from_pydcs(Weapons.R_60M_x_2): 1982,
|
Weapon.from_pydcs(Weapons.R_60M_x_2): 1982,
|
||||||
Weapon.from_pydcs(Weapons.R_60M_x_2_): 1982,
|
Weapon.from_pydcs(Weapons.R_60M_x_2_): 1982,
|
||||||
|
|
||||||
# R-73
|
# R-73
|
||||||
Weapon.from_pydcs(Weapons.R_73): 1984,
|
Weapon.from_pydcs(Weapons.R_73): 1984,
|
||||||
Weapon.from_pydcs(Weapons.R_73_): 1984,
|
Weapon.from_pydcs(Weapons.R_73_): 1984,
|
||||||
|
|
||||||
# R-77
|
# R-77
|
||||||
Weapon.from_pydcs(Weapons.R_77): 2002,
|
Weapon.from_pydcs(Weapons.R_77): 2002,
|
||||||
Weapon.from_pydcs(Weapons.R_77_): 2002,
|
Weapon.from_pydcs(Weapons.R_77_): 2002,
|
||||||
|
|
||||||
# UK
|
# UK
|
||||||
# ALARM
|
# ALARM
|
||||||
Weapon.from_pydcs(Weapons.ALARM): 1990,
|
Weapon.from_pydcs(Weapons.ALARM): 1990,
|
||||||
Weapon.from_pydcs(Weapons.ALARM_2): 1990,
|
Weapon.from_pydcs(Weapons.ALARM_2): 1990,
|
||||||
|
|
||||||
# France
|
# France
|
||||||
# BLG-66 Belouga
|
# BLG-66 Belouga
|
||||||
Weapon.from_pydcs(Weapons.AUF2_BLG_66_AC_x_2): 1979,
|
Weapon.from_pydcs(Weapons.AUF2_BLG_66_AC_x_2): 1979,
|
||||||
Weapon.from_pydcs(Weapons.BLG_66_AC_Belouga): 1979,
|
Weapon.from_pydcs(Weapons.BLG_66_AC_Belouga): 1979,
|
||||||
Weapon.from_pydcs(Weapons.BLG_66_AC_Belouga_): 1979,
|
Weapon.from_pydcs(Weapons.BLG_66_AC_Belouga_): 1979,
|
||||||
|
|
||||||
# HOT-3
|
# HOT-3
|
||||||
Weapon.from_pydcs(Weapons.HOT3): 1998,
|
Weapon.from_pydcs(Weapons.HOT3): 1998,
|
||||||
Weapon.from_pydcs(Weapons.HOT3_): 1998,
|
Weapon.from_pydcs(Weapons.HOT3_): 1998,
|
||||||
|
|
||||||
# Magic 2
|
# Magic 2
|
||||||
Weapon.from_pydcs(Weapons.Matra_Magic_II): 1986,
|
Weapon.from_pydcs(Weapons.Matra_Magic_II): 1986,
|
||||||
Weapon.from_pydcs(Weapons.R_550_Magic_2): 1986,
|
Weapon.from_pydcs(Weapons.R_550_Magic_2): 1986,
|
||||||
|
|
||||||
# Super 530D
|
# Super 530D
|
||||||
Weapon.from_pydcs(Weapons.Matra_Super_530D): 1988,
|
Weapon.from_pydcs(Weapons.Matra_Super_530D): 1988,
|
||||||
Weapon.from_pydcs(Weapons.Super_530D): 1988,
|
Weapon.from_pydcs(Weapons.Super_530D): 1988,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
342
game/db.py
342
game/db.py
@ -25,6 +25,7 @@ from dcs.helicopters import (
|
|||||||
helicopter_map,
|
helicopter_map,
|
||||||
)
|
)
|
||||||
from dcs.mapping import Point
|
from dcs.mapping import Point
|
||||||
|
|
||||||
# mypy can't resolve these if they're wildcard imports for some reason.
|
# mypy can't resolve these if they're wildcard imports for some reason.
|
||||||
from dcs.planes import (
|
from dcs.planes import (
|
||||||
AJS37,
|
AJS37,
|
||||||
@ -112,7 +113,7 @@ from dcs.planes import (
|
|||||||
WingLoong_I,
|
WingLoong_I,
|
||||||
Yak_40,
|
Yak_40,
|
||||||
plane_map,
|
plane_map,
|
||||||
I_16
|
I_16,
|
||||||
)
|
)
|
||||||
from dcs.ships import (
|
from dcs.ships import (
|
||||||
Armed_speedboat,
|
Armed_speedboat,
|
||||||
@ -166,6 +167,7 @@ from dcs.vehicles import (
|
|||||||
|
|
||||||
import pydcs_extensions.frenchpack.frenchpack as frenchpack
|
import pydcs_extensions.frenchpack.frenchpack as frenchpack
|
||||||
import pydcs_extensions.highdigitsams.highdigitsams as highdigitsams
|
import pydcs_extensions.highdigitsams.highdigitsams as highdigitsams
|
||||||
|
|
||||||
# PATCH pydcs data with MODS
|
# PATCH pydcs data with MODS
|
||||||
from game.factions.faction_loader import FactionLoader
|
from game.factions.faction_loader import FactionLoader
|
||||||
from pydcs_extensions.a4ec.a4ec import A_4E_C
|
from pydcs_extensions.a4ec.a4ec import A_4E_C
|
||||||
@ -222,46 +224,122 @@ vehicle_map["Kamikaze"] = frenchpack.DIM__KAMIKAZE
|
|||||||
|
|
||||||
vehicle_map[highdigitsams.AAA_SON_9_Fire_Can.id] = highdigitsams.AAA_SON_9_Fire_Can
|
vehicle_map[highdigitsams.AAA_SON_9_Fire_Can.id] = highdigitsams.AAA_SON_9_Fire_Can
|
||||||
vehicle_map[highdigitsams.AAA_100mm_KS_19.id] = highdigitsams.AAA_100mm_KS_19
|
vehicle_map[highdigitsams.AAA_100mm_KS_19.id] = highdigitsams.AAA_100mm_KS_19
|
||||||
vehicle_map[highdigitsams.SAM_SA_10B_S_300PS_54K6_CP.id] = highdigitsams.SAM_SA_10B_S_300PS_54K6_CP
|
vehicle_map[
|
||||||
vehicle_map[highdigitsams.SAM_SA_10B_S_300PS_5P85SE_LN.id] = highdigitsams.SAM_SA_10B_S_300PS_5P85SE_LN
|
highdigitsams.SAM_SA_10B_S_300PS_54K6_CP.id
|
||||||
vehicle_map[highdigitsams.SAM_SA_10B_S_300PS_5P85SU_LN.id] = highdigitsams.SAM_SA_10B_S_300PS_5P85SU_LN
|
] = highdigitsams.SAM_SA_10B_S_300PS_54K6_CP
|
||||||
vehicle_map[highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85CE.id] = highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85CE
|
vehicle_map[
|
||||||
vehicle_map[highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85DE.id] = highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85DE
|
highdigitsams.SAM_SA_10B_S_300PS_5P85SE_LN.id
|
||||||
vehicle_map[highdigitsams.SAM_SA_10B_S_300PS_30N6_TR.id] = highdigitsams.SAM_SA_10B_S_300PS_30N6_TR
|
] = highdigitsams.SAM_SA_10B_S_300PS_5P85SE_LN
|
||||||
vehicle_map[highdigitsams.SAM_SA_10B_S_300PS_40B6M_TR.id] = highdigitsams.SAM_SA_10B_S_300PS_40B6M_TR
|
vehicle_map[
|
||||||
vehicle_map[highdigitsams.SAM_SA_10B_S_300PS_40B6MD_SR.id] = highdigitsams.SAM_SA_10B_S_300PS_40B6MD_SR
|
highdigitsams.SAM_SA_10B_S_300PS_5P85SU_LN.id
|
||||||
vehicle_map[highdigitsams.SAM_SA_10B_S_300PS_64H6E_SR.id] = highdigitsams.SAM_SA_10B_S_300PS_64H6E_SR
|
] = highdigitsams.SAM_SA_10B_S_300PS_5P85SU_LN
|
||||||
vehicle_map[highdigitsams.SAM_SA_20_S_300PMU1_CP_54K6.id] = highdigitsams.SAM_SA_20_S_300PMU1_CP_54K6
|
vehicle_map[
|
||||||
vehicle_map[highdigitsams.SAM_SA_20_S_300PMU1_TR_30N6E.id] = highdigitsams.SAM_SA_20_S_300PMU1_TR_30N6E
|
highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85CE.id
|
||||||
vehicle_map[highdigitsams.SAM_SA_20_S_300PMU1_TR_30N6E_truck.id] = highdigitsams.SAM_SA_20_S_300PMU1_TR_30N6E_truck
|
] = highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85CE
|
||||||
vehicle_map[highdigitsams.SAM_SA_20_S_300PMU1_SR_5N66E.id] = highdigitsams.SAM_SA_20_S_300PMU1_SR_5N66E
|
vehicle_map[
|
||||||
vehicle_map[highdigitsams.SAM_SA_20_S_300PMU1_SR_64N6E.id] = highdigitsams.SAM_SA_20_S_300PMU1_SR_64N6E
|
highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85DE.id
|
||||||
vehicle_map[highdigitsams.SAM_SA_20_S_300PMU1_LN_5P85CE.id] = highdigitsams.SAM_SA_20_S_300PMU1_LN_5P85CE
|
] = highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85DE
|
||||||
vehicle_map[highdigitsams.SAM_SA_20_S_300PMU1_LN_5P85DE.id] = highdigitsams.SAM_SA_20_S_300PMU1_LN_5P85DE
|
vehicle_map[
|
||||||
vehicle_map[highdigitsams.SAM_SA_20B_S_300PMU2_CP_54K6E2.id] = highdigitsams.SAM_SA_20B_S_300PMU2_CP_54K6E2
|
highdigitsams.SAM_SA_10B_S_300PS_30N6_TR.id
|
||||||
vehicle_map[highdigitsams.SAM_SA_20B_S_300PMU2_TR_92H6E_truck.id] = highdigitsams.SAM_SA_20B_S_300PMU2_TR_92H6E_truck
|
] = highdigitsams.SAM_SA_10B_S_300PS_30N6_TR
|
||||||
vehicle_map[highdigitsams.SAM_SA_20B_S_300PMU2_SR_64N6E2.id] = highdigitsams.SAM_SA_20B_S_300PMU2_SR_64N6E2
|
vehicle_map[
|
||||||
vehicle_map[highdigitsams.SAM_SA_20B_S_300PMU2_LN_5P85SE2.id] = highdigitsams.SAM_SA_20B_S_300PMU2_LN_5P85SE2
|
highdigitsams.SAM_SA_10B_S_300PS_40B6M_TR.id
|
||||||
vehicle_map[highdigitsams.SAM_SA_12_S_300V_9S457_CP.id] = highdigitsams.SAM_SA_12_S_300V_9S457_CP
|
] = highdigitsams.SAM_SA_10B_S_300PS_40B6M_TR
|
||||||
vehicle_map[highdigitsams.SAM_SA_12_S_300V_9A82_LN.id] = highdigitsams.SAM_SA_12_S_300V_9A82_LN
|
vehicle_map[
|
||||||
vehicle_map[highdigitsams.SAM_SA_12_S_300V_9A83_LN.id] = highdigitsams.SAM_SA_12_S_300V_9A83_LN
|
highdigitsams.SAM_SA_10B_S_300PS_40B6MD_SR.id
|
||||||
vehicle_map[highdigitsams.SAM_SA_12_S_300V_9S15_SR.id] = highdigitsams.SAM_SA_12_S_300V_9S15_SR
|
] = highdigitsams.SAM_SA_10B_S_300PS_40B6MD_SR
|
||||||
vehicle_map[highdigitsams.SAM_SA_12_S_300V_9S19_SR.id] = highdigitsams.SAM_SA_12_S_300V_9S19_SR
|
vehicle_map[
|
||||||
vehicle_map[highdigitsams.SAM_SA_12_S_300V_9S32_TR.id] = highdigitsams.SAM_SA_12_S_300V_9S32_TR
|
highdigitsams.SAM_SA_10B_S_300PS_64H6E_SR.id
|
||||||
vehicle_map[highdigitsams.SAM_SA_23_S_300VM_9S457ME_CP.id] = highdigitsams.SAM_SA_23_S_300VM_9S457ME_CP
|
] = highdigitsams.SAM_SA_10B_S_300PS_64H6E_SR
|
||||||
vehicle_map[highdigitsams.SAM_SA_23_S_300VM_9S15M2_SR.id] = highdigitsams.SAM_SA_23_S_300VM_9S15M2_SR
|
vehicle_map[
|
||||||
vehicle_map[highdigitsams.SAM_SA_23_S_300VM_9S19M2_SR.id] = highdigitsams.SAM_SA_23_S_300VM_9S19M2_SR
|
highdigitsams.SAM_SA_20_S_300PMU1_CP_54K6.id
|
||||||
vehicle_map[highdigitsams.SAM_SA_23_S_300VM_9S32ME_TR.id] = highdigitsams.SAM_SA_23_S_300VM_9S32ME_TR
|
] = highdigitsams.SAM_SA_20_S_300PMU1_CP_54K6
|
||||||
vehicle_map[highdigitsams.SAM_SA_23_S_300VM_9A83ME_LN.id] = highdigitsams.SAM_SA_23_S_300VM_9A83ME_LN
|
vehicle_map[
|
||||||
vehicle_map[highdigitsams.SAM_SA_23_S_300VM_9A82ME_LN.id] = highdigitsams.SAM_SA_23_S_300VM_9A82ME_LN
|
highdigitsams.SAM_SA_20_S_300PMU1_TR_30N6E.id
|
||||||
vehicle_map[highdigitsams.SAM_SA_17_Buk_M1_2_LN_9A310M1_2.id] = highdigitsams.SAM_SA_17_Buk_M1_2_LN_9A310M1_2
|
] = highdigitsams.SAM_SA_20_S_300PMU1_TR_30N6E
|
||||||
vehicle_map[highdigitsams.SAM_SA_2__V759__LN_SM_90.id] = highdigitsams.SAM_SA_2__V759__LN_SM_90
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_20_S_300PMU1_TR_30N6E_truck.id
|
||||||
|
] = highdigitsams.SAM_SA_20_S_300PMU1_TR_30N6E_truck
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_20_S_300PMU1_SR_5N66E.id
|
||||||
|
] = highdigitsams.SAM_SA_20_S_300PMU1_SR_5N66E
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_20_S_300PMU1_SR_64N6E.id
|
||||||
|
] = highdigitsams.SAM_SA_20_S_300PMU1_SR_64N6E
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_20_S_300PMU1_LN_5P85CE.id
|
||||||
|
] = highdigitsams.SAM_SA_20_S_300PMU1_LN_5P85CE
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_20_S_300PMU1_LN_5P85DE.id
|
||||||
|
] = highdigitsams.SAM_SA_20_S_300PMU1_LN_5P85DE
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_20B_S_300PMU2_CP_54K6E2.id
|
||||||
|
] = highdigitsams.SAM_SA_20B_S_300PMU2_CP_54K6E2
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_20B_S_300PMU2_TR_92H6E_truck.id
|
||||||
|
] = highdigitsams.SAM_SA_20B_S_300PMU2_TR_92H6E_truck
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_20B_S_300PMU2_SR_64N6E2.id
|
||||||
|
] = highdigitsams.SAM_SA_20B_S_300PMU2_SR_64N6E2
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_20B_S_300PMU2_LN_5P85SE2.id
|
||||||
|
] = highdigitsams.SAM_SA_20B_S_300PMU2_LN_5P85SE2
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_12_S_300V_9S457_CP.id
|
||||||
|
] = highdigitsams.SAM_SA_12_S_300V_9S457_CP
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_12_S_300V_9A82_LN.id
|
||||||
|
] = highdigitsams.SAM_SA_12_S_300V_9A82_LN
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_12_S_300V_9A83_LN.id
|
||||||
|
] = highdigitsams.SAM_SA_12_S_300V_9A83_LN
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_12_S_300V_9S15_SR.id
|
||||||
|
] = highdigitsams.SAM_SA_12_S_300V_9S15_SR
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_12_S_300V_9S19_SR.id
|
||||||
|
] = highdigitsams.SAM_SA_12_S_300V_9S19_SR
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_12_S_300V_9S32_TR.id
|
||||||
|
] = highdigitsams.SAM_SA_12_S_300V_9S32_TR
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_23_S_300VM_9S457ME_CP.id
|
||||||
|
] = highdigitsams.SAM_SA_23_S_300VM_9S457ME_CP
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_23_S_300VM_9S15M2_SR.id
|
||||||
|
] = highdigitsams.SAM_SA_23_S_300VM_9S15M2_SR
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_23_S_300VM_9S19M2_SR.id
|
||||||
|
] = highdigitsams.SAM_SA_23_S_300VM_9S19M2_SR
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_23_S_300VM_9S32ME_TR.id
|
||||||
|
] = highdigitsams.SAM_SA_23_S_300VM_9S32ME_TR
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_23_S_300VM_9A83ME_LN.id
|
||||||
|
] = highdigitsams.SAM_SA_23_S_300VM_9A83ME_LN
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_23_S_300VM_9A82ME_LN.id
|
||||||
|
] = highdigitsams.SAM_SA_23_S_300VM_9A82ME_LN
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_17_Buk_M1_2_LN_9A310M1_2.id
|
||||||
|
] = highdigitsams.SAM_SA_17_Buk_M1_2_LN_9A310M1_2
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_2__V759__LN_SM_90.id
|
||||||
|
] = highdigitsams.SAM_SA_2__V759__LN_SM_90
|
||||||
vehicle_map[highdigitsams.SAM_HQ_2_LN_SM_90.id] = highdigitsams.SAM_HQ_2_LN_SM_90
|
vehicle_map[highdigitsams.SAM_HQ_2_LN_SM_90.id] = highdigitsams.SAM_HQ_2_LN_SM_90
|
||||||
vehicle_map[highdigitsams.SAM_SA_3__V_601P__LN_5P73.id] = highdigitsams.SAM_SA_3__V_601P__LN_5P73
|
vehicle_map[
|
||||||
vehicle_map[highdigitsams.SAM_SA_24_Igla_S_manpad.id] = highdigitsams.SAM_SA_24_Igla_S_manpad
|
highdigitsams.SAM_SA_3__V_601P__LN_5P73.id
|
||||||
vehicle_map[highdigitsams.SAM_SA_14_Strela_3_manpad.id] = highdigitsams.SAM_SA_14_Strela_3_manpad
|
] = highdigitsams.SAM_SA_3__V_601P__LN_5P73
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_24_Igla_S_manpad.id
|
||||||
|
] = highdigitsams.SAM_SA_24_Igla_S_manpad
|
||||||
|
vehicle_map[
|
||||||
|
highdigitsams.SAM_SA_14_Strela_3_manpad.id
|
||||||
|
] = highdigitsams.SAM_SA_14_Strela_3_manpad
|
||||||
vehicle_map[highdigitsams.Polyana_D4M1_C2_node.id] = highdigitsams.Polyana_D4M1_C2_node
|
vehicle_map[highdigitsams.Polyana_D4M1_C2_node.id] = highdigitsams.Polyana_D4M1_C2_node
|
||||||
vehicle_map[highdigitsams._34Ya6E_Gazetchik_E_decoy.id] = highdigitsams._34Ya6E_Gazetchik_E_decoy
|
vehicle_map[
|
||||||
|
highdigitsams._34Ya6E_Gazetchik_E_decoy.id
|
||||||
|
] = highdigitsams._34Ya6E_Gazetchik_E_decoy
|
||||||
|
|
||||||
"""
|
"""
|
||||||
---------- BEGINNING OF CONFIGURATION SECTION
|
---------- BEGINNING OF CONFIGURATION SECTION
|
||||||
@ -308,7 +386,6 @@ PRICES = {
|
|||||||
JF_17: 20,
|
JF_17: 20,
|
||||||
Su_30: 24,
|
Su_30: 24,
|
||||||
Su_57: 40,
|
Su_57: 40,
|
||||||
|
|
||||||
SpitfireLFMkIX: 14,
|
SpitfireLFMkIX: 14,
|
||||||
SpitfireLFMkIXCW: 14,
|
SpitfireLFMkIXCW: 14,
|
||||||
I_16: 10,
|
I_16: 10,
|
||||||
@ -317,7 +394,6 @@ PRICES = {
|
|||||||
FW_190A8: 14,
|
FW_190A8: 14,
|
||||||
A_20G: 22,
|
A_20G: 22,
|
||||||
Ju_88A4: 24,
|
Ju_88A4: 24,
|
||||||
|
|
||||||
F_5E_3: 8,
|
F_5E_3: 8,
|
||||||
MiG_15bis: 4,
|
MiG_15bis: 4,
|
||||||
MiG_19P: 6,
|
MiG_19P: 6,
|
||||||
@ -328,7 +404,6 @@ PRICES = {
|
|||||||
C_101CC: 6,
|
C_101CC: 6,
|
||||||
A_4E_C: 8,
|
A_4E_C: 8,
|
||||||
MB_339PAN: 6,
|
MB_339PAN: 6,
|
||||||
|
|
||||||
AV8BNA: 14,
|
AV8BNA: 14,
|
||||||
M_2000C: 16,
|
M_2000C: 16,
|
||||||
Mirage_2000_5: 20,
|
Mirage_2000_5: 20,
|
||||||
@ -342,7 +417,6 @@ PRICES = {
|
|||||||
F_22A: 40,
|
F_22A: 40,
|
||||||
Tornado_IDS: 20,
|
Tornado_IDS: 20,
|
||||||
Tornado_GR4: 20,
|
Tornado_GR4: 20,
|
||||||
|
|
||||||
# bomber
|
# bomber
|
||||||
Su_17M4: 10,
|
Su_17M4: 10,
|
||||||
Su_25: 15,
|
Su_25: 15,
|
||||||
@ -352,12 +426,10 @@ PRICES = {
|
|||||||
Su_24M: 20,
|
Su_24M: 20,
|
||||||
Su_24MR: 24,
|
Su_24MR: 24,
|
||||||
MiG_27K: 20,
|
MiG_27K: 20,
|
||||||
|
|
||||||
A_10A: 16,
|
A_10A: 16,
|
||||||
A_10C: 22,
|
A_10C: 22,
|
||||||
A_10C_2: 24,
|
A_10C_2: 24,
|
||||||
S_3B: 10,
|
S_3B: 10,
|
||||||
|
|
||||||
# heli
|
# heli
|
||||||
Ka_50: 13,
|
Ka_50: 13,
|
||||||
SA342M: 8,
|
SA342M: 8,
|
||||||
@ -373,7 +445,6 @@ PRICES = {
|
|||||||
AH_64D: 30,
|
AH_64D: 30,
|
||||||
OH_58D: 6,
|
OH_58D: 6,
|
||||||
SH_60B: 6,
|
SH_60B: 6,
|
||||||
|
|
||||||
# Bombers
|
# Bombers
|
||||||
B_52H: 35,
|
B_52H: 35,
|
||||||
B_1B: 50,
|
B_1B: 50,
|
||||||
@ -382,7 +453,6 @@ PRICES = {
|
|||||||
Tu_22M3: 40,
|
Tu_22M3: 40,
|
||||||
Tu_95MS: 35,
|
Tu_95MS: 35,
|
||||||
F_111F: 21,
|
F_111F: 21,
|
||||||
|
|
||||||
# special
|
# special
|
||||||
IL_76MD: 30,
|
IL_76MD: 30,
|
||||||
An_26B: 25,
|
An_26B: 25,
|
||||||
@ -393,14 +463,12 @@ PRICES = {
|
|||||||
KC_135: 25,
|
KC_135: 25,
|
||||||
KC130: 25,
|
KC130: 25,
|
||||||
KC135MPRS: 25,
|
KC135MPRS: 25,
|
||||||
|
|
||||||
A_50: 50,
|
A_50: 50,
|
||||||
KJ_2000: 50,
|
KJ_2000: 50,
|
||||||
E_3A: 50,
|
E_3A: 50,
|
||||||
E_2C: 50,
|
E_2C: 50,
|
||||||
C_130: 25,
|
C_130: 25,
|
||||||
Hercules: 25,
|
Hercules: 25,
|
||||||
|
|
||||||
# WW2
|
# WW2
|
||||||
P_51D_30_NA: 18,
|
P_51D_30_NA: 18,
|
||||||
P_51D: 16,
|
P_51D: 16,
|
||||||
@ -408,17 +476,14 @@ PRICES = {
|
|||||||
P_47D_30bl1: 16,
|
P_47D_30bl1: 16,
|
||||||
P_47D_40: 18,
|
P_47D_40: 18,
|
||||||
B_17G: 30,
|
B_17G: 30,
|
||||||
|
|
||||||
# Drones
|
# Drones
|
||||||
MQ_9_Reaper: 12,
|
MQ_9_Reaper: 12,
|
||||||
RQ_1A_Predator: 6,
|
RQ_1A_Predator: 6,
|
||||||
WingLoong_I: 6,
|
WingLoong_I: 6,
|
||||||
|
|
||||||
# Modded
|
# Modded
|
||||||
Rafale_M: 26,
|
Rafale_M: 26,
|
||||||
Rafale_A_S: 26,
|
Rafale_A_S: 26,
|
||||||
Rafale_B: 26,
|
Rafale_B: 26,
|
||||||
|
|
||||||
# armor
|
# armor
|
||||||
Armor.APC_MTLB: 4,
|
Armor.APC_MTLB: 4,
|
||||||
Armor.FDDM_Grad: 4,
|
Armor.FDDM_Grad: 4,
|
||||||
@ -437,7 +502,6 @@ PRICES = {
|
|||||||
Armor.IFV_BMP_3: 18,
|
Armor.IFV_BMP_3: 18,
|
||||||
Armor.ZBD_04A: 12,
|
Armor.ZBD_04A: 12,
|
||||||
Armor.ZTZ_96B: 30,
|
Armor.ZTZ_96B: 30,
|
||||||
|
|
||||||
Armor.APC_Cobra: 4,
|
Armor.APC_Cobra: 4,
|
||||||
Armor.APC_M113: 6,
|
Armor.APC_M113: 6,
|
||||||
Armor.APC_M1043_HMMWV_Armament: 2,
|
Armor.APC_M1043_HMMWV_Armament: 2,
|
||||||
@ -457,10 +521,8 @@ PRICES = {
|
|||||||
Armor.IFV_Marder: 10,
|
Armor.IFV_Marder: 10,
|
||||||
Armor.IFV_MCV_80: 10,
|
Armor.IFV_MCV_80: 10,
|
||||||
Armor.IFV_LAV_25: 7,
|
Armor.IFV_LAV_25: 7,
|
||||||
|
|
||||||
Artillery.MLRS_M270: 55,
|
Artillery.MLRS_M270: 55,
|
||||||
Artillery.SPH_M109_Paladin: 25,
|
Artillery.SPH_M109_Paladin: 25,
|
||||||
|
|
||||||
Artillery.SPH_2S9_Nona: 12,
|
Artillery.SPH_2S9_Nona: 12,
|
||||||
Artillery.SPH_2S1_Gvozdika: 18,
|
Artillery.SPH_2S1_Gvozdika: 18,
|
||||||
Artillery.SPH_2S3_Akatsia: 24,
|
Artillery.SPH_2S3_Akatsia: 24,
|
||||||
@ -470,14 +532,11 @@ PRICES = {
|
|||||||
Artillery.MLRS_9A52_Smerch: 40,
|
Artillery.MLRS_9A52_Smerch: 40,
|
||||||
Artillery._2B11_mortar: 4,
|
Artillery._2B11_mortar: 4,
|
||||||
Artillery.SpGH_Dana: 26,
|
Artillery.SpGH_Dana: 26,
|
||||||
|
|
||||||
Unarmed.Transport_UAZ_469: 3,
|
Unarmed.Transport_UAZ_469: 3,
|
||||||
Unarmed.Transport_Ural_375: 3,
|
Unarmed.Transport_Ural_375: 3,
|
||||||
Infantry.Infantry_M4: 1,
|
Infantry.Infantry_M4: 1,
|
||||||
Infantry.Soldier_AK: 1,
|
Infantry.Soldier_AK: 1,
|
||||||
|
|
||||||
Unarmed.Transport_M818: 3,
|
Unarmed.Transport_M818: 3,
|
||||||
|
|
||||||
# WW2
|
# WW2
|
||||||
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G: 24,
|
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G: 24,
|
||||||
Armor.MT_Pz_Kpfw_IV_Ausf_H: 16,
|
Armor.MT_Pz_Kpfw_IV_Ausf_H: 16,
|
||||||
@ -504,22 +563,18 @@ PRICES = {
|
|||||||
Armor.Daimler_Armoured_Car: 8,
|
Armor.Daimler_Armoured_Car: 8,
|
||||||
Armor.LT_Mk_VII_Tetrarch: 8,
|
Armor.LT_Mk_VII_Tetrarch: 8,
|
||||||
Armor.M4_Tractor: 2,
|
Armor.M4_Tractor: 2,
|
||||||
|
|
||||||
# ship
|
# ship
|
||||||
CV_1143_5_Admiral_Kuznetsov: 100,
|
CV_1143_5_Admiral_Kuznetsov: 100,
|
||||||
CVN_74_John_C__Stennis: 100,
|
CVN_74_John_C__Stennis: 100,
|
||||||
LHA_1_Tarawa: 50,
|
LHA_1_Tarawa: 50,
|
||||||
|
|
||||||
Bulk_cargo_ship_Yakushev: 10,
|
Bulk_cargo_ship_Yakushev: 10,
|
||||||
Armed_speedboat: 10,
|
Armed_speedboat: 10,
|
||||||
Dry_cargo_ship_Ivanov: 10,
|
Dry_cargo_ship_Ivanov: 10,
|
||||||
Tanker_Elnya_160: 10,
|
Tanker_Elnya_160: 10,
|
||||||
|
|
||||||
# Air Defence units
|
# Air Defence units
|
||||||
AirDefence.SAM_SA_19_Tunguska_2S6: 30,
|
AirDefence.SAM_SA_19_Tunguska_2S6: 30,
|
||||||
AirDefence.SAM_SA_6_Kub_LN_2P25: 20,
|
AirDefence.SAM_SA_6_Kub_LN_2P25: 20,
|
||||||
AirDefence.SAM_SA_3_S_125_LN_5P73: 6,
|
AirDefence.SAM_SA_3_S_125_LN_5P73: 6,
|
||||||
|
|
||||||
AirDefence.SAM_SA_11_Buk_LN_9A310M1: 30,
|
AirDefence.SAM_SA_11_Buk_LN_9A310M1: 30,
|
||||||
AirDefence.SAM_SA_11_Buk_CC_9S470M1: 25,
|
AirDefence.SAM_SA_11_Buk_CC_9S470M1: 25,
|
||||||
AirDefence.SAM_SA_11_Buk_SR_9S18M1: 28,
|
AirDefence.SAM_SA_11_Buk_SR_9S18M1: 28,
|
||||||
@ -558,7 +613,6 @@ PRICES = {
|
|||||||
AirDefence.SAM_SA_18_Igla_S_comm: 8,
|
AirDefence.SAM_SA_18_Igla_S_comm: 8,
|
||||||
AirDefence.EWR_1L13: 30,
|
AirDefence.EWR_1L13: 30,
|
||||||
AirDefence.SAM_SA_6_Kub_STR_9S91: 22,
|
AirDefence.SAM_SA_6_Kub_STR_9S91: 22,
|
||||||
|
|
||||||
AirDefence.EWR_55G6: 30,
|
AirDefence.EWR_55G6: 30,
|
||||||
AirDefence.CP_9S80M1_Sborka: 10,
|
AirDefence.CP_9S80M1_Sborka: 10,
|
||||||
AirDefence.SAM_Hawk_TR_AN_MPQ_46: 14,
|
AirDefence.SAM_Hawk_TR_AN_MPQ_46: 14,
|
||||||
@ -589,7 +643,6 @@ PRICES = {
|
|||||||
AirDefence.AAA_M1_37mm: 7,
|
AirDefence.AAA_M1_37mm: 7,
|
||||||
AirDefence.AAA_M45_Quadmount: 4,
|
AirDefence.AAA_M45_Quadmount: 4,
|
||||||
AirDefence.AA_gun_QF_3_7: 10,
|
AirDefence.AA_gun_QF_3_7: 10,
|
||||||
|
|
||||||
# FRENCH PACK MOD
|
# FRENCH PACK MOD
|
||||||
frenchpack.AMX_10RCR: 10,
|
frenchpack.AMX_10RCR: 10,
|
||||||
frenchpack.AMX_10RCR_SEPAR: 12,
|
frenchpack.AMX_10RCR_SEPAR: 12,
|
||||||
@ -619,7 +672,6 @@ PRICES = {
|
|||||||
frenchpack.DIM__TOYOTA_GREEN: 2,
|
frenchpack.DIM__TOYOTA_GREEN: 2,
|
||||||
frenchpack.DIM__TOYOTA_DESERT: 2,
|
frenchpack.DIM__TOYOTA_DESERT: 2,
|
||||||
frenchpack.DIM__KAMIKAZE: 6,
|
frenchpack.DIM__KAMIKAZE: 6,
|
||||||
|
|
||||||
# SA-10
|
# SA-10
|
||||||
AirDefence.SAM_SA_10_S_300PS_CP_54K6: 18,
|
AirDefence.SAM_SA_10_S_300PS_CP_54K6: 18,
|
||||||
AirDefence.SAM_SA_10_S_300PS_TR_30N6: 24,
|
AirDefence.SAM_SA_10_S_300PS_TR_30N6: 24,
|
||||||
@ -627,11 +679,9 @@ PRICES = {
|
|||||||
AirDefence.SAM_SA_10_S_300PS_SR_64H6E: 30,
|
AirDefence.SAM_SA_10_S_300PS_SR_64H6E: 30,
|
||||||
AirDefence.SAM_SA_10_S_300PS_LN_5P85C: 22,
|
AirDefence.SAM_SA_10_S_300PS_LN_5P85C: 22,
|
||||||
AirDefence.SAM_SA_10_S_300PS_LN_5P85D: 22,
|
AirDefence.SAM_SA_10_S_300PS_LN_5P85D: 22,
|
||||||
|
|
||||||
# High digit sams mod
|
# High digit sams mod
|
||||||
highdigitsams.AAA_SON_9_Fire_Can: 8,
|
highdigitsams.AAA_SON_9_Fire_Can: 8,
|
||||||
highdigitsams.AAA_100mm_KS_19: 10,
|
highdigitsams.AAA_100mm_KS_19: 10,
|
||||||
|
|
||||||
highdigitsams.SAM_SA_10B_S_300PS_54K6_CP: 20,
|
highdigitsams.SAM_SA_10B_S_300PS_54K6_CP: 20,
|
||||||
highdigitsams.SAM_SA_10B_S_300PS_5P85SE_LN: 24,
|
highdigitsams.SAM_SA_10B_S_300PS_5P85SE_LN: 24,
|
||||||
highdigitsams.SAM_SA_10B_S_300PS_5P85SU_LN: 24,
|
highdigitsams.SAM_SA_10B_S_300PS_5P85SU_LN: 24,
|
||||||
@ -641,14 +691,12 @@ PRICES = {
|
|||||||
highdigitsams.SAM_SA_10B_S_300PS_40B6M_TR: 26,
|
highdigitsams.SAM_SA_10B_S_300PS_40B6M_TR: 26,
|
||||||
highdigitsams.SAM_SA_10B_S_300PS_40B6MD_SR: 32,
|
highdigitsams.SAM_SA_10B_S_300PS_40B6MD_SR: 32,
|
||||||
highdigitsams.SAM_SA_10B_S_300PS_64H6E_SR: 32,
|
highdigitsams.SAM_SA_10B_S_300PS_64H6E_SR: 32,
|
||||||
|
|
||||||
highdigitsams.SAM_SA_12_S_300V_9S457_CP: 22,
|
highdigitsams.SAM_SA_12_S_300V_9S457_CP: 22,
|
||||||
highdigitsams.SAM_SA_12_S_300V_9A82_LN: 26,
|
highdigitsams.SAM_SA_12_S_300V_9A82_LN: 26,
|
||||||
highdigitsams.SAM_SA_12_S_300V_9A83_LN: 26,
|
highdigitsams.SAM_SA_12_S_300V_9A83_LN: 26,
|
||||||
highdigitsams.SAM_SA_12_S_300V_9S15_SR: 34,
|
highdigitsams.SAM_SA_12_S_300V_9S15_SR: 34,
|
||||||
highdigitsams.SAM_SA_12_S_300V_9S19_SR: 34,
|
highdigitsams.SAM_SA_12_S_300V_9S19_SR: 34,
|
||||||
highdigitsams.SAM_SA_12_S_300V_9S32_TR: 28,
|
highdigitsams.SAM_SA_12_S_300V_9S32_TR: 28,
|
||||||
|
|
||||||
highdigitsams.SAM_SA_20_S_300PMU1_CP_54K6: 26,
|
highdigitsams.SAM_SA_20_S_300PMU1_CP_54K6: 26,
|
||||||
highdigitsams.SAM_SA_20_S_300PMU1_TR_30N6E: 30,
|
highdigitsams.SAM_SA_20_S_300PMU1_TR_30N6E: 30,
|
||||||
highdigitsams.SAM_SA_20_S_300PMU1_TR_30N6E_truck: 32,
|
highdigitsams.SAM_SA_20_S_300PMU1_TR_30N6E_truck: 32,
|
||||||
@ -656,21 +704,17 @@ PRICES = {
|
|||||||
highdigitsams.SAM_SA_20_S_300PMU1_SR_64N6E: 38,
|
highdigitsams.SAM_SA_20_S_300PMU1_SR_64N6E: 38,
|
||||||
highdigitsams.SAM_SA_20_S_300PMU1_LN_5P85CE: 28,
|
highdigitsams.SAM_SA_20_S_300PMU1_LN_5P85CE: 28,
|
||||||
highdigitsams.SAM_SA_20_S_300PMU1_LN_5P85DE: 28,
|
highdigitsams.SAM_SA_20_S_300PMU1_LN_5P85DE: 28,
|
||||||
|
|
||||||
highdigitsams.SAM_SA_20B_S_300PMU2_CP_54K6E2: 27,
|
highdigitsams.SAM_SA_20B_S_300PMU2_CP_54K6E2: 27,
|
||||||
highdigitsams.SAM_SA_20B_S_300PMU2_TR_92H6E_truck: 33,
|
highdigitsams.SAM_SA_20B_S_300PMU2_TR_92H6E_truck: 33,
|
||||||
highdigitsams.SAM_SA_20B_S_300PMU2_SR_64N6E2: 40,
|
highdigitsams.SAM_SA_20B_S_300PMU2_SR_64N6E2: 40,
|
||||||
highdigitsams.SAM_SA_20B_S_300PMU2_LN_5P85SE2: 30,
|
highdigitsams.SAM_SA_20B_S_300PMU2_LN_5P85SE2: 30,
|
||||||
|
|
||||||
highdigitsams.SAM_SA_23_S_300VM_9S457ME_CP: 30,
|
highdigitsams.SAM_SA_23_S_300VM_9S457ME_CP: 30,
|
||||||
highdigitsams.SAM_SA_23_S_300VM_9S15M2_SR: 45,
|
highdigitsams.SAM_SA_23_S_300VM_9S15M2_SR: 45,
|
||||||
highdigitsams.SAM_SA_23_S_300VM_9S19M2_SR: 45,
|
highdigitsams.SAM_SA_23_S_300VM_9S19M2_SR: 45,
|
||||||
highdigitsams.SAM_SA_23_S_300VM_9S32ME_TR: 35,
|
highdigitsams.SAM_SA_23_S_300VM_9S32ME_TR: 35,
|
||||||
highdigitsams.SAM_SA_23_S_300VM_9A83ME_LN: 32,
|
highdigitsams.SAM_SA_23_S_300VM_9A83ME_LN: 32,
|
||||||
highdigitsams.SAM_SA_23_S_300VM_9A82ME_LN: 32,
|
highdigitsams.SAM_SA_23_S_300VM_9A82ME_LN: 32,
|
||||||
|
|
||||||
highdigitsams.SAM_SA_17_Buk_M1_2_LN_9A310M1_2: 40,
|
highdigitsams.SAM_SA_17_Buk_M1_2_LN_9A310M1_2: 40,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -728,7 +772,7 @@ UNIT_BY_TASK = {
|
|||||||
SpitfireLFMkIX,
|
SpitfireLFMkIX,
|
||||||
A_4E_C,
|
A_4E_C,
|
||||||
Rafale_M,
|
Rafale_M,
|
||||||
SA342Mistral
|
SA342Mistral,
|
||||||
],
|
],
|
||||||
CAS: [
|
CAS: [
|
||||||
AH_1W,
|
AH_1W,
|
||||||
@ -782,15 +826,9 @@ UNIT_BY_TASK = {
|
|||||||
UH_1H,
|
UH_1H,
|
||||||
SH_60B,
|
SH_60B,
|
||||||
WingLoong_I,
|
WingLoong_I,
|
||||||
Hercules
|
Hercules,
|
||||||
],
|
|
||||||
Transport: [
|
|
||||||
IL_76MD,
|
|
||||||
An_26B,
|
|
||||||
An_30M,
|
|
||||||
Yak_40,
|
|
||||||
C_130
|
|
||||||
],
|
],
|
||||||
|
Transport: [IL_76MD, An_26B, An_30M, Yak_40, C_130],
|
||||||
Refueling: [
|
Refueling: [
|
||||||
IL_78M,
|
IL_78M,
|
||||||
KC_135,
|
KC_135,
|
||||||
@ -798,12 +836,7 @@ UNIT_BY_TASK = {
|
|||||||
S_3B_Tanker,
|
S_3B_Tanker,
|
||||||
KC135MPRS,
|
KC135MPRS,
|
||||||
],
|
],
|
||||||
AWACS: [
|
AWACS: [E_3A, E_2C, A_50, KJ_2000],
|
||||||
E_3A,
|
|
||||||
E_2C,
|
|
||||||
A_50,
|
|
||||||
KJ_2000
|
|
||||||
],
|
|
||||||
PinpointStrike: [
|
PinpointStrike: [
|
||||||
Armor.APC_MTLB,
|
Armor.APC_MTLB,
|
||||||
Armor.APC_MTLB,
|
Armor.APC_MTLB,
|
||||||
@ -851,7 +884,6 @@ UNIT_BY_TASK = {
|
|||||||
Armor.MBT_T_80U,
|
Armor.MBT_T_80U,
|
||||||
Armor.MBT_T_90,
|
Armor.MBT_T_90,
|
||||||
Armor.ZTZ_96B,
|
Armor.ZTZ_96B,
|
||||||
|
|
||||||
Armor.APC_Cobra,
|
Armor.APC_Cobra,
|
||||||
Armor.APC_Cobra,
|
Armor.APC_Cobra,
|
||||||
Armor.APC_Cobra,
|
Armor.APC_Cobra,
|
||||||
@ -895,7 +927,6 @@ UNIT_BY_TASK = {
|
|||||||
Armor.MBT_Leopard_2,
|
Armor.MBT_Leopard_2,
|
||||||
Armor.MBT_Challenger_II,
|
Armor.MBT_Challenger_II,
|
||||||
Armor.MBT_Merkava_Mk__4,
|
Armor.MBT_Merkava_Mk__4,
|
||||||
|
|
||||||
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G,
|
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G,
|
||||||
Armor.MT_Pz_Kpfw_IV_Ausf_H,
|
Armor.MT_Pz_Kpfw_IV_Ausf_H,
|
||||||
Armor.HT_Pz_Kpfw_VI_Tiger_I,
|
Armor.HT_Pz_Kpfw_VI_Tiger_I,
|
||||||
@ -945,7 +976,6 @@ UNIT_BY_TASK = {
|
|||||||
Artillery.Sturmpanzer_IV_Brummbär,
|
Artillery.Sturmpanzer_IV_Brummbär,
|
||||||
Armor.Daimler_Armoured_Car,
|
Armor.Daimler_Armoured_Car,
|
||||||
Armor.LT_Mk_VII_Tetrarch,
|
Armor.LT_Mk_VII_Tetrarch,
|
||||||
|
|
||||||
Artillery.MLRS_M270,
|
Artillery.MLRS_M270,
|
||||||
Artillery.SPH_M109_Paladin,
|
Artillery.SPH_M109_Paladin,
|
||||||
Artillery.SPH_2S9_Nona,
|
Artillery.SPH_2S9_Nona,
|
||||||
@ -959,7 +989,6 @@ UNIT_BY_TASK = {
|
|||||||
Artillery.SpGH_Dana,
|
Artillery.SpGH_Dana,
|
||||||
Artillery.M12_GMC,
|
Artillery.M12_GMC,
|
||||||
Artillery.Sturmpanzer_IV_Brummbär,
|
Artillery.Sturmpanzer_IV_Brummbär,
|
||||||
|
|
||||||
AirDefence.AAA_ZU_23_on_Ural_375,
|
AirDefence.AAA_ZU_23_on_Ural_375,
|
||||||
AirDefence.AAA_ZU_23_Insurgent_on_Ural_375,
|
AirDefence.AAA_ZU_23_Insurgent_on_Ural_375,
|
||||||
AirDefence.AAA_ZSU_57_2,
|
AirDefence.AAA_ZSU_57_2,
|
||||||
@ -983,12 +1012,10 @@ UNIT_BY_TASK = {
|
|||||||
AirDefence.AAA_Bofors_40mm,
|
AirDefence.AAA_Bofors_40mm,
|
||||||
AirDefence.AAA_M1_37mm,
|
AirDefence.AAA_M1_37mm,
|
||||||
AirDefence.AA_gun_QF_3_7,
|
AirDefence.AA_gun_QF_3_7,
|
||||||
|
|
||||||
frenchpack.DIM__TOYOTA_BLUE,
|
frenchpack.DIM__TOYOTA_BLUE,
|
||||||
frenchpack.DIM__TOYOTA_DESERT,
|
frenchpack.DIM__TOYOTA_DESERT,
|
||||||
frenchpack.DIM__TOYOTA_GREEN,
|
frenchpack.DIM__TOYOTA_GREEN,
|
||||||
frenchpack.DIM__KAMIKAZE,
|
frenchpack.DIM__KAMIKAZE,
|
||||||
|
|
||||||
frenchpack.AMX_10RCR,
|
frenchpack.AMX_10RCR,
|
||||||
frenchpack.AMX_10RCR_SEPAR,
|
frenchpack.AMX_10RCR_SEPAR,
|
||||||
frenchpack.ERC_90,
|
frenchpack.ERC_90,
|
||||||
@ -1006,15 +1033,29 @@ UNIT_BY_TASK = {
|
|||||||
frenchpack.DIM__TOYOTA_GREEN,
|
frenchpack.DIM__TOYOTA_GREEN,
|
||||||
frenchpack.DIM__TOYOTA_DESERT,
|
frenchpack.DIM__TOYOTA_DESERT,
|
||||||
frenchpack.DIM__KAMIKAZE,
|
frenchpack.DIM__KAMIKAZE,
|
||||||
|
|
||||||
],
|
],
|
||||||
AirDefence: [
|
AirDefence: [],
|
||||||
|
Reconnaissance: [
|
||||||
|
Unarmed.Transport_M818,
|
||||||
|
Unarmed.Transport_Ural_375,
|
||||||
|
Unarmed.Transport_UAZ_469,
|
||||||
|
],
|
||||||
|
Nothing: [
|
||||||
|
Infantry.Infantry_M4,
|
||||||
|
Infantry.Soldier_AK,
|
||||||
],
|
],
|
||||||
Reconnaissance: [Unarmed.Transport_M818, Unarmed.Transport_Ural_375, Unarmed.Transport_UAZ_469],
|
|
||||||
Nothing: [Infantry.Infantry_M4, Infantry.Soldier_AK, ],
|
|
||||||
Embarking: [],
|
Embarking: [],
|
||||||
Carriage: [CVN_74_John_C__Stennis, LHA_1_Tarawa, CV_1143_5_Admiral_Kuznetsov, ],
|
Carriage: [
|
||||||
CargoTransportation: [Dry_cargo_ship_Ivanov, Bulk_cargo_ship_Yakushev, Tanker_Elnya_160, Armed_speedboat, ]
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
CV_1143_5_Admiral_Kuznetsov,
|
||||||
|
],
|
||||||
|
CargoTransportation: [
|
||||||
|
Dry_cargo_ship_Ivanov,
|
||||||
|
Bulk_cargo_ship_Yakushev,
|
||||||
|
Tanker_Elnya_160,
|
||||||
|
Armed_speedboat,
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -1022,7 +1063,6 @@ Units from AirDefense category of UNIT_BY_TASK that will be removed from use if
|
|||||||
"""
|
"""
|
||||||
SAM_BAN = [
|
SAM_BAN = [
|
||||||
AirDefence.SAM_Linebacker_M6,
|
AirDefence.SAM_Linebacker_M6,
|
||||||
|
|
||||||
AirDefence.SAM_SA_9_Strela_1_9P31,
|
AirDefence.SAM_SA_9_Strela_1_9P31,
|
||||||
AirDefence.SAM_SA_8_Osa_9A33,
|
AirDefence.SAM_SA_8_Osa_9A33,
|
||||||
AirDefence.SAM_SA_19_Tunguska_2S6,
|
AirDefence.SAM_SA_19_Tunguska_2S6,
|
||||||
@ -1051,20 +1091,19 @@ SAM_CONVERT = {
|
|||||||
AirDefence.SAM_Hawk_TR_AN_MPQ_46: AirDefence.SAM_Hawk_PCP,
|
AirDefence.SAM_Hawk_TR_AN_MPQ_46: AirDefence.SAM_Hawk_PCP,
|
||||||
AirDefence.SAM_Hawk_SR_AN_MPQ_50: AirDefence.SAM_Hawk_PCP,
|
AirDefence.SAM_Hawk_SR_AN_MPQ_50: AirDefence.SAM_Hawk_PCP,
|
||||||
AirDefence.SAM_Hawk_LN_M192: AirDefence.SAM_Hawk_PCP,
|
AirDefence.SAM_Hawk_LN_M192: AirDefence.SAM_Hawk_PCP,
|
||||||
'except': {
|
"except": {
|
||||||
# this radar is shared between the two S300's. if we attempt to find a SAM site at a base and can't find one
|
# this radar is shared between the two S300's. if we attempt to find a SAM site at a base and can't find one
|
||||||
# model, we can safely assume the other was deployed
|
# model, we can safely assume the other was deployed
|
||||||
# well, perhaps not safely, but we'll make the assumption anyway :p
|
# well, perhaps not safely, but we'll make the assumption anyway :p
|
||||||
AirDefence.SAM_SA_10_S_300PS_TR_30N6: AirDefence.SAM_SA_10_S_300PS_CP_54K6,
|
AirDefence.SAM_SA_10_S_300PS_TR_30N6: AirDefence.SAM_SA_10_S_300PS_CP_54K6,
|
||||||
AirDefence.SAM_SR_P_19: AirDefence.SAM_SA_2_LN_SM_90
|
AirDefence.SAM_SR_P_19: AirDefence.SAM_SA_2_LN_SM_90,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Units that will always be spawned in the air
|
Units that will always be spawned in the air
|
||||||
"""
|
"""
|
||||||
TAKEOFF_BAN: List[Type[FlyingType]] = [
|
TAKEOFF_BAN: List[Type[FlyingType]] = []
|
||||||
]
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Units that will be always spawned in the air if launched from the carrier
|
Units that will be always spawned in the air if launched from the carrier
|
||||||
@ -1140,14 +1179,13 @@ EXPANDED_TASK_PAYLOAD_OVERRIDE = {
|
|||||||
"SEAD": ("SEAD",),
|
"SEAD": ("SEAD",),
|
||||||
"DEAD": ("SEAD",),
|
"DEAD": ("SEAD",),
|
||||||
"ESCORT": ("CAP HEAVY", "CAP"),
|
"ESCORT": ("CAP HEAVY", "CAP"),
|
||||||
"BAI": ( "BAI", "CAS MAVERICK F", "CAS"),
|
"BAI": ("BAI", "CAS MAVERICK F", "CAS"),
|
||||||
"SWEEP": ("CAP HEAVY", "CAP"),
|
"SWEEP": ("CAP HEAVY", "CAP"),
|
||||||
"OCA_RUNWAY": ("RUNWAY_ATTACK","RUNWAY_STRIKE","STRIKE"),
|
"OCA_RUNWAY": ("RUNWAY_ATTACK", "RUNWAY_STRIKE", "STRIKE"),
|
||||||
"OCA_AIRCRAFT": ("OCA","CAS MAVERICK F", "CAS")
|
"OCA_AIRCRAFT": ("OCA", "CAS MAVERICK F", "CAS"),
|
||||||
}
|
}
|
||||||
|
|
||||||
PLANE_PAYLOAD_OVERRIDES: Dict[Type[PlaneType], Dict[Type[Task], str]] = {
|
PLANE_PAYLOAD_OVERRIDES: Dict[Type[PlaneType], Dict[Type[Task], str]] = {
|
||||||
|
|
||||||
B_1B: COMMON_OVERRIDE,
|
B_1B: COMMON_OVERRIDE,
|
||||||
B_52H: COMMON_OVERRIDE,
|
B_52H: COMMON_OVERRIDE,
|
||||||
F_117A: COMMON_OVERRIDE,
|
F_117A: COMMON_OVERRIDE,
|
||||||
@ -1254,11 +1292,9 @@ PLANE_PAYLOAD_OVERRIDES: Dict[Type[PlaneType], Dict[Type[Task], str]] = {
|
|||||||
AH_64A: COMMON_OVERRIDE,
|
AH_64A: COMMON_OVERRIDE,
|
||||||
SH_60B: COMMON_OVERRIDE,
|
SH_60B: COMMON_OVERRIDE,
|
||||||
Hercules: COMMON_OVERRIDE,
|
Hercules: COMMON_OVERRIDE,
|
||||||
|
|
||||||
Su_25TM: {
|
Su_25TM: {
|
||||||
SEAD: "Kh-31P*2_Kh-25ML*4_R-73*2_L-081_MPS410",
|
SEAD: "Kh-31P*2_Kh-25ML*4_R-73*2_L-081_MPS410",
|
||||||
},
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -1319,9 +1355,17 @@ TIME_PERIODS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
REWARDS = {
|
REWARDS = {
|
||||||
"power": 4, "warehouse": 2, "ware": 2, "fuel": 2, "ammo": 2,
|
"power": 4,
|
||||||
"farp": 1, "fob": 1, "factory": 10, "comms": 10, "oil": 10,
|
"warehouse": 2,
|
||||||
"derrick": 8
|
"ware": 2,
|
||||||
|
"fuel": 2,
|
||||||
|
"ammo": 2,
|
||||||
|
"farp": 1,
|
||||||
|
"fob": 1,
|
||||||
|
"factory": 10,
|
||||||
|
"comms": 10,
|
||||||
|
"oil": 10,
|
||||||
|
"derrick": 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
CARRIER_CAPABLE = [
|
CARRIER_CAPABLE = [
|
||||||
@ -1334,7 +1378,6 @@ CARRIER_CAPABLE = [
|
|||||||
Rafale_M,
|
Rafale_M,
|
||||||
S_3B,
|
S_3B,
|
||||||
E_2C,
|
E_2C,
|
||||||
|
|
||||||
UH_1H,
|
UH_1H,
|
||||||
Mi_8MT,
|
Mi_8MT,
|
||||||
Ka_50,
|
Ka_50,
|
||||||
@ -1342,7 +1385,6 @@ CARRIER_CAPABLE = [
|
|||||||
OH_58D,
|
OH_58D,
|
||||||
UH_60A,
|
UH_60A,
|
||||||
SH_60B,
|
SH_60B,
|
||||||
|
|
||||||
SA342L,
|
SA342L,
|
||||||
SA342M,
|
SA342M,
|
||||||
SA342Minigun,
|
SA342Minigun,
|
||||||
@ -1351,7 +1393,6 @@ CARRIER_CAPABLE = [
|
|||||||
|
|
||||||
LHA_CAPABLE = [
|
LHA_CAPABLE = [
|
||||||
AV8BNA,
|
AV8BNA,
|
||||||
|
|
||||||
UH_1H,
|
UH_1H,
|
||||||
Mi_8MT,
|
Mi_8MT,
|
||||||
Ka_50,
|
Ka_50,
|
||||||
@ -1359,11 +1400,10 @@ LHA_CAPABLE = [
|
|||||||
OH_58D,
|
OH_58D,
|
||||||
UH_60A,
|
UH_60A,
|
||||||
SH_60B,
|
SH_60B,
|
||||||
|
|
||||||
SA342L,
|
SA342L,
|
||||||
SA342M,
|
SA342M,
|
||||||
SA342Minigun,
|
SA342Minigun,
|
||||||
SA342Mistral
|
SA342Mistral,
|
||||||
]
|
]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -1422,27 +1462,50 @@ def find_unittype(for_task: Task, country_name: str) -> List[Type[UnitType]]:
|
|||||||
MANPADS: List[VehicleType] = [
|
MANPADS: List[VehicleType] = [
|
||||||
AirDefence.SAM_SA_18_Igla_MANPADS,
|
AirDefence.SAM_SA_18_Igla_MANPADS,
|
||||||
AirDefence.SAM_SA_18_Igla_S_MANPADS,
|
AirDefence.SAM_SA_18_Igla_S_MANPADS,
|
||||||
AirDefence.Stinger_MANPADS
|
AirDefence.Stinger_MANPADS,
|
||||||
]
|
]
|
||||||
|
|
||||||
INFANTRY: List[VehicleType] = [
|
INFANTRY: List[VehicleType] = [
|
||||||
Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS,
|
Infantry.Paratrooper_AKS,
|
||||||
|
Infantry.Paratrooper_AKS,
|
||||||
|
Infantry.Paratrooper_AKS,
|
||||||
|
Infantry.Paratrooper_AKS,
|
||||||
Infantry.Paratrooper_AKS,
|
Infantry.Paratrooper_AKS,
|
||||||
Infantry.Soldier_RPG,
|
Infantry.Soldier_RPG,
|
||||||
Infantry.Infantry_M4, Infantry.Infantry_M4, Infantry.Infantry_M4, Infantry.Infantry_M4, Infantry.Infantry_M4,
|
Infantry.Infantry_M4,
|
||||||
|
Infantry.Infantry_M4,
|
||||||
|
Infantry.Infantry_M4,
|
||||||
|
Infantry.Infantry_M4,
|
||||||
|
Infantry.Infantry_M4,
|
||||||
Infantry.Soldier_M249,
|
Infantry.Soldier_M249,
|
||||||
Artillery._2B11_mortar,
|
Artillery._2B11_mortar,
|
||||||
Infantry.Soldier_AK, Infantry.Soldier_AK, Infantry.Soldier_AK, Infantry.Soldier_AK, Infantry.Soldier_AK,
|
Infantry.Soldier_AK,
|
||||||
|
Infantry.Soldier_AK,
|
||||||
|
Infantry.Soldier_AK,
|
||||||
|
Infantry.Soldier_AK,
|
||||||
|
Infantry.Soldier_AK,
|
||||||
Infantry.Paratrooper_RPG_16,
|
Infantry.Paratrooper_RPG_16,
|
||||||
Infantry.Georgian_soldier_with_M4, Infantry.Georgian_soldier_with_M4, Infantry.Georgian_soldier_with_M4,
|
|
||||||
Infantry.Georgian_soldier_with_M4,
|
Infantry.Georgian_soldier_with_M4,
|
||||||
Infantry.Infantry_Soldier_Rus, Infantry.Infantry_Soldier_Rus, Infantry.Infantry_Soldier_Rus,
|
Infantry.Georgian_soldier_with_M4,
|
||||||
|
Infantry.Georgian_soldier_with_M4,
|
||||||
|
Infantry.Georgian_soldier_with_M4,
|
||||||
Infantry.Infantry_Soldier_Rus,
|
Infantry.Infantry_Soldier_Rus,
|
||||||
Infantry.Infantry_SMLE_No_4_Mk_1, Infantry.Infantry_SMLE_No_4_Mk_1, Infantry.Infantry_SMLE_No_4_Mk_1,
|
Infantry.Infantry_Soldier_Rus,
|
||||||
Infantry.Infantry_Mauser_98, Infantry.Infantry_Mauser_98, Infantry.Infantry_Mauser_98,
|
Infantry.Infantry_Soldier_Rus,
|
||||||
|
Infantry.Infantry_Soldier_Rus,
|
||||||
|
Infantry.Infantry_SMLE_No_4_Mk_1,
|
||||||
|
Infantry.Infantry_SMLE_No_4_Mk_1,
|
||||||
|
Infantry.Infantry_SMLE_No_4_Mk_1,
|
||||||
Infantry.Infantry_Mauser_98,
|
Infantry.Infantry_Mauser_98,
|
||||||
Infantry.Infantry_M1_Garand, Infantry.Infantry_M1_Garand, Infantry.Infantry_M1_Garand,
|
Infantry.Infantry_Mauser_98,
|
||||||
Infantry.Infantry_Soldier_Insurgents, Infantry.Infantry_Soldier_Insurgents, Infantry.Infantry_Soldier_Insurgents
|
Infantry.Infantry_Mauser_98,
|
||||||
|
Infantry.Infantry_Mauser_98,
|
||||||
|
Infantry.Infantry_M1_Garand,
|
||||||
|
Infantry.Infantry_M1_Garand,
|
||||||
|
Infantry.Infantry_M1_Garand,
|
||||||
|
Infantry.Infantry_Soldier_Insurgents,
|
||||||
|
Infantry.Infantry_Soldier_Insurgents,
|
||||||
|
Infantry.Infantry_Soldier_Insurgents,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -1465,6 +1528,7 @@ def unit_type_name(unit_type) -> str:
|
|||||||
def unit_type_name_2(unit_type) -> str:
|
def unit_type_name_2(unit_type) -> str:
|
||||||
return unit_type.name and unit_type.name or unit_type.id
|
return unit_type.name and unit_type.name or unit_type.id
|
||||||
|
|
||||||
|
|
||||||
def unit_get_expanded_info(country_name: str, unit_type, request_type: str) -> str:
|
def unit_get_expanded_info(country_name: str, unit_type, request_type: str) -> str:
|
||||||
original_name = unit_type.name and unit_type.name or unit_type.id
|
original_name = unit_type.name and unit_type.name or unit_type.id
|
||||||
default_value = None
|
default_value = None
|
||||||
@ -1493,6 +1557,7 @@ def unit_get_expanded_info(country_name: str, unit_type, request_type: str) -> s
|
|||||||
return default_value
|
return default_value
|
||||||
return faction_value
|
return faction_value
|
||||||
|
|
||||||
|
|
||||||
def unit_type_from_name(name: str) -> Optional[Type[UnitType]]:
|
def unit_type_from_name(name: str) -> Optional[Type[UnitType]]:
|
||||||
if name in vehicle_map:
|
if name in vehicle_map:
|
||||||
return vehicle_map[name]
|
return vehicle_map[name]
|
||||||
@ -1526,9 +1591,13 @@ def task_name(task) -> str:
|
|||||||
return task.name
|
return task.name
|
||||||
|
|
||||||
|
|
||||||
def choose_units(for_task: Task, factor: float, count: int, country: str) -> List[UnitType]:
|
def choose_units(
|
||||||
|
for_task: Task, factor: float, count: int, country: str
|
||||||
|
) -> List[UnitType]:
|
||||||
suitable_unittypes = find_unittype(for_task, country)
|
suitable_unittypes = find_unittype(for_task, country)
|
||||||
suitable_unittypes = [x for x in suitable_unittypes if x not in helicopter_map.values()]
|
suitable_unittypes = [
|
||||||
|
x for x in suitable_unittypes if x not in helicopter_map.values()
|
||||||
|
]
|
||||||
suitable_unittypes.sort(key=lambda x: PRICES[x])
|
suitable_unittypes.sort(key=lambda x: PRICES[x])
|
||||||
|
|
||||||
idx = int(len(suitable_unittypes) * factor)
|
idx = int(len(suitable_unittypes) * factor)
|
||||||
@ -1576,7 +1645,10 @@ def unitdict_restrict_count(unit_dict: UnitsDict, total_count: int) -> UnitsDict
|
|||||||
|
|
||||||
|
|
||||||
def assigned_units_split(fd: AssignedUnitsDict) -> Tuple[PlaneDict, PlaneDict]:
|
def assigned_units_split(fd: AssignedUnitsDict) -> Tuple[PlaneDict, PlaneDict]:
|
||||||
return {k: v1 for k, (v1, v2) in fd.items()}, {k: v2 for k, (v1, v2) in fd.items()},
|
return (
|
||||||
|
{k: v1 for k, (v1, v2) in fd.items()},
|
||||||
|
{k: v2 for k, (v1, v2) in fd.items()},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def assigned_units_from(d: PlaneDict) -> AssignedUnitsDict:
|
def assigned_units_from(d: PlaneDict) -> AssignedUnitsDict:
|
||||||
@ -1620,7 +1692,9 @@ def _validate_db():
|
|||||||
total_set = set()
|
total_set = set()
|
||||||
for t, unit_collection in UNIT_BY_TASK.items():
|
for t, unit_collection in UNIT_BY_TASK.items():
|
||||||
for unit_type in set(unit_collection):
|
for unit_type in set(unit_collection):
|
||||||
assert unit_type not in total_set, "{} is duplicate for task {}".format(unit_type, t)
|
assert unit_type not in total_set, "{} is duplicate for task {}".format(
|
||||||
|
unit_type, t
|
||||||
|
)
|
||||||
total_set.add(unit_type)
|
total_set.add(unit_type)
|
||||||
|
|
||||||
# check prices
|
# check prices
|
||||||
|
|||||||
@ -96,13 +96,14 @@ class StateData:
|
|||||||
# them when they've already dead. Dedup.
|
# them when they've already dead. Dedup.
|
||||||
killed_ground_units=list(set(data["killed_ground_units"])),
|
killed_ground_units=list(set(data["killed_ground_units"])),
|
||||||
destroyed_statics=data["destroyed_objects_positions"],
|
destroyed_statics=data["destroyed_objects_positions"],
|
||||||
base_capture_events=data["base_capture_events"]
|
base_capture_events=data["base_capture_events"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Debriefing:
|
class Debriefing:
|
||||||
def __init__(self, state_data: Dict[str, Any], game: Game,
|
def __init__(
|
||||||
unit_map: UnitMap) -> None:
|
self, state_data: Dict[str, Any], game: Game, unit_map: UnitMap
|
||||||
|
) -> None:
|
||||||
self.state_data = StateData.from_json(state_data)
|
self.state_data = StateData.from_json(state_data)
|
||||||
self.unit_map = unit_map
|
self.unit_map = unit_map
|
||||||
|
|
||||||
@ -135,12 +136,9 @@ class Debriefing:
|
|||||||
yield from self.ground_losses.enemy_airfields
|
yield from self.ground_losses.enemy_airfields
|
||||||
|
|
||||||
def casualty_count(self, control_point: ControlPoint) -> int:
|
def casualty_count(self, control_point: ControlPoint) -> int:
|
||||||
return len(
|
return len([x for x in self.front_line_losses if x.origin == control_point])
|
||||||
[x for x in self.front_line_losses if x.origin == control_point]
|
|
||||||
)
|
|
||||||
|
|
||||||
def front_line_losses_by_type(
|
def front_line_losses_by_type(self, player: bool) -> Dict[Type[UnitType], int]:
|
||||||
self, player: bool) -> Dict[Type[UnitType], int]:
|
|
||||||
losses_by_type: Dict[Type[UnitType], int] = defaultdict(int)
|
losses_by_type: Dict[Type[UnitType], int] = defaultdict(int)
|
||||||
if player:
|
if player:
|
||||||
losses = self.ground_losses.player_front_line
|
losses = self.ground_losses.player_front_line
|
||||||
@ -221,8 +219,10 @@ class Debriefing:
|
|||||||
# deaths, so we expect to see quite a few unclaimed dead ground
|
# deaths, so we expect to see quite a few unclaimed dead ground
|
||||||
# units. We should start tracking those and covert this to a
|
# units. We should start tracking those and covert this to a
|
||||||
# warning.
|
# warning.
|
||||||
logging.debug(f"Death of untracked ground unit {unit_name} will "
|
logging.debug(
|
||||||
"have no effect. This may be normal behavior.")
|
f"Death of untracked ground unit {unit_name} will "
|
||||||
|
"have no effect. This may be normal behavior."
|
||||||
|
)
|
||||||
|
|
||||||
return losses
|
return losses
|
||||||
|
|
||||||
@ -241,8 +241,9 @@ class PollDebriefingFileThread(threading.Thread):
|
|||||||
"""Thread class with a stop() method. The thread itself has to check
|
"""Thread class with a stop() method. The thread itself has to check
|
||||||
regularly for the stopped() condition."""
|
regularly for the stopped() condition."""
|
||||||
|
|
||||||
def __init__(self, callback: Callable[[Debriefing], None],
|
def __init__(
|
||||||
game: Game, unit_map: UnitMap) -> None:
|
self, callback: Callable[[Debriefing], None], game: Game, unit_map: UnitMap
|
||||||
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._stop_event = threading.Event()
|
self._stop_event = threading.Event()
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
@ -261,7 +262,10 @@ class PollDebriefingFileThread(threading.Thread):
|
|||||||
else:
|
else:
|
||||||
last_modified = 0
|
last_modified = 0
|
||||||
while not self.stopped():
|
while not self.stopped():
|
||||||
if os.path.isfile("state.json") and os.path.getmtime("state.json") > last_modified:
|
if (
|
||||||
|
os.path.isfile("state.json")
|
||||||
|
and os.path.getmtime("state.json") > last_modified
|
||||||
|
):
|
||||||
with open("state.json", "r") as json_file:
|
with open("state.json", "r") as json_file:
|
||||||
json_data = json.load(json_file)
|
json_data = json.load(json_file)
|
||||||
debriefing = Debriefing(json_data, self.game, self.unit_map)
|
debriefing = Debriefing(json_data, self.game, self.unit_map)
|
||||||
@ -270,8 +274,9 @@ class PollDebriefingFileThread(threading.Thread):
|
|||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
|
|
||||||
def wait_for_debriefing(callback: Callable[[Debriefing], None],
|
def wait_for_debriefing(
|
||||||
game: Game, unit_map) -> PollDebriefingFileThread:
|
callback: Callable[[Debriefing], None], game: Game, unit_map
|
||||||
|
) -> PollDebriefingFileThread:
|
||||||
thread = PollDebriefingFileThread(callback, game, unit_map)
|
thread = PollDebriefingFileThread(callback, game, unit_map)
|
||||||
thread.start()
|
thread.start()
|
||||||
return thread
|
return thread
|
||||||
|
|||||||
@ -37,7 +37,15 @@ class Event:
|
|||||||
to_cp = None # type: ControlPoint
|
to_cp = None # type: ControlPoint
|
||||||
difficulty = 1 # type: int
|
difficulty = 1 # type: int
|
||||||
|
|
||||||
def __init__(self, game, from_cp: ControlPoint, target_cp: ControlPoint, location: Point, attacker_name: str, defender_name: str):
|
def __init__(
|
||||||
|
self,
|
||||||
|
game,
|
||||||
|
from_cp: ControlPoint,
|
||||||
|
target_cp: ControlPoint,
|
||||||
|
location: Point,
|
||||||
|
attacker_name: str,
|
||||||
|
defender_name: str,
|
||||||
|
):
|
||||||
self.game = game
|
self.game = game
|
||||||
self.from_cp = from_cp
|
self.from_cp = from_cp
|
||||||
self.to_cp = target_cp
|
self.to_cp = target_cp
|
||||||
@ -57,12 +65,14 @@ class Event:
|
|||||||
Operation.prepare(self.game)
|
Operation.prepare(self.game)
|
||||||
unit_map = Operation.generate()
|
unit_map = Operation.generate()
|
||||||
Operation.current_mission.save(
|
Operation.current_mission.save(
|
||||||
persistency.mission_path_for("liberation_nextturn.miz"))
|
persistency.mission_path_for("liberation_nextturn.miz")
|
||||||
|
)
|
||||||
return unit_map
|
return unit_map
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _transfer_aircraft(ato: AirTaskingOrder, losses: AirLosses,
|
def _transfer_aircraft(
|
||||||
for_player: bool) -> None:
|
ato: AirTaskingOrder, losses: AirLosses, for_player: bool
|
||||||
|
) -> None:
|
||||||
for package in ato.packages:
|
for package in ato.packages:
|
||||||
for flight in package.flights:
|
for flight in package.flights:
|
||||||
# No need to transfer to the same location.
|
# No need to transfer to the same location.
|
||||||
@ -77,13 +87,16 @@ class Event:
|
|||||||
if flight.arrival.captured != for_player:
|
if flight.arrival.captured != for_player:
|
||||||
logging.info(
|
logging.info(
|
||||||
f"Not transferring {flight} because {flight.arrival} "
|
f"Not transferring {flight} because {flight.arrival} "
|
||||||
"was captured")
|
"was captured"
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
transfer_count = losses.surviving_flight_members(flight)
|
transfer_count = losses.surviving_flight_members(flight)
|
||||||
if transfer_count < 0:
|
if transfer_count < 0:
|
||||||
logging.error(f"{flight} had {flight.count} aircraft but "
|
logging.error(
|
||||||
f"{transfer_count} losses were recorded.")
|
f"{flight} had {flight.count} aircraft but "
|
||||||
|
f"{transfer_count} losses were recorded."
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
aircraft = flight.unit_type
|
aircraft = flight.unit_type
|
||||||
@ -91,7 +104,8 @@ class Event:
|
|||||||
if available < transfer_count:
|
if available < transfer_count:
|
||||||
logging.error(
|
logging.error(
|
||||||
f"Found killed {aircraft} from {flight.departure} but "
|
f"Found killed {aircraft} from {flight.departure} but "
|
||||||
f"that airbase has only {available} available.")
|
f"that airbase has only {available} available."
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
flight.departure.base.aircraft[aircraft] -= transfer_count
|
flight.departure.base.aircraft[aircraft] -= transfer_count
|
||||||
@ -101,10 +115,12 @@ class Event:
|
|||||||
flight.arrival.base.aircraft[aircraft] += transfer_count
|
flight.arrival.base.aircraft[aircraft] += transfer_count
|
||||||
|
|
||||||
def complete_aircraft_transfers(self, debriefing: Debriefing) -> None:
|
def complete_aircraft_transfers(self, debriefing: Debriefing) -> None:
|
||||||
self._transfer_aircraft(self.game.blue_ato, debriefing.air_losses,
|
self._transfer_aircraft(
|
||||||
for_player=True)
|
self.game.blue_ato, debriefing.air_losses, for_player=True
|
||||||
self._transfer_aircraft(self.game.red_ato, debriefing.air_losses,
|
)
|
||||||
for_player=False)
|
self._transfer_aircraft(
|
||||||
|
self.game.red_ato, debriefing.air_losses, for_player=False
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def commit_air_losses(debriefing: Debriefing) -> None:
|
def commit_air_losses(debriefing: Debriefing) -> None:
|
||||||
@ -115,7 +131,8 @@ class Event:
|
|||||||
if available <= 0:
|
if available <= 0:
|
||||||
logging.error(
|
logging.error(
|
||||||
f"Found killed {aircraft} from {cp} but that airbase has "
|
f"Found killed {aircraft} from {cp} but that airbase has "
|
||||||
"none available.")
|
"none available."
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
logging.info(f"{aircraft} destroyed from {cp}")
|
logging.info(f"{aircraft} destroyed from {cp}")
|
||||||
@ -130,7 +147,8 @@ class Event:
|
|||||||
if available <= 0:
|
if available <= 0:
|
||||||
logging.error(
|
logging.error(
|
||||||
f"Found killed {unit_type} from {control_point} but that "
|
f"Found killed {unit_type} from {control_point} but that "
|
||||||
"airbase has none available.")
|
"airbase has none available."
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
logging.info(f"{unit_type} destroyed from {control_point}")
|
logging.info(f"{unit_type} destroyed from {control_point}")
|
||||||
@ -149,11 +167,14 @@ class Event:
|
|||||||
def commit_building_losses(self, debriefing: Debriefing) -> None:
|
def commit_building_losses(self, debriefing: Debriefing) -> None:
|
||||||
for loss in debriefing.building_losses:
|
for loss in debriefing.building_losses:
|
||||||
loss.ground_object.kill()
|
loss.ground_object.kill()
|
||||||
self.game.informations.append(Information(
|
self.game.informations.append(
|
||||||
|
Information(
|
||||||
"Building destroyed",
|
"Building destroyed",
|
||||||
f"{loss.ground_object.dcs_identifier} has been destroyed at "
|
f"{loss.ground_object.dcs_identifier} has been destroyed at "
|
||||||
f"location {loss.ground_object.obj_name}", self.game.turn
|
f"location {loss.ground_object.obj_name}",
|
||||||
))
|
self.game.turn,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def commit_damaged_runways(debriefing: Debriefing) -> None:
|
def commit_damaged_runways(debriefing: Debriefing) -> None:
|
||||||
@ -171,9 +192,9 @@ class Event:
|
|||||||
|
|
||||||
# ------------------------------
|
# ------------------------------
|
||||||
# Captured bases
|
# Captured bases
|
||||||
#if self.game.player_country in db.BLUEFOR_FACTIONS:
|
# if self.game.player_country in db.BLUEFOR_FACTIONS:
|
||||||
coalition = 2 # Value in DCS mission event for BLUE
|
coalition = 2 # Value in DCS mission event for BLUE
|
||||||
#else:
|
# else:
|
||||||
# coalition = 1 # Value in DCS mission event for RED
|
# coalition = 1 # Value in DCS mission event for RED
|
||||||
|
|
||||||
for captured in debriefing.base_capture_events:
|
for captured in debriefing.base_capture_events:
|
||||||
@ -187,12 +208,22 @@ class Event:
|
|||||||
|
|
||||||
if cp.captured and new_owner_coalition != coalition:
|
if cp.captured and new_owner_coalition != coalition:
|
||||||
for_player = False
|
for_player = False
|
||||||
info = Information(cp.name + " lost !", "The ennemy took control of " + cp.name + "\nShame on us !", self.game.turn)
|
info = Information(
|
||||||
|
cp.name + " lost !",
|
||||||
|
"The ennemy took control of "
|
||||||
|
+ cp.name
|
||||||
|
+ "\nShame on us !",
|
||||||
|
self.game.turn,
|
||||||
|
)
|
||||||
self.game.informations.append(info)
|
self.game.informations.append(info)
|
||||||
captured_cps.append(cp)
|
captured_cps.append(cp)
|
||||||
elif not(cp.captured) and new_owner_coalition == coalition:
|
elif not (cp.captured) and new_owner_coalition == coalition:
|
||||||
for_player = True
|
for_player = True
|
||||||
info = Information(cp.name + " captured !", "We took control of " + cp.name + "! Great job !", self.game.turn)
|
info = Information(
|
||||||
|
cp.name + " captured !",
|
||||||
|
"We took control of " + cp.name + "! Great job !",
|
||||||
|
self.game.turn,
|
||||||
|
)
|
||||||
self.game.informations.append(info)
|
self.game.informations.append(info)
|
||||||
captured_cps.append(cp)
|
captured_cps.append(cp)
|
||||||
else:
|
else:
|
||||||
@ -218,7 +249,12 @@ class Event:
|
|||||||
for cp in self.game.theater.player_points():
|
for cp in self.game.theater.player_points():
|
||||||
enemy_cps = [e for e in cp.connected_points if not e.captured]
|
enemy_cps = [e for e in cp.connected_points if not e.captured]
|
||||||
for enemy_cp in enemy_cps:
|
for enemy_cp in enemy_cps:
|
||||||
print("Compute frontline progression for : " + cp.name + " to " + enemy_cp.name)
|
print(
|
||||||
|
"Compute frontline progression for : "
|
||||||
|
+ cp.name
|
||||||
|
+ " to "
|
||||||
|
+ enemy_cp.name
|
||||||
|
)
|
||||||
|
|
||||||
delta = 0.0
|
delta = 0.0
|
||||||
player_won = True
|
player_won = True
|
||||||
@ -234,7 +270,11 @@ class Event:
|
|||||||
|
|
||||||
ratio = (1.0 + enemy_casualties) / (1.0 + ally_casualties)
|
ratio = (1.0 + enemy_casualties) / (1.0 + ally_casualties)
|
||||||
|
|
||||||
player_aggresive = cp.stances[enemy_cp.id] in [CombatStance.AGGRESSIVE, CombatStance.ELIMINATION, CombatStance.BREAKTHROUGH]
|
player_aggresive = cp.stances[enemy_cp.id] in [
|
||||||
|
CombatStance.AGGRESSIVE,
|
||||||
|
CombatStance.ELIMINATION,
|
||||||
|
CombatStance.BREAKTHROUGH,
|
||||||
|
]
|
||||||
|
|
||||||
if ally_units_alive == 0:
|
if ally_units_alive == 0:
|
||||||
player_won = False
|
player_won = False
|
||||||
@ -259,11 +299,17 @@ class Event:
|
|||||||
delta = DEFEAT_INFLUENCE
|
delta = DEFEAT_INFLUENCE
|
||||||
elif ally_casualties > enemy_casualties:
|
elif ally_casualties > enemy_casualties:
|
||||||
|
|
||||||
if ally_units_alive > 2*enemy_units_alive and player_aggresive:
|
if (
|
||||||
|
ally_units_alive > 2 * enemy_units_alive
|
||||||
|
and player_aggresive
|
||||||
|
):
|
||||||
# Even with casualties if the enemy is overwhelmed, they are going to lose ground
|
# Even with casualties if the enemy is overwhelmed, they are going to lose ground
|
||||||
player_won = True
|
player_won = True
|
||||||
delta = MINOR_DEFEAT_INFLUENCE
|
delta = MINOR_DEFEAT_INFLUENCE
|
||||||
elif ally_units_alive > 3*enemy_units_alive and player_aggresive:
|
elif (
|
||||||
|
ally_units_alive > 3 * enemy_units_alive
|
||||||
|
and player_aggresive
|
||||||
|
):
|
||||||
player_won = True
|
player_won = True
|
||||||
delta = STRONG_DEFEAT_INFLUENCE
|
delta = STRONG_DEFEAT_INFLUENCE
|
||||||
else:
|
else:
|
||||||
@ -275,7 +321,10 @@ class Event:
|
|||||||
delta = STRONG_DEFEAT_INFLUENCE
|
delta = STRONG_DEFEAT_INFLUENCE
|
||||||
|
|
||||||
# No progress with defensive strategies
|
# No progress with defensive strategies
|
||||||
if player_won and cp.stances[enemy_cp.id] in [CombatStance.DEFENSIVE, CombatStance.AMBUSH]:
|
if player_won and cp.stances[enemy_cp.id] in [
|
||||||
|
CombatStance.DEFENSIVE,
|
||||||
|
CombatStance.AMBUSH,
|
||||||
|
]:
|
||||||
print("Defensive stance, progress is limited")
|
print("Defensive stance, progress is limited")
|
||||||
delta = MINOR_DEFEAT_INFLUENCE
|
delta = MINOR_DEFEAT_INFLUENCE
|
||||||
|
|
||||||
@ -283,28 +332,40 @@ class Event:
|
|||||||
print(cp.name + " won ! factor > " + str(delta))
|
print(cp.name + " won ! factor > " + str(delta))
|
||||||
cp.base.affect_strength(delta)
|
cp.base.affect_strength(delta)
|
||||||
enemy_cp.base.affect_strength(-delta)
|
enemy_cp.base.affect_strength(-delta)
|
||||||
info = Information("Frontline Report",
|
info = Information(
|
||||||
"Our ground forces from " + cp.name + " are making progress toward " + enemy_cp.name,
|
"Frontline Report",
|
||||||
self.game.turn)
|
"Our ground forces from "
|
||||||
|
+ cp.name
|
||||||
|
+ " are making progress toward "
|
||||||
|
+ enemy_cp.name,
|
||||||
|
self.game.turn,
|
||||||
|
)
|
||||||
self.game.informations.append(info)
|
self.game.informations.append(info)
|
||||||
else:
|
else:
|
||||||
print(cp.name + " lost ! factor > " + str(delta))
|
print(cp.name + " lost ! factor > " + str(delta))
|
||||||
enemy_cp.base.affect_strength(delta)
|
enemy_cp.base.affect_strength(delta)
|
||||||
cp.base.affect_strength(-delta)
|
cp.base.affect_strength(-delta)
|
||||||
info = Information("Frontline Report",
|
info = Information(
|
||||||
"Our ground forces from " + cp.name + " are losing ground against the enemy forces from " + enemy_cp.name,
|
"Frontline Report",
|
||||||
self.game.turn)
|
"Our ground forces from "
|
||||||
|
+ cp.name
|
||||||
|
+ " are losing ground against the enemy forces from "
|
||||||
|
+ enemy_cp.name,
|
||||||
|
self.game.turn,
|
||||||
|
)
|
||||||
self.game.informations.append(info)
|
self.game.informations.append(info)
|
||||||
|
|
||||||
def redeploy_units(self, cp: ControlPoint) -> None:
|
def redeploy_units(self, cp: ControlPoint) -> None:
|
||||||
""""
|
""" "
|
||||||
Auto redeploy units to newly captured base
|
Auto redeploy units to newly captured base
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ally_connected_cps = [ocp for ocp in cp.connected_points if
|
ally_connected_cps = [
|
||||||
cp.captured == ocp.captured]
|
ocp for ocp in cp.connected_points if cp.captured == ocp.captured
|
||||||
enemy_connected_cps = [ocp for ocp in cp.connected_points if
|
]
|
||||||
cp.captured != ocp.captured]
|
enemy_connected_cps = [
|
||||||
|
ocp for ocp in cp.connected_points if cp.captured != ocp.captured
|
||||||
|
]
|
||||||
|
|
||||||
# If the newly captured cp does not have enemy connected cp,
|
# If the newly captured cp does not have enemy connected cp,
|
||||||
# then it is not necessary to redeploy frontline units there.
|
# then it is not necessary to redeploy frontline units there.
|
||||||
@ -315,8 +376,7 @@ class Event:
|
|||||||
for ally_cp in ally_connected_cps:
|
for ally_cp in ally_connected_cps:
|
||||||
self.redeploy_between(cp, ally_cp)
|
self.redeploy_between(cp, ally_cp)
|
||||||
|
|
||||||
def redeploy_between(self, destination: ControlPoint,
|
def redeploy_between(self, destination: ControlPoint, source: ControlPoint) -> None:
|
||||||
source: ControlPoint) -> None:
|
|
||||||
total_units_redeployed = 0
|
total_units_redeployed = 0
|
||||||
moved_units = {}
|
moved_units = {}
|
||||||
|
|
||||||
@ -333,8 +393,7 @@ class Event:
|
|||||||
|
|
||||||
for frontline_unit, count in source.base.armor.items():
|
for frontline_unit, count in source.base.armor.items():
|
||||||
moved_units[frontline_unit] = int(count * move_factor)
|
moved_units[frontline_unit] = int(count * move_factor)
|
||||||
total_units_redeployed = total_units_redeployed + int(
|
total_units_redeployed = total_units_redeployed + int(count * move_factor)
|
||||||
count * move_factor)
|
|
||||||
|
|
||||||
destination.base.commision_units(moved_units)
|
destination.base.commision_units(moved_units)
|
||||||
source.base.commit_losses(moved_units)
|
source.base.commit_losses(moved_units)
|
||||||
@ -362,7 +421,6 @@ class Event:
|
|||||||
|
|
||||||
|
|
||||||
class UnitsDeliveryEvent:
|
class UnitsDeliveryEvent:
|
||||||
|
|
||||||
def __init__(self, control_point: ControlPoint) -> None:
|
def __init__(self, control_point: ControlPoint) -> None:
|
||||||
self.to_cp = control_point
|
self.to_cp = control_point
|
||||||
self.units: Dict[Type[UnitType], int] = {}
|
self.units: Dict[Type[UnitType], int] = {}
|
||||||
@ -390,8 +448,7 @@ class UnitsDeliveryEvent:
|
|||||||
logging.error(f"Could not refund {unit_type.id}, price unknown")
|
logging.error(f"Could not refund {unit_type.id}, price unknown")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
logging.info(
|
logging.info(f"Refunding {count} {unit_type.id} at {self.to_cp.name}")
|
||||||
f"Refunding {count} {unit_type.id} at {self.to_cp.name}")
|
|
||||||
game.adjust_budget(price * count, player=self.to_cp.captured)
|
game.adjust_budget(price * count, player=self.to_cp.captured)
|
||||||
|
|
||||||
def available_next_turn(self, unit_type: Type[UnitType]) -> int:
|
def available_next_turn(self, unit_type: Type[UnitType]) -> int:
|
||||||
@ -411,11 +468,11 @@ class UnitsDeliveryEvent:
|
|||||||
if count >= 0:
|
if count >= 0:
|
||||||
bought_units[unit_type] = count
|
bought_units[unit_type] = count
|
||||||
game.message(
|
game.message(
|
||||||
f"{coalition} reinforcements: {aircraft} x {count} at {name}")
|
f"{coalition} reinforcements: {aircraft} x {count} at {name}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
sold_units[unit_type] = -count
|
sold_units[unit_type] = -count
|
||||||
game.message(
|
game.message(f"{coalition} sold: {aircraft} x {-count} at {name}")
|
||||||
f"{coalition} sold: {aircraft} x {-count} at {name}")
|
|
||||||
self.to_cp.base.commision_units(bought_units)
|
self.to_cp.base.commision_units(bought_units)
|
||||||
self.to_cp.base.commit_losses(sold_units)
|
self.to_cp.base.commit_losses(sold_units)
|
||||||
self.units = {}
|
self.units = {}
|
||||||
|
|||||||
@ -7,5 +7,6 @@ class FrontlineAttackEvent(Event):
|
|||||||
Currently the same as its parent, but here for legacy compatibility as well as to allow for
|
Currently the same as its parent, but here for legacy compatibility as well as to allow for
|
||||||
future unique Event handling
|
future unique Event handling
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Frontline attack"
|
return "Frontline attack"
|
||||||
|
|||||||
@ -10,8 +10,18 @@ from dcs.planes import plane_map
|
|||||||
from dcs.unittype import FlyingType, ShipType, VehicleType, UnitType
|
from dcs.unittype import FlyingType, ShipType, VehicleType, UnitType
|
||||||
from dcs.vehicles import Armor, Unarmed, Infantry, Artillery, AirDefence
|
from dcs.vehicles import Armor, Unarmed, Infantry, Artillery, AirDefence
|
||||||
|
|
||||||
from game.data.building_data import WW2_ALLIES_BUILDINGS, DEFAULT_AVAILABLE_BUILDINGS, WW2_GERMANY_BUILDINGS, WW2_FREE
|
from game.data.building_data import (
|
||||||
from game.data.doctrine import Doctrine, MODERN_DOCTRINE, COLDWAR_DOCTRINE, WWII_DOCTRINE
|
WW2_ALLIES_BUILDINGS,
|
||||||
|
DEFAULT_AVAILABLE_BUILDINGS,
|
||||||
|
WW2_GERMANY_BUILDINGS,
|
||||||
|
WW2_FREE,
|
||||||
|
)
|
||||||
|
from game.data.doctrine import (
|
||||||
|
Doctrine,
|
||||||
|
MODERN_DOCTRINE,
|
||||||
|
COLDWAR_DOCTRINE,
|
||||||
|
WWII_DOCTRINE,
|
||||||
|
)
|
||||||
from pydcs_extensions.mod_units import MODDED_VEHICLES, MODDED_AIRPLANES
|
from pydcs_extensions.mod_units import MODDED_VEHICLES, MODDED_AIRPLANES
|
||||||
|
|
||||||
|
|
||||||
@ -109,8 +119,7 @@ class Faction:
|
|||||||
building_set: List[str] = field(default_factory=list)
|
building_set: List[str] = field(default_factory=list)
|
||||||
|
|
||||||
# List of default livery overrides
|
# List of default livery overrides
|
||||||
liveries_overrides: Dict[Type[UnitType], List[str]] = field(
|
liveries_overrides: Dict[Type[UnitType], List[str]] = field(default_factory=dict)
|
||||||
default_factory=dict)
|
|
||||||
|
|
||||||
#: Set to True if the faction should force the "Unrestricted satnav" option
|
#: Set to True if the faction should force the "Unrestricted satnav" option
|
||||||
#: for the mission. This option enables GPS for capable aircraft regardless
|
#: for the mission. This option enables GPS for capable aircraft regardless
|
||||||
@ -128,7 +137,11 @@ class Faction:
|
|||||||
|
|
||||||
faction.country = json.get("country", "/")
|
faction.country = json.get("country", "/")
|
||||||
if faction.country not in [c.name for c in country_dict.values()]:
|
if faction.country not in [c.name for c in country_dict.values()]:
|
||||||
raise AssertionError("Faction's country (\"{}\") is not a valid DCS country ID".format(faction.country))
|
raise AssertionError(
|
||||||
|
'Faction\'s country ("{}") is not a valid DCS country ID'.format(
|
||||||
|
faction.country
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
faction.name = json.get("name", "")
|
faction.name = json.get("name", "")
|
||||||
if not faction.name:
|
if not faction.name:
|
||||||
@ -141,14 +154,10 @@ class Faction:
|
|||||||
faction.awacs = load_all_aircraft(json.get("awacs", []))
|
faction.awacs = load_all_aircraft(json.get("awacs", []))
|
||||||
faction.tankers = load_all_aircraft(json.get("tankers", []))
|
faction.tankers = load_all_aircraft(json.get("tankers", []))
|
||||||
|
|
||||||
faction.frontline_units = load_all_vehicles(
|
faction.frontline_units = load_all_vehicles(json.get("frontline_units", []))
|
||||||
json.get("frontline_units", []))
|
faction.artillery_units = load_all_vehicles(json.get("artillery_units", []))
|
||||||
faction.artillery_units = load_all_vehicles(
|
faction.infantry_units = load_all_vehicles(json.get("infantry_units", []))
|
||||||
json.get("artillery_units", []))
|
faction.logistics_units = load_all_vehicles(json.get("logistics_units", []))
|
||||||
faction.infantry_units = load_all_vehicles(
|
|
||||||
json.get("infantry_units", []))
|
|
||||||
faction.logistics_units = load_all_vehicles(
|
|
||||||
json.get("logistics_units", []))
|
|
||||||
|
|
||||||
faction.ewrs = json.get("ewrs", [])
|
faction.ewrs = json.get("ewrs", [])
|
||||||
|
|
||||||
@ -163,13 +172,10 @@ class Faction:
|
|||||||
faction.requirements = json.get("requirements", {})
|
faction.requirements = json.get("requirements", {})
|
||||||
|
|
||||||
faction.carrier_names = json.get("carrier_names", [])
|
faction.carrier_names = json.get("carrier_names", [])
|
||||||
faction.helicopter_carrier_names = json.get(
|
faction.helicopter_carrier_names = json.get("helicopter_carrier_names", [])
|
||||||
"helicopter_carrier_names", [])
|
|
||||||
faction.navy_generators = json.get("navy_generators", [])
|
faction.navy_generators = json.get("navy_generators", [])
|
||||||
faction.aircraft_carrier = load_all_ships(
|
faction.aircraft_carrier = load_all_ships(json.get("aircraft_carrier", []))
|
||||||
json.get("aircraft_carrier", []))
|
faction.helicopter_carrier = load_all_ships(json.get("helicopter_carrier", []))
|
||||||
faction.helicopter_carrier = load_all_ships(
|
|
||||||
json.get("helicopter_carrier", []))
|
|
||||||
faction.destroyers = load_all_ships(json.get("destroyers", []))
|
faction.destroyers = load_all_ships(json.get("destroyers", []))
|
||||||
faction.cruisers = load_all_ships(json.get("cruisers", []))
|
faction.cruisers = load_all_ships(json.get("cruisers", []))
|
||||||
faction.has_jtac = json.get("has_jtac", False)
|
faction.has_jtac = json.get("has_jtac", False)
|
||||||
@ -220,13 +226,18 @@ class Faction:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def units(self) -> List[Type[UnitType]]:
|
def units(self) -> List[Type[UnitType]]:
|
||||||
return (self.infantry_units + self.aircrafts + self.awacs +
|
return (
|
||||||
self.artillery_units + self.frontline_units +
|
self.infantry_units
|
||||||
self.tankers + self.logistics_units)
|
+ self.aircrafts
|
||||||
|
+ self.awacs
|
||||||
|
+ self.artillery_units
|
||||||
|
+ self.frontline_units
|
||||||
|
+ self.tankers
|
||||||
|
+ self.logistics_units
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def unit_loader(
|
def unit_loader(unit: str, class_repository: List[Any]) -> Optional[Type[UnitType]]:
|
||||||
unit: str, class_repository: List[Any]) -> Optional[Type[UnitType]]:
|
|
||||||
"""
|
"""
|
||||||
Find unit by name
|
Find unit by name
|
||||||
:param unit: Unit name as string
|
:param unit: Unit name as string
|
||||||
@ -250,9 +261,10 @@ def unit_loader(
|
|||||||
|
|
||||||
|
|
||||||
def load_aircraft(name: str) -> Optional[Type[FlyingType]]:
|
def load_aircraft(name: str) -> Optional[Type[FlyingType]]:
|
||||||
return cast(Optional[FlyingType], unit_loader(
|
return cast(
|
||||||
name, [dcs.planes, dcs.helicopters, MODDED_AIRPLANES]
|
Optional[FlyingType],
|
||||||
))
|
unit_loader(name, [dcs.planes, dcs.helicopters, MODDED_AIRPLANES]),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def load_all_aircraft(data) -> List[Type[FlyingType]]:
|
def load_all_aircraft(data) -> List[Type[FlyingType]]:
|
||||||
@ -265,9 +277,12 @@ def load_all_aircraft(data) -> List[Type[FlyingType]]:
|
|||||||
|
|
||||||
|
|
||||||
def load_vehicle(name: str) -> Optional[Type[VehicleType]]:
|
def load_vehicle(name: str) -> Optional[Type[VehicleType]]:
|
||||||
return cast(Optional[FlyingType], unit_loader(
|
return cast(
|
||||||
|
Optional[FlyingType],
|
||||||
|
unit_loader(
|
||||||
name, [Infantry, Unarmed, Armor, AirDefence, Artillery, MODDED_VEHICLES]
|
name, [Infantry, Unarmed, Armor, AirDefence, Artillery, MODDED_VEHICLES]
|
||||||
))
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def load_all_vehicles(data) -> List[Type[VehicleType]]:
|
def load_all_vehicles(data) -> List[Type[VehicleType]]:
|
||||||
|
|||||||
102
game/game.py
102
game/game.py
@ -78,10 +78,16 @@ class TurnState(Enum):
|
|||||||
|
|
||||||
|
|
||||||
class Game:
|
class Game:
|
||||||
def __init__(self, player_name: str, enemy_name: str,
|
def __init__(
|
||||||
theater: ConflictTheater, start_date: datetime,
|
self,
|
||||||
settings: Settings, player_budget: float,
|
player_name: str,
|
||||||
enemy_budget: float) -> None:
|
enemy_name: str,
|
||||||
|
theater: ConflictTheater,
|
||||||
|
start_date: datetime,
|
||||||
|
settings: Settings,
|
||||||
|
player_budget: float,
|
||||||
|
enemy_budget: float,
|
||||||
|
) -> None:
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
self.events: List[Event] = []
|
self.events: List[Event] = []
|
||||||
self.theater = theater
|
self.theater = theater
|
||||||
@ -112,9 +118,7 @@ class Game:
|
|||||||
self.blue_ato = AirTaskingOrder()
|
self.blue_ato = AirTaskingOrder()
|
||||||
self.red_ato = AirTaskingOrder()
|
self.red_ato = AirTaskingOrder()
|
||||||
|
|
||||||
self.aircraft_inventory = GlobalAircraftInventory(
|
self.aircraft_inventory = GlobalAircraftInventory(self.theater.controlpoints)
|
||||||
self.theater.controlpoints
|
|
||||||
)
|
|
||||||
|
|
||||||
self.sanitize_sides()
|
self.sanitize_sides()
|
||||||
|
|
||||||
@ -147,8 +151,9 @@ class Game:
|
|||||||
self.on_load()
|
self.on_load()
|
||||||
|
|
||||||
def generate_conditions(self) -> Conditions:
|
def generate_conditions(self) -> Conditions:
|
||||||
return Conditions.generate(self.theater, self.date,
|
return Conditions.generate(
|
||||||
self.current_turn_time_of_day, self.settings)
|
self.theater, self.date, self.current_turn_time_of_day, self.settings
|
||||||
|
)
|
||||||
|
|
||||||
def sanitize_sides(self):
|
def sanitize_sides(self):
|
||||||
"""
|
"""
|
||||||
@ -184,13 +189,24 @@ class Game:
|
|||||||
return random.randint(1, 100) <= prob * mult
|
return random.randint(1, 100) <= prob * mult
|
||||||
|
|
||||||
def _generate_player_event(self, event_class, player_cp, enemy_cp):
|
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_name, self.enemy_name))
|
self.events.append(
|
||||||
|
event_class(
|
||||||
|
self,
|
||||||
|
player_cp,
|
||||||
|
enemy_cp,
|
||||||
|
enemy_cp.position,
|
||||||
|
self.player_name,
|
||||||
|
self.enemy_name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def _generate_events(self):
|
def _generate_events(self):
|
||||||
for front_line in self.theater.conflicts(True):
|
for front_line in self.theater.conflicts(True):
|
||||||
self._generate_player_event(FrontlineAttackEvent,
|
self._generate_player_event(
|
||||||
|
FrontlineAttackEvent,
|
||||||
front_line.control_point_a,
|
front_line.control_point_a,
|
||||||
front_line.control_point_b)
|
front_line.control_point_b,
|
||||||
|
)
|
||||||
|
|
||||||
def adjust_budget(self, amount: float, player: bool) -> None:
|
def adjust_budget(self, amount: float, player: bool) -> None:
|
||||||
if player:
|
if player:
|
||||||
@ -208,7 +224,7 @@ class Game:
|
|||||||
self.enemy_budget += Income(self, player=False).total
|
self.enemy_budget += Income(self, player=False).total
|
||||||
|
|
||||||
def initiate_event(self, event: Event) -> UnitMap:
|
def initiate_event(self, event: Event) -> UnitMap:
|
||||||
#assert event in self.events
|
# assert event in self.events
|
||||||
logging.info("Generating {} (regular)".format(event))
|
logging.info("Generating {} (regular)".format(event))
|
||||||
return event.generate()
|
return event.generate()
|
||||||
|
|
||||||
@ -223,7 +239,11 @@ class Game:
|
|||||||
|
|
||||||
def is_player_attack(self, event):
|
def is_player_attack(self, event):
|
||||||
if isinstance(event, Event):
|
if isinstance(event, Event):
|
||||||
return event and event.attacker_name and event.attacker_name == self.player_name
|
return (
|
||||||
|
event
|
||||||
|
and event.attacker_name
|
||||||
|
and event.attacker_name == self.player_name
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise RuntimeError(f"{event} was passed when an Event type was expected")
|
raise RuntimeError(f"{event} was passed when an Event type was expected")
|
||||||
|
|
||||||
@ -235,7 +255,9 @@ class Game:
|
|||||||
|
|
||||||
def pass_turn(self, no_action: bool = False) -> None:
|
def pass_turn(self, no_action: bool = False) -> None:
|
||||||
logging.info("Pass turn")
|
logging.info("Pass turn")
|
||||||
self.informations.append(Information("End of turn #" + str(self.turn), "-" * 40, 0))
|
self.informations.append(
|
||||||
|
Information("End of turn #" + str(self.turn), "-" * 40, 0)
|
||||||
|
)
|
||||||
self.turn += 1
|
self.turn += 1
|
||||||
|
|
||||||
for control_point in self.theater.controlpoints:
|
for control_point in self.theater.controlpoints:
|
||||||
@ -281,7 +303,7 @@ class Game:
|
|||||||
|
|
||||||
# Check for win or loss condition
|
# Check for win or loss condition
|
||||||
turn_state = self.check_win_loss()
|
turn_state = self.check_win_loss()
|
||||||
if turn_state in (TurnState.LOSS,TurnState.WIN):
|
if turn_state in (TurnState.LOSS, TurnState.WIN):
|
||||||
return self.process_win_loss(turn_state)
|
return self.process_win_loss(turn_state)
|
||||||
|
|
||||||
# Plan flights & combat for next turn
|
# Plan flights & combat for next turn
|
||||||
@ -305,8 +327,11 @@ class Game:
|
|||||||
|
|
||||||
self.plan_procurement(blue_planner, red_planner)
|
self.plan_procurement(blue_planner, red_planner)
|
||||||
|
|
||||||
def plan_procurement(self, blue_planner: CoalitionMissionPlanner,
|
def plan_procurement(
|
||||||
red_planner: CoalitionMissionPlanner) -> None:
|
self,
|
||||||
|
blue_planner: CoalitionMissionPlanner,
|
||||||
|
red_planner: CoalitionMissionPlanner,
|
||||||
|
) -> None:
|
||||||
# The first turn needs to buy a *lot* of aircraft to fill CAPs, so it
|
# The first turn needs to buy a *lot* of aircraft to fill CAPs, so it
|
||||||
# gets much more of the budget that turn. Otherwise budget (after
|
# gets much more of the budget that turn. Otherwise budget (after
|
||||||
# repairs) is split evenly between air and ground. For the default
|
# repairs) is split evenly between air and ground. For the default
|
||||||
@ -320,7 +345,7 @@ class Game:
|
|||||||
manage_runways=self.settings.automate_runway_repair,
|
manage_runways=self.settings.automate_runway_repair,
|
||||||
manage_front_line=self.settings.automate_front_line_reinforcements,
|
manage_front_line=self.settings.automate_front_line_reinforcements,
|
||||||
manage_aircraft=self.settings.automate_aircraft_reinforcements,
|
manage_aircraft=self.settings.automate_aircraft_reinforcements,
|
||||||
front_line_budget_share=ground_portion
|
front_line_budget_share=ground_portion,
|
||||||
).spend_budget(self.budget, blue_planner.procurement_requests)
|
).spend_budget(self.budget, blue_planner.procurement_requests)
|
||||||
|
|
||||||
self.enemy_budget = ProcurementAi(
|
self.enemy_budget = ProcurementAi(
|
||||||
@ -330,7 +355,7 @@ class Game:
|
|||||||
manage_runways=True,
|
manage_runways=True,
|
||||||
manage_front_line=True,
|
manage_front_line=True,
|
||||||
manage_aircraft=True,
|
manage_aircraft=True,
|
||||||
front_line_budget_share=ground_portion
|
front_line_budget_share=ground_portion,
|
||||||
).spend_budget(self.enemy_budget, red_planner.procurement_requests)
|
).spend_budget(self.enemy_budget, red_planner.procurement_requests)
|
||||||
|
|
||||||
def message(self, text: str) -> None:
|
def message(self, text: str) -> None:
|
||||||
@ -361,10 +386,12 @@ class Game:
|
|||||||
def compute_threat_zones(self) -> None:
|
def compute_threat_zones(self) -> None:
|
||||||
self.blue_threat_zone = ThreatZones.for_faction(self, player=True)
|
self.blue_threat_zone = ThreatZones.for_faction(self, player=True)
|
||||||
self.red_threat_zone = ThreatZones.for_faction(self, player=False)
|
self.red_threat_zone = ThreatZones.for_faction(self, player=False)
|
||||||
self.blue_navmesh = NavMesh.from_threat_zones(self.red_threat_zone,
|
self.blue_navmesh = NavMesh.from_threat_zones(
|
||||||
self.theater)
|
self.red_threat_zone, self.theater
|
||||||
self.red_navmesh = NavMesh.from_threat_zones(self.blue_threat_zone,
|
)
|
||||||
self.theater)
|
self.red_navmesh = NavMesh.from_threat_zones(
|
||||||
|
self.blue_threat_zone, self.theater
|
||||||
|
)
|
||||||
|
|
||||||
def threat_zone_for(self, player: bool) -> ThreatZones:
|
def threat_zone_for(self, player: bool) -> ThreatZones:
|
||||||
if player:
|
if player:
|
||||||
@ -386,9 +413,9 @@ class Game:
|
|||||||
|
|
||||||
# By default, use the existing frontline conflict position
|
# By default, use the existing frontline conflict position
|
||||||
for front_line in self.theater.conflicts():
|
for front_line in self.theater.conflicts():
|
||||||
position = Conflict.frontline_position(front_line.control_point_a,
|
position = Conflict.frontline_position(
|
||||||
front_line.control_point_b,
|
front_line.control_point_a, front_line.control_point_b, self.theater
|
||||||
self.theater)
|
)
|
||||||
zones.append(position[0])
|
zones.append(position[0])
|
||||||
zones.append(front_line.control_point_a.position)
|
zones.append(front_line.control_point_a.position)
|
||||||
zones.append(front_line.control_point_b.position)
|
zones.append(front_line.control_point_b.position)
|
||||||
@ -413,7 +440,10 @@ class Game:
|
|||||||
d = cp.position.distance_to_point(cp2.position)
|
d = cp.position.distance_to_point(cp2.position)
|
||||||
if d < min_distance:
|
if d < min_distance:
|
||||||
min_distance = d
|
min_distance = d
|
||||||
cpoint = Point((cp.position.x + cp2.position.x) / 2, (cp.position.y + cp2.position.y) / 2)
|
cpoint = Point(
|
||||||
|
(cp.position.x + cp2.position.x) / 2,
|
||||||
|
(cp.position.y + cp2.position.y) / 2,
|
||||||
|
)
|
||||||
zones.append(cp.position)
|
zones.append(cp.position)
|
||||||
zones.append(cp2.position)
|
zones.append(cp2.position)
|
||||||
break
|
break
|
||||||
@ -422,8 +452,7 @@ class Game:
|
|||||||
if cpoint is not None:
|
if cpoint is not None:
|
||||||
zones.append(cpoint)
|
zones.append(cpoint)
|
||||||
|
|
||||||
packages = itertools.chain(self.blue_ato.packages,
|
packages = itertools.chain(self.blue_ato.packages, self.red_ato.packages)
|
||||||
self.red_ato.packages)
|
|
||||||
for package in packages:
|
for package in packages:
|
||||||
if package.primary_task is FlightType.BARCAP:
|
if package.primary_task is FlightType.BARCAP:
|
||||||
# BARCAPs will be planned at most locations on smaller theaters,
|
# BARCAPs will be planned at most locations on smaller theaters,
|
||||||
@ -460,7 +489,10 @@ class Game:
|
|||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
for z in self.__culling_zones:
|
for z in self.__culling_zones:
|
||||||
if z.distance_to_point(pos) < self.settings.perf_culling_distance * 1000:
|
if (
|
||||||
|
z.distance_to_point(pos)
|
||||||
|
< self.settings.perf_culling_distance * 1000
|
||||||
|
):
|
||||||
return False
|
return False
|
||||||
for p in self.__culling_points:
|
for p in self.__culling_points:
|
||||||
if p.distance_to_point(pos) < 2500:
|
if p.distance_to_point(pos) < 2500:
|
||||||
@ -502,6 +534,10 @@ class Game:
|
|||||||
|
|
||||||
def process_win_loss(self, turn_state: TurnState):
|
def process_win_loss(self, turn_state: TurnState):
|
||||||
if turn_state is TurnState.WIN:
|
if turn_state is TurnState.WIN:
|
||||||
return self.message("Congratulations, you are victorious! Start a new campaign to continue.")
|
return self.message(
|
||||||
|
"Congratulations, you are victorious! Start a new campaign to continue."
|
||||||
|
)
|
||||||
elif turn_state is TurnState.LOSS:
|
elif turn_state is TurnState.LOSS:
|
||||||
return self.message("Game Over, you lose. Start a new campaign to continue.")
|
return self.message(
|
||||||
|
"Game Over, you lose. Start a new campaign to continue."
|
||||||
|
)
|
||||||
|
|||||||
@ -46,10 +46,10 @@ class Income:
|
|||||||
for tgo in tgos:
|
for tgo in tgos:
|
||||||
if not tgo.is_dead:
|
if not tgo.is_dead:
|
||||||
count += 1
|
count += 1
|
||||||
self.buildings.append(BuildingIncome(name, category, count,
|
self.buildings.append(
|
||||||
REWARDS[category]))
|
BuildingIncome(name, category, count, REWARDS[category])
|
||||||
|
)
|
||||||
|
|
||||||
self.from_bases = sum(cp.income_per_turn for cp in self.control_points)
|
self.from_bases = sum(cp.income_per_turn for cp in self.control_points)
|
||||||
self.total_buildings = sum(b.income for b in self.buildings)
|
self.total_buildings = sum(b.income for b in self.buildings)
|
||||||
self.total = ((self.total_buildings + self.from_bases) *
|
self.total = (self.total_buildings + self.from_bases) * self.multiplier
|
||||||
self.multiplier)
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
class Information():
|
|
||||||
|
|
||||||
|
class Information:
|
||||||
def __init__(self, title="", text="", turn=0):
|
def __init__(self, title="", text="", turn=0):
|
||||||
self.title = title
|
self.title = title
|
||||||
self.text = text
|
self.text = text
|
||||||
@ -9,9 +9,11 @@ class Information():
|
|||||||
self.timestamp = datetime.datetime.now()
|
self.timestamp = datetime.datetime.now()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '[{}][{}] {} {}'.format(
|
return "[{}][{}] {} {}".format(
|
||||||
self.timestamp.strftime("%Y-%m-%d %H:%M:%S") if self.timestamp is not None else '',
|
self.timestamp.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
if self.timestamp is not None
|
||||||
|
else "",
|
||||||
self.turn,
|
self.turn,
|
||||||
self.title,
|
self.title,
|
||||||
self.text
|
self.text,
|
||||||
)
|
)
|
||||||
@ -79,6 +79,7 @@ class ControlPointAircraftInventory:
|
|||||||
|
|
||||||
class GlobalAircraftInventory:
|
class GlobalAircraftInventory:
|
||||||
"""Game-wide aircraft inventory."""
|
"""Game-wide aircraft inventory."""
|
||||||
|
|
||||||
def __init__(self, control_points: Iterable[ControlPoint]) -> None:
|
def __init__(self, control_points: Iterable[ControlPoint]) -> None:
|
||||||
self.inventories: Dict[ControlPoint, ControlPointAircraftInventory] = {
|
self.inventories: Dict[ControlPoint, ControlPointAircraftInventory] = {
|
||||||
cp: ControlPointAircraftInventory(cp) for cp in control_points
|
cp: ControlPointAircraftInventory(cp) for cp in control_points
|
||||||
@ -100,8 +101,8 @@ class GlobalAircraftInventory:
|
|||||||
inventory.add_aircraft(aircraft, count)
|
inventory.add_aircraft(aircraft, count)
|
||||||
|
|
||||||
def for_control_point(
|
def for_control_point(
|
||||||
self,
|
self, control_point: ControlPoint
|
||||||
control_point: ControlPoint) -> ControlPointAircraftInventory:
|
) -> ControlPointAircraftInventory:
|
||||||
"""Returns the inventory specific to the given control point."""
|
"""Returns the inventory specific to the given control point."""
|
||||||
return self.inventories[control_point]
|
return self.inventories[control_point]
|
||||||
|
|
||||||
|
|||||||
@ -7,8 +7,7 @@ class DestroyedUnit:
|
|||||||
y: int
|
y: int
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
def __init__(self, x , y, name):
|
def __init__(self, x, y, name):
|
||||||
self.x = x
|
self.x = x
|
||||||
self.y = y
|
self.y = y
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@ class FrontlineData:
|
|||||||
This Data structure will store information about an existing frontline
|
This Data structure will store information about an existing frontline
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, from_cp:ControlPoint, to_cp: ControlPoint):
|
def __init__(self, from_cp: ControlPoint, to_cp: ControlPoint):
|
||||||
self.to_cp = to_cp
|
self.to_cp = to_cp
|
||||||
self.from_cp = from_cp
|
self.from_cp = from_cp
|
||||||
self.enemy_units_position = []
|
self.enemy_units_position = []
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
class FactionTurnMetadata:
|
class FactionTurnMetadata:
|
||||||
"""
|
"""
|
||||||
Store metadata about a faction
|
Store metadata about a faction
|
||||||
@ -20,8 +21,8 @@ class GameTurnMetadata:
|
|||||||
Store metadata about a game turn
|
Store metadata about a game turn
|
||||||
"""
|
"""
|
||||||
|
|
||||||
allied_units:FactionTurnMetadata
|
allied_units: FactionTurnMetadata
|
||||||
enemy_units:FactionTurnMetadata
|
enemy_units: FactionTurnMetadata
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.allied_units = FactionTurnMetadata()
|
self.allied_units = FactionTurnMetadata()
|
||||||
@ -53,4 +54,3 @@ class GameStats:
|
|||||||
turn_data.enemy_units.vehicles_count += sum(cp.base.armor.values())
|
turn_data.enemy_units.vehicles_count += sum(cp.base.armor.values())
|
||||||
|
|
||||||
self.data_per_turn.append(turn_data)
|
self.data_per_turn.append(turn_data)
|
||||||
|
|
||||||
|
|||||||
@ -114,9 +114,11 @@ class NavMesh:
|
|||||||
return self.travel_cost(a, b)
|
return self.travel_cost(a, b)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def reconstruct_path(came_from: Dict[NavPoint, Optional[NavPoint]],
|
def reconstruct_path(
|
||||||
|
came_from: Dict[NavPoint, Optional[NavPoint]],
|
||||||
origin: NavPoint,
|
origin: NavPoint,
|
||||||
destination: NavPoint) -> List[Point]:
|
destination: NavPoint,
|
||||||
|
) -> List[Point]:
|
||||||
current = destination
|
current = destination
|
||||||
path: List[Point] = []
|
path: List[Point] = []
|
||||||
while current != origin:
|
while current != origin:
|
||||||
@ -141,16 +143,14 @@ class NavMesh:
|
|||||||
raise ValueError(f"Origin point {origin} is outside the navmesh")
|
raise ValueError(f"Origin point {origin} is outside the navmesh")
|
||||||
destination_poly = self.localize(destination)
|
destination_poly = self.localize(destination)
|
||||||
if destination_poly is None:
|
if destination_poly is None:
|
||||||
raise ValueError(
|
raise ValueError(f"Origin point {destination} is outside the navmesh")
|
||||||
f"Origin point {destination} is outside the navmesh")
|
|
||||||
|
|
||||||
return self._shortest_path(
|
return self._shortest_path(
|
||||||
NavPoint(self.dcs_to_shapely_point(origin), origin_poly),
|
NavPoint(self.dcs_to_shapely_point(origin), origin_poly),
|
||||||
NavPoint(self.dcs_to_shapely_point(destination), destination_poly)
|
NavPoint(self.dcs_to_shapely_point(destination), destination_poly),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _shortest_path(self, origin: NavPoint,
|
def _shortest_path(self, origin: NavPoint, destination: NavPoint) -> List[Point]:
|
||||||
destination: NavPoint) -> List[Point]:
|
|
||||||
# Adapted from
|
# Adapted from
|
||||||
# https://www.redblobgames.com/pathfinding/a-star/implementation.py.
|
# https://www.redblobgames.com/pathfinding/a-star/implementation.py.
|
||||||
frontier = NavFrontier()
|
frontier = NavFrontier()
|
||||||
@ -167,9 +167,7 @@ class NavMesh:
|
|||||||
if current.poly == destination.poly:
|
if current.poly == destination.poly:
|
||||||
# Made it to the correct nav poly. Add the leg from the border
|
# Made it to the correct nav poly. Add the leg from the border
|
||||||
# to the target.
|
# to the target.
|
||||||
cost = best_known[current] + self.travel_cost(
|
cost = best_known[current] + self.travel_cost(current, destination)
|
||||||
current, destination
|
|
||||||
)
|
|
||||||
if cost < best_known[destination]:
|
if cost < best_known[destination]:
|
||||||
best_known[destination] = cost
|
best_known[destination] = cost
|
||||||
estimated = cost
|
estimated = cost
|
||||||
@ -185,14 +183,10 @@ class NavMesh:
|
|||||||
raise RuntimeError
|
raise RuntimeError
|
||||||
_, neighbor_point = nearest_points(current.point, boundary)
|
_, neighbor_point = nearest_points(current.point, boundary)
|
||||||
neighbor_nav = NavPoint(neighbor_point, neighbor)
|
neighbor_nav = NavPoint(neighbor_point, neighbor)
|
||||||
cost = best_known[current] + self.travel_cost(
|
cost = best_known[current] + self.travel_cost(current, neighbor_nav)
|
||||||
current, neighbor_nav
|
|
||||||
)
|
|
||||||
if cost < best_known[neighbor_nav]:
|
if cost < best_known[neighbor_nav]:
|
||||||
best_known[neighbor_nav] = cost
|
best_known[neighbor_nav] = cost
|
||||||
estimated = cost + self.travel_heuristic(
|
estimated = cost + self.travel_heuristic(neighbor_nav, destination)
|
||||||
neighbor_nav, destination
|
|
||||||
)
|
|
||||||
frontier.push(neighbor_nav, estimated)
|
frontier.push(neighbor_nav, estimated)
|
||||||
came_from[neighbor_nav] = current
|
came_from[neighbor_nav] = current
|
||||||
|
|
||||||
@ -209,13 +203,16 @@ class NavMesh:
|
|||||||
# threatened airbases at the map edges have room to retreat from the
|
# threatened airbases at the map edges have room to retreat from the
|
||||||
# threat without running off the navmesh.
|
# threat without running off the navmesh.
|
||||||
return box(*LineString(points).bounds).buffer(
|
return box(*LineString(points).bounds).buffer(
|
||||||
nautical_miles(100).meters, resolution=1)
|
nautical_miles(100).meters, resolution=1
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_navpolys(polys: List[Polygon],
|
def create_navpolys(
|
||||||
threat_zones: ThreatZones) -> List[NavMeshPoly]:
|
polys: List[Polygon], threat_zones: ThreatZones
|
||||||
return [NavMeshPoly(i, p, threat_zones.threatened(p))
|
) -> List[NavMeshPoly]:
|
||||||
for i, p in enumerate(polys)]
|
return [
|
||||||
|
NavMeshPoly(i, p, threat_zones.threatened(p)) for i, p in enumerate(polys)
|
||||||
|
]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def associate_neighbors(polys: List[NavMeshPoly]) -> None:
|
def associate_neighbors(polys: List[NavMeshPoly]) -> None:
|
||||||
@ -234,8 +231,7 @@ class NavMesh:
|
|||||||
point = (int(x), int(y))
|
point = (int(x), int(y))
|
||||||
neighbors = {}
|
neighbors = {}
|
||||||
for potential_neighbor in points_map[point]:
|
for potential_neighbor in points_map[point]:
|
||||||
intersection = navpoly.poly.intersection(
|
intersection = navpoly.poly.intersection(potential_neighbor.poly)
|
||||||
potential_neighbor.poly)
|
|
||||||
if not intersection.is_empty:
|
if not intersection.is_empty:
|
||||||
potential_neighbor.neighbors[navpoly] = intersection
|
potential_neighbor.neighbors[navpoly] = intersection
|
||||||
neighbors[potential_neighbor] = intersection
|
neighbors[potential_neighbor] = intersection
|
||||||
@ -243,8 +239,9 @@ class NavMesh:
|
|||||||
points_map[point].add(navpoly)
|
points_map[point].add(navpoly)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_threat_zones(cls, threat_zones: ThreatZones,
|
def from_threat_zones(
|
||||||
theater: ConflictTheater) -> NavMesh:
|
cls, threat_zones: ThreatZones, theater: ConflictTheater
|
||||||
|
) -> NavMesh:
|
||||||
# Simplify the threat poly to reduce the number of nav zones. Increase
|
# Simplify the threat poly to reduce the number of nav zones. Increase
|
||||||
# the size of the zone and then simplify it with the buffer size as the
|
# the size of the zone and then simplify it with the buffer size as the
|
||||||
# error margin. This will create a simpler poly around the threat zone.
|
# error margin. This will create a simpler poly around the threat zone.
|
||||||
|
|||||||
@ -41,6 +41,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
class Operation:
|
class Operation:
|
||||||
"""Static class for managing the final Mission generation"""
|
"""Static class for managing the final Mission generation"""
|
||||||
|
|
||||||
current_mission = None # type: Mission
|
current_mission = None # type: Mission
|
||||||
airgen = None # type: AircraftConflictGenerator
|
airgen = None # type: AircraftConflictGenerator
|
||||||
triggersgen = None # type: TriggersGenerator
|
triggersgen = None # type: TriggersGenerator
|
||||||
@ -84,7 +85,7 @@ class Operation:
|
|||||||
cls.game.enemy_name,
|
cls.game.enemy_name,
|
||||||
cls.game.player_country,
|
cls.game.player_country,
|
||||||
cls.game.enemy_country,
|
cls.game.enemy_country,
|
||||||
frontline.position
|
frontline.position,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -93,7 +94,7 @@ class Operation:
|
|||||||
player_cp, enemy_cp = cls.game.theater.closest_opposing_control_points()
|
player_cp, enemy_cp = cls.game.theater.closest_opposing_control_points()
|
||||||
mid_point = player_cp.position.point_from_heading(
|
mid_point = player_cp.position.point_from_heading(
|
||||||
player_cp.position.heading_between_point(enemy_cp.position),
|
player_cp.position.heading_between_point(enemy_cp.position),
|
||||||
player_cp.position.distance_to_point(enemy_cp.position) / 2
|
player_cp.position.distance_to_point(enemy_cp.position) / 2,
|
||||||
)
|
)
|
||||||
return Conflict(
|
return Conflict(
|
||||||
cls.game.theater,
|
cls.game.theater,
|
||||||
@ -103,7 +104,7 @@ class Operation:
|
|||||||
cls.game.enemy_name,
|
cls.game.enemy_name,
|
||||||
cls.game.player_country,
|
cls.game.player_country,
|
||||||
cls.game.enemy_country,
|
cls.game.enemy_country,
|
||||||
mid_point
|
mid_point,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -118,9 +119,11 @@ class Operation:
|
|||||||
p_country = cls.game.player_country
|
p_country = cls.game.player_country
|
||||||
e_country = cls.game.enemy_country
|
e_country = cls.game.enemy_country
|
||||||
cls.current_mission.coalition["blue"].add_country(
|
cls.current_mission.coalition["blue"].add_country(
|
||||||
country_dict[db.country_id_from_name(p_country)]())
|
country_dict[db.country_id_from_name(p_country)]()
|
||||||
|
)
|
||||||
cls.current_mission.coalition["red"].add_country(
|
cls.current_mission.coalition["red"].add_country(
|
||||||
country_dict[db.country_id_from_name(e_country)]())
|
country_dict[db.country_id_from_name(e_country)]()
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def inject_lua_trigger(cls, contents: str, comment: str) -> None:
|
def inject_lua_trigger(cls, contents: str, comment: str) -> None:
|
||||||
@ -133,12 +136,11 @@ class Operation:
|
|||||||
cls.plugin_scripts.append(mnemonic)
|
cls.plugin_scripts.append(mnemonic)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def inject_plugin_script(cls, plugin_mnemonic: str, script: str,
|
def inject_plugin_script(
|
||||||
script_mnemonic: str) -> None:
|
cls, plugin_mnemonic: str, script: str, script_mnemonic: str
|
||||||
|
) -> None:
|
||||||
if script_mnemonic in cls.plugin_scripts:
|
if script_mnemonic in cls.plugin_scripts:
|
||||||
logging.debug(
|
logging.debug(f"Skipping already loaded {script} for {plugin_mnemonic}")
|
||||||
f"Skipping already loaded {script} for {plugin_mnemonic}"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
cls.plugin_scripts.append(script_mnemonic)
|
cls.plugin_scripts.append(script_mnemonic)
|
||||||
|
|
||||||
@ -146,15 +148,12 @@ class Operation:
|
|||||||
|
|
||||||
script_path = Path(plugin_path, script)
|
script_path = Path(plugin_path, script)
|
||||||
if not script_path.exists():
|
if not script_path.exists():
|
||||||
logging.error(
|
logging.error(f"Cannot find {script_path} for plugin {plugin_mnemonic}")
|
||||||
f"Cannot find {script_path} for plugin {plugin_mnemonic}"
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
trigger = TriggerStart(comment=f"Load {script_mnemonic}")
|
trigger = TriggerStart(comment=f"Load {script_mnemonic}")
|
||||||
filename = script_path.resolve()
|
filename = script_path.resolve()
|
||||||
fileref = cls.current_mission.map_resource.add_resource_file(
|
fileref = cls.current_mission.map_resource.add_resource_file(filename)
|
||||||
filename)
|
|
||||||
trigger.add_action(DoScriptFile(fileref))
|
trigger.add_action(DoScriptFile(fileref))
|
||||||
cls.current_mission.triggerrules.triggers.append(trigger)
|
cls.current_mission.triggerrules.triggers.append(trigger)
|
||||||
|
|
||||||
@ -166,11 +165,10 @@ class Operation:
|
|||||||
jtacs: List[JtacInfo],
|
jtacs: List[JtacInfo],
|
||||||
airgen: AircraftConflictGenerator,
|
airgen: AircraftConflictGenerator,
|
||||||
):
|
):
|
||||||
"""Generates subscribed MissionInfoGenerator objects (currently kneeboards and briefings)
|
"""Generates subscribed MissionInfoGenerator objects (currently kneeboards and briefings)"""
|
||||||
"""
|
|
||||||
gens: List[MissionInfoGenerator] = [
|
gens: List[MissionInfoGenerator] = [
|
||||||
KneeboardGenerator(cls.current_mission, cls.game),
|
KneeboardGenerator(cls.current_mission, cls.game),
|
||||||
BriefingGenerator(cls.current_mission, cls.game)
|
BriefingGenerator(cls.current_mission, cls.game),
|
||||||
]
|
]
|
||||||
for gen in gens:
|
for gen in gens:
|
||||||
for dynamic_runway in groundobjectgen.runways.values():
|
for dynamic_runway in groundobjectgen.runways.values():
|
||||||
@ -208,8 +206,9 @@ class Operation:
|
|||||||
cls.radio_registry.reserve(frequency)
|
cls.radio_registry.reserve(frequency)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def assign_channels_to_flights(cls, flights: List[FlightData],
|
def assign_channels_to_flights(
|
||||||
air_support: AirSupport) -> None:
|
cls, flights: List[FlightData], air_support: AirSupport
|
||||||
|
) -> None:
|
||||||
"""Assigns preset radio channels for client flights."""
|
"""Assigns preset radio channels for client flights."""
|
||||||
for flight in flights:
|
for flight in flights:
|
||||||
if not flight.client_units:
|
if not flight.client_units:
|
||||||
@ -217,8 +216,7 @@ class Operation:
|
|||||||
cls.assign_channels_to_flight(flight, air_support)
|
cls.assign_channels_to_flight(flight, air_support)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def assign_channels_to_flight(flight: FlightData,
|
def assign_channels_to_flight(flight: FlightData, air_support: AirSupport) -> None:
|
||||||
air_support: AirSupport) -> None:
|
|
||||||
"""Assigns preset radio channels for a client flight."""
|
"""Assigns preset radio channels for a client flight."""
|
||||||
airframe = flight.aircraft_type
|
airframe = flight.aircraft_type
|
||||||
|
|
||||||
@ -234,7 +232,9 @@ class Operation:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _create_tacan_registry(cls, unique_map_frequencies: Set[RadioFrequency]) -> None:
|
def _create_tacan_registry(
|
||||||
|
cls, unique_map_frequencies: Set[RadioFrequency]
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Dedup beacon/radio frequencies, since some maps have some frequencies
|
Dedup beacon/radio frequencies, since some maps have some frequencies
|
||||||
used multiple times.
|
used multiple times.
|
||||||
@ -246,13 +246,14 @@ class Operation:
|
|||||||
unique_map_frequencies.add(beacon.frequency)
|
unique_map_frequencies.add(beacon.frequency)
|
||||||
if beacon.is_tacan:
|
if beacon.is_tacan:
|
||||||
if beacon.channel is None:
|
if beacon.channel is None:
|
||||||
logging.error(
|
logging.error(f"TACAN beacon has no channel: {beacon.callsign}")
|
||||||
f"TACAN beacon has no channel: {beacon.callsign}")
|
|
||||||
else:
|
else:
|
||||||
cls.tacan_registry.reserve(beacon.tacan_channel)
|
cls.tacan_registry.reserve(beacon.tacan_channel)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _create_radio_registry(cls, unique_map_frequencies: Set[RadioFrequency]) -> None:
|
def _create_radio_registry(
|
||||||
|
cls, unique_map_frequencies: Set[RadioFrequency]
|
||||||
|
) -> None:
|
||||||
cls.radio_registry = RadioRegistry()
|
cls.radio_registry = RadioRegistry()
|
||||||
for data in AIRFIELD_DATA.values():
|
for data in AIRFIELD_DATA.values():
|
||||||
if data.theater == cls.game.theater.terrain.name and data.atc:
|
if data.theater == cls.game.theater.terrain.name and data.atc:
|
||||||
@ -270,7 +271,7 @@ class Operation:
|
|||||||
cls.game,
|
cls.game,
|
||||||
cls.radio_registry,
|
cls.radio_registry,
|
||||||
cls.tacan_registry,
|
cls.tacan_registry,
|
||||||
cls.unit_map
|
cls.unit_map,
|
||||||
)
|
)
|
||||||
cls.groundobjectgen.generate()
|
cls.groundobjectgen.generate()
|
||||||
|
|
||||||
@ -284,10 +285,13 @@ class Operation:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
pos = Point(d["x"], d["z"])
|
pos = Point(d["x"], d["z"])
|
||||||
if utype is not None and not cls.game.position_culled(pos) and cls.game.settings.perf_destroyed_units:
|
if (
|
||||||
|
utype is not None
|
||||||
|
and not cls.game.position_culled(pos)
|
||||||
|
and cls.game.settings.perf_destroyed_units
|
||||||
|
):
|
||||||
cls.current_mission.static_group(
|
cls.current_mission.static_group(
|
||||||
country=cls.current_mission.country(
|
country=cls.current_mission.country(cls.game.player_country),
|
||||||
cls.game.player_country),
|
|
||||||
name="",
|
name="",
|
||||||
_type=utype,
|
_type=utype,
|
||||||
hidden=True,
|
hidden=True,
|
||||||
@ -302,13 +306,13 @@ class Operation:
|
|||||||
cls.create_unit_map()
|
cls.create_unit_map()
|
||||||
cls.create_radio_registries()
|
cls.create_radio_registries()
|
||||||
# Set mission time and weather conditions.
|
# Set mission time and weather conditions.
|
||||||
EnvironmentGenerator(cls.current_mission,
|
EnvironmentGenerator(cls.current_mission, cls.game.conditions).generate()
|
||||||
cls.game.conditions).generate()
|
|
||||||
cls._generate_ground_units()
|
cls._generate_ground_units()
|
||||||
cls._generate_destroyed_units()
|
cls._generate_destroyed_units()
|
||||||
cls._generate_air_units()
|
cls._generate_air_units()
|
||||||
cls.assign_channels_to_flights(cls.airgen.flights,
|
cls.assign_channels_to_flights(
|
||||||
cls.airsupportgen.air_support)
|
cls.airgen.flights, cls.airsupportgen.air_support
|
||||||
|
)
|
||||||
cls._generate_ground_conflicts()
|
cls._generate_ground_conflicts()
|
||||||
|
|
||||||
# Triggers
|
# Triggers
|
||||||
@ -317,14 +321,16 @@ class Operation:
|
|||||||
|
|
||||||
# Setup combined arms parameters
|
# Setup combined arms parameters
|
||||||
cls.current_mission.groundControl.pilot_can_control_vehicles = cls.ca_slots > 0
|
cls.current_mission.groundControl.pilot_can_control_vehicles = cls.ca_slots > 0
|
||||||
if cls.game.player_country in [country.name for country in cls.current_mission.coalition["blue"].countries.values()]:
|
if cls.game.player_country in [
|
||||||
|
country.name
|
||||||
|
for country in cls.current_mission.coalition["blue"].countries.values()
|
||||||
|
]:
|
||||||
cls.current_mission.groundControl.blue_tactical_commander = cls.ca_slots
|
cls.current_mission.groundControl.blue_tactical_commander = cls.ca_slots
|
||||||
else:
|
else:
|
||||||
cls.current_mission.groundControl.red_tactical_commander = cls.ca_slots
|
cls.current_mission.groundControl.red_tactical_commander = cls.ca_slots
|
||||||
|
|
||||||
# Options
|
# Options
|
||||||
forcedoptionsgen = ForcedOptionsGenerator(
|
forcedoptionsgen = ForcedOptionsGenerator(cls.current_mission, cls.game)
|
||||||
cls.current_mission, cls.game)
|
|
||||||
forcedoptionsgen.generate()
|
forcedoptionsgen.generate()
|
||||||
|
|
||||||
# Generate Visuals Smoke Effects
|
# Generate Visuals Smoke Effects
|
||||||
@ -341,13 +347,11 @@ class Operation:
|
|||||||
plugin.inject_scripts(cls)
|
plugin.inject_scripts(cls)
|
||||||
plugin.inject_configuration(cls)
|
plugin.inject_configuration(cls)
|
||||||
|
|
||||||
cls.assign_channels_to_flights(cls.airgen.flights,
|
cls.assign_channels_to_flights(
|
||||||
cls.airsupportgen.air_support)
|
cls.airgen.flights, cls.airsupportgen.air_support
|
||||||
|
)
|
||||||
cls.notify_info_generators(
|
cls.notify_info_generators(
|
||||||
cls.groundobjectgen,
|
cls.groundobjectgen, cls.airsupportgen, cls.jtacs, cls.airgen
|
||||||
cls.airsupportgen,
|
|
||||||
cls.jtacs,
|
|
||||||
cls.airgen
|
|
||||||
)
|
)
|
||||||
cls.reset_naming_ids()
|
cls.reset_naming_ids()
|
||||||
return cls.unit_map
|
return cls.unit_map
|
||||||
@ -359,29 +363,38 @@ class Operation:
|
|||||||
# Air Support (Tanker & Awacs)
|
# Air Support (Tanker & Awacs)
|
||||||
assert cls.radio_registry and cls.tacan_registry
|
assert cls.radio_registry and cls.tacan_registry
|
||||||
cls.airsupportgen = AirSupportConflictGenerator(
|
cls.airsupportgen = AirSupportConflictGenerator(
|
||||||
cls.current_mission, cls.air_conflict(), cls.game, cls.radio_registry,
|
cls.current_mission,
|
||||||
cls.tacan_registry)
|
cls.air_conflict(),
|
||||||
|
cls.game,
|
||||||
|
cls.radio_registry,
|
||||||
|
cls.tacan_registry,
|
||||||
|
)
|
||||||
cls.airsupportgen.generate()
|
cls.airsupportgen.generate()
|
||||||
|
|
||||||
# Generate Aircraft Activity on the map
|
# Generate Aircraft Activity on the map
|
||||||
cls.airgen = AircraftConflictGenerator(
|
cls.airgen = AircraftConflictGenerator(
|
||||||
cls.current_mission, cls.game.settings, cls.game,
|
cls.current_mission,
|
||||||
cls.radio_registry, cls.unit_map)
|
cls.game.settings,
|
||||||
|
cls.game,
|
||||||
|
cls.radio_registry,
|
||||||
|
cls.unit_map,
|
||||||
|
)
|
||||||
cls.airgen.clear_parking_slots()
|
cls.airgen.clear_parking_slots()
|
||||||
|
|
||||||
cls.airgen.generate_flights(
|
cls.airgen.generate_flights(
|
||||||
cls.current_mission.country(cls.game.player_country),
|
cls.current_mission.country(cls.game.player_country),
|
||||||
cls.game.blue_ato,
|
cls.game.blue_ato,
|
||||||
cls.groundobjectgen.runways
|
cls.groundobjectgen.runways,
|
||||||
)
|
)
|
||||||
cls.airgen.generate_flights(
|
cls.airgen.generate_flights(
|
||||||
cls.current_mission.country(cls.game.enemy_country),
|
cls.current_mission.country(cls.game.enemy_country),
|
||||||
cls.game.red_ato,
|
cls.game.red_ato,
|
||||||
cls.groundobjectgen.runways
|
cls.groundobjectgen.runways,
|
||||||
)
|
)
|
||||||
cls.airgen.spawn_unused_aircraft(
|
cls.airgen.spawn_unused_aircraft(
|
||||||
cls.current_mission.country(cls.game.player_country),
|
cls.current_mission.country(cls.game.player_country),
|
||||||
cls.current_mission.country(cls.game.enemy_country))
|
cls.current_mission.country(cls.game.enemy_country),
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _generate_ground_conflicts(cls) -> None:
|
def _generate_ground_conflicts(cls) -> None:
|
||||||
@ -396,17 +409,19 @@ class Operation:
|
|||||||
cls.current_mission.country(cls.game.enemy_country),
|
cls.current_mission.country(cls.game.enemy_country),
|
||||||
player_cp,
|
player_cp,
|
||||||
enemy_cp,
|
enemy_cp,
|
||||||
cls.game.theater
|
cls.game.theater,
|
||||||
)
|
)
|
||||||
# Generate frontline ops
|
# Generate frontline ops
|
||||||
player_gp = cls.game.ground_planners[player_cp.id].units_per_cp[enemy_cp.id]
|
player_gp = cls.game.ground_planners[player_cp.id].units_per_cp[enemy_cp.id]
|
||||||
enemy_gp = cls.game.ground_planners[enemy_cp.id].units_per_cp[player_cp.id]
|
enemy_gp = cls.game.ground_planners[enemy_cp.id].units_per_cp[player_cp.id]
|
||||||
ground_conflict_gen = GroundConflictGenerator(
|
ground_conflict_gen = GroundConflictGenerator(
|
||||||
cls.current_mission,
|
cls.current_mission,
|
||||||
conflict, cls.game,
|
conflict,
|
||||||
player_gp, enemy_gp,
|
cls.game,
|
||||||
|
player_gp,
|
||||||
|
enemy_gp,
|
||||||
player_cp.stances[enemy_cp.id],
|
player_cp.stances[enemy_cp.id],
|
||||||
cls.unit_map
|
cls.unit_map,
|
||||||
)
|
)
|
||||||
ground_conflict_gen.generate()
|
ground_conflict_gen.generate()
|
||||||
cls.jtacs.extend(ground_conflict_gen.jtacs)
|
cls.jtacs.extend(ground_conflict_gen.jtacs)
|
||||||
@ -416,9 +431,12 @@ class Operation:
|
|||||||
namegen.reset_numbers()
|
namegen.reset_numbers()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def generate_lua(cls, airgen: AircraftConflictGenerator,
|
def generate_lua(
|
||||||
|
cls,
|
||||||
|
airgen: AircraftConflictGenerator,
|
||||||
airsupportgen: AirSupportConflictGenerator,
|
airsupportgen: AirSupportConflictGenerator,
|
||||||
jtacs: List[JtacInfo]) -> None:
|
jtacs: List[JtacInfo],
|
||||||
|
) -> None:
|
||||||
# TODO: Refactor this
|
# TODO: Refactor this
|
||||||
luaData = {
|
luaData = {
|
||||||
"AircraftCarriers": {},
|
"AircraftCarriers": {},
|
||||||
@ -434,7 +452,7 @@ class Operation:
|
|||||||
"callsign": tanker.callsign,
|
"callsign": tanker.callsign,
|
||||||
"variant": tanker.variant,
|
"variant": tanker.variant,
|
||||||
"radio": tanker.freq.mhz,
|
"radio": tanker.freq.mhz,
|
||||||
"tacan": str(tanker.tacan.number) + tanker.tacan.band.name
|
"tacan": str(tanker.tacan.number) + tanker.tacan.band.name,
|
||||||
}
|
}
|
||||||
|
|
||||||
if airsupportgen.air_support.awacs:
|
if airsupportgen.air_support.awacs:
|
||||||
@ -442,7 +460,7 @@ class Operation:
|
|||||||
luaData["AWACs"][awacs.callsign] = {
|
luaData["AWACs"][awacs.callsign] = {
|
||||||
"dcsGroupName": awacs.dcsGroupName,
|
"dcsGroupName": awacs.dcsGroupName,
|
||||||
"callsign": awacs.callsign,
|
"callsign": awacs.callsign,
|
||||||
"radio": awacs.freq.mhz
|
"radio": awacs.freq.mhz,
|
||||||
}
|
}
|
||||||
|
|
||||||
for jtac in jtacs:
|
for jtac in jtacs:
|
||||||
@ -451,14 +469,16 @@ class Operation:
|
|||||||
"callsign": jtac.callsign,
|
"callsign": jtac.callsign,
|
||||||
"zone": jtac.region,
|
"zone": jtac.region,
|
||||||
"dcsUnit": jtac.unit_name,
|
"dcsUnit": jtac.unit_name,
|
||||||
"laserCode": jtac.code
|
"laserCode": jtac.code,
|
||||||
}
|
}
|
||||||
|
|
||||||
for flight in airgen.flights:
|
for flight in airgen.flights:
|
||||||
if flight.friendly and flight.flight_type in [FlightType.ANTISHIP,
|
if flight.friendly and flight.flight_type in [
|
||||||
|
FlightType.ANTISHIP,
|
||||||
FlightType.DEAD,
|
FlightType.DEAD,
|
||||||
FlightType.SEAD,
|
FlightType.SEAD,
|
||||||
FlightType.STRIKE]:
|
FlightType.STRIKE,
|
||||||
|
]:
|
||||||
flightType = str(flight.flight_type)
|
flightType = str(flight.flight_type)
|
||||||
flightTarget = flight.package.target
|
flightTarget = flight.package.target
|
||||||
if flightTarget:
|
if flightTarget:
|
||||||
@ -466,23 +486,27 @@ class Operation:
|
|||||||
flightTargetType = None
|
flightTargetType = None
|
||||||
if isinstance(flightTarget, TheaterGroundObject):
|
if isinstance(flightTarget, TheaterGroundObject):
|
||||||
flightTargetName = flightTarget.obj_name
|
flightTargetName = flightTarget.obj_name
|
||||||
flightTargetType = flightType + \
|
flightTargetType = (
|
||||||
f" TGT ({flightTarget.category})"
|
flightType + f" TGT ({flightTarget.category})"
|
||||||
elif hasattr(flightTarget, 'name'):
|
)
|
||||||
|
elif hasattr(flightTarget, "name"):
|
||||||
flightTargetName = flightTarget.name
|
flightTargetName = flightTarget.name
|
||||||
flightTargetType = flightType + " TGT (Airbase)"
|
flightTargetType = flightType + " TGT (Airbase)"
|
||||||
luaData["TargetPoints"][flightTargetName] = {
|
luaData["TargetPoints"][flightTargetName] = {
|
||||||
"name": flightTargetName,
|
"name": flightTargetName,
|
||||||
"type": flightTargetType,
|
"type": flightTargetType,
|
||||||
"position": {"x": flightTarget.position.x,
|
"position": {
|
||||||
"y": flightTarget.position.y}
|
"x": flightTarget.position.x,
|
||||||
|
"y": flightTarget.position.y,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# set a LUA table with data from Liberation that we want to set
|
# set a LUA table with data from Liberation that we want to set
|
||||||
# at the moment it contains Liberation's install path, and an overridable definition for the JTACAutoLase function
|
# at the moment it contains Liberation's install path, and an overridable definition for the JTACAutoLase function
|
||||||
# later, we'll add data about the units and points having been generated, in order to facilitate the configuration of the plugin lua scripts
|
# later, we'll add data about the units and points having been generated, in order to facilitate the configuration of the plugin lua scripts
|
||||||
state_location = "[[" + os.path.abspath(".") + "]]"
|
state_location = "[[" + os.path.abspath(".") + "]]"
|
||||||
lua = """
|
lua = (
|
||||||
|
"""
|
||||||
-- setting configuration table
|
-- setting configuration table
|
||||||
env.info("DCSLiberation|: setting configuration table")
|
env.info("DCSLiberation|: setting configuration table")
|
||||||
|
|
||||||
@ -490,9 +514,12 @@ class Operation:
|
|||||||
dcsLiberation = {}
|
dcsLiberation = {}
|
||||||
|
|
||||||
-- the base location for state.json; if non-existent, it'll be replaced with LIBERATION_EXPORT_DIR, TEMP, or DCS working directory
|
-- the base location for state.json; if non-existent, it'll be replaced with LIBERATION_EXPORT_DIR, TEMP, or DCS working directory
|
||||||
dcsLiberation.installPath=""" + state_location + """
|
dcsLiberation.installPath="""
|
||||||
|
+ state_location
|
||||||
|
+ """
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
)
|
||||||
# Process the tankers
|
# Process the tankers
|
||||||
lua += """
|
lua += """
|
||||||
|
|
||||||
|
|||||||
@ -67,4 +67,3 @@ def autosave(game) -> bool:
|
|||||||
except Exception:
|
except Exception:
|
||||||
logging.exception("Could not save game")
|
logging.exception("Could not save game")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@ -14,9 +14,9 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class LuaPluginWorkOrder:
|
class LuaPluginWorkOrder:
|
||||||
|
def __init__(
|
||||||
def __init__(self, parent_mnemonic: str, filename: str, mnemonic: str,
|
self, parent_mnemonic: str, filename: str, mnemonic: str, disable: bool
|
||||||
disable: bool) -> None:
|
) -> None:
|
||||||
self.parent_mnemonic = parent_mnemonic
|
self.parent_mnemonic = parent_mnemonic
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.mnemonic = mnemonic
|
self.mnemonic = mnemonic
|
||||||
@ -26,8 +26,9 @@ class LuaPluginWorkOrder:
|
|||||||
if self.disable:
|
if self.disable:
|
||||||
operation.bypass_plugin_script(self.mnemonic)
|
operation.bypass_plugin_script(self.mnemonic)
|
||||||
else:
|
else:
|
||||||
operation.inject_plugin_script(self.parent_mnemonic, self.filename,
|
operation.inject_plugin_script(
|
||||||
self.mnemonic)
|
self.parent_mnemonic, self.filename, self.mnemonic
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PluginSettings:
|
class PluginSettings:
|
||||||
@ -45,8 +46,7 @@ class PluginSettings:
|
|||||||
# Plugin options are saved in the game's Settings, but it's possible for
|
# Plugin options are saved in the game's Settings, but it's possible for
|
||||||
# plugins to change across loads. If new plugins are added or new
|
# plugins to change across loads. If new plugins are added or new
|
||||||
# options added to those plugins, initialize the new settings.
|
# options added to those plugins, initialize the new settings.
|
||||||
self.settings.initialize_plugin_option(self.identifier,
|
self.settings.initialize_plugin_option(self.identifier, self.enabled_by_default)
|
||||||
self.enabled_by_default)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enabled(self) -> bool:
|
def enabled(self) -> bool:
|
||||||
@ -57,8 +57,7 @@ class PluginSettings:
|
|||||||
|
|
||||||
|
|
||||||
class LuaPluginOption(PluginSettings):
|
class LuaPluginOption(PluginSettings):
|
||||||
def __init__(self, identifier: str, name: str,
|
def __init__(self, identifier: str, name: str, enabled_by_default: bool) -> None:
|
||||||
enabled_by_default: bool) -> None:
|
|
||||||
super().__init__(identifier, enabled_by_default)
|
super().__init__(identifier, enabled_by_default)
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
@ -80,24 +79,34 @@ class LuaPluginDefinition:
|
|||||||
options = []
|
options = []
|
||||||
for option in data.get("specificOptions"):
|
for option in data.get("specificOptions"):
|
||||||
option_id = option["mnemonic"]
|
option_id = option["mnemonic"]
|
||||||
options.append(LuaPluginOption(
|
options.append(
|
||||||
|
LuaPluginOption(
|
||||||
identifier=f"{name}.{option_id}",
|
identifier=f"{name}.{option_id}",
|
||||||
name=option.get("nameInUI", name),
|
name=option.get("nameInUI", name),
|
||||||
enabled_by_default=option.get("defaultValue")
|
enabled_by_default=option.get("defaultValue"),
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
work_orders = []
|
work_orders = []
|
||||||
for work_order in data.get("scriptsWorkOrders"):
|
for work_order in data.get("scriptsWorkOrders"):
|
||||||
work_orders.append(LuaPluginWorkOrder(
|
work_orders.append(
|
||||||
name, work_order.get("file"), work_order["mnemonic"],
|
LuaPluginWorkOrder(
|
||||||
work_order.get("disable", False)
|
name,
|
||||||
))
|
work_order.get("file"),
|
||||||
|
work_order["mnemonic"],
|
||||||
|
work_order.get("disable", False),
|
||||||
|
)
|
||||||
|
)
|
||||||
config_work_orders = []
|
config_work_orders = []
|
||||||
for work_order in data.get("configurationWorkOrders"):
|
for work_order in data.get("configurationWorkOrders"):
|
||||||
config_work_orders.append(LuaPluginWorkOrder(
|
config_work_orders.append(
|
||||||
name, work_order.get("file"), work_order["mnemonic"],
|
LuaPluginWorkOrder(
|
||||||
work_order.get("disable", False)
|
name,
|
||||||
))
|
work_order.get("file"),
|
||||||
|
work_order["mnemonic"],
|
||||||
|
work_order.get("disable", False),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
identifier=name,
|
identifier=name,
|
||||||
@ -106,16 +115,14 @@ class LuaPluginDefinition:
|
|||||||
enabled_by_default=data.get("defaultValue", False),
|
enabled_by_default=data.get("defaultValue", False),
|
||||||
options=options,
|
options=options,
|
||||||
work_orders=work_orders,
|
work_orders=work_orders,
|
||||||
config_work_orders=config_work_orders
|
config_work_orders=config_work_orders,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class LuaPlugin(PluginSettings):
|
class LuaPlugin(PluginSettings):
|
||||||
|
|
||||||
def __init__(self, definition: LuaPluginDefinition) -> None:
|
def __init__(self, definition: LuaPluginDefinition) -> None:
|
||||||
self.definition = definition
|
self.definition = definition
|
||||||
super().__init__(self.definition.identifier,
|
super().__init__(self.definition.identifier, self.definition.enabled_by_default)
|
||||||
self.definition.enabled_by_default)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
@ -155,12 +162,12 @@ class LuaPlugin(PluginSettings):
|
|||||||
for option in self.options:
|
for option in self.options:
|
||||||
enabled = str(option.enabled).lower()
|
enabled = str(option.enabled).lower()
|
||||||
name = option.identifier
|
name = option.identifier
|
||||||
option_decls.append(
|
option_decls.append(f" dcsLiberation.plugins.{name} = {enabled}")
|
||||||
f" dcsLiberation.plugins.{name} = {enabled}")
|
|
||||||
|
|
||||||
joined_options = "\n".join(option_decls)
|
joined_options = "\n".join(option_decls)
|
||||||
|
|
||||||
lua = textwrap.dedent(f"""\
|
lua = textwrap.dedent(
|
||||||
|
f"""\
|
||||||
-- {self.identifier} plugin configuration.
|
-- {self.identifier} plugin configuration.
|
||||||
|
|
||||||
if dcsLiberation then
|
if dcsLiberation then
|
||||||
@ -171,10 +178,10 @@ class LuaPlugin(PluginSettings):
|
|||||||
{joined_options}
|
{joined_options}
|
||||||
end
|
end
|
||||||
|
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
operation.inject_lua_trigger(
|
operation.inject_lua_trigger(lua, f"{self.identifier} plugin configuration")
|
||||||
lua, f"{self.identifier} plugin configuration")
|
|
||||||
|
|
||||||
for work_order in self.definition.config_work_orders:
|
for work_order in self.definition.config_work_orders:
|
||||||
work_order.work(operation)
|
work_order.work(operation)
|
||||||
|
|||||||
@ -27,7 +27,8 @@ class LuaPluginManager:
|
|||||||
if not plugin_path.exists():
|
if not plugin_path.exists():
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"Invalid plugin configuration: required plugin {name} "
|
f"Invalid plugin configuration: required plugin {name} "
|
||||||
f"does not exist at {plugin_path}")
|
f"does not exist at {plugin_path}"
|
||||||
|
)
|
||||||
logging.info(f"Loading plugin {name} from {plugin_path}")
|
logging.info(f"Loading plugin {name} from {plugin_path}")
|
||||||
plugin = LuaPlugin.from_json(name, plugin_path)
|
plugin = LuaPlugin.from_json(name, plugin_path)
|
||||||
if plugin is not None:
|
if plugin is not None:
|
||||||
|
|||||||
@ -2,7 +2,6 @@ from dcs import Point
|
|||||||
|
|
||||||
|
|
||||||
class PointWithHeading(Point):
|
class PointWithHeading(Point):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(PointWithHeading, self).__init__(0, 0)
|
super(PointWithHeading, self).__init__(0, 0)
|
||||||
self.heading = 0
|
self.heading = 0
|
||||||
|
|||||||
@ -35,9 +35,16 @@ class AircraftProcurementRequest:
|
|||||||
|
|
||||||
|
|
||||||
class ProcurementAi:
|
class ProcurementAi:
|
||||||
def __init__(self, game: Game, for_player: bool, faction: Faction,
|
def __init__(
|
||||||
manage_runways: bool, manage_front_line: bool,
|
self,
|
||||||
manage_aircraft: bool, front_line_budget_share: float) -> None:
|
game: Game,
|
||||||
|
for_player: bool,
|
||||||
|
faction: Faction,
|
||||||
|
manage_runways: bool,
|
||||||
|
manage_front_line: bool,
|
||||||
|
manage_aircraft: bool,
|
||||||
|
front_line_budget_share: float,
|
||||||
|
) -> None:
|
||||||
if front_line_budget_share > 1.0:
|
if front_line_budget_share > 1.0:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
@ -51,8 +58,8 @@ class ProcurementAi:
|
|||||||
self.threat_zones = self.game.threat_zone_for(not self.is_player)
|
self.threat_zones = self.game.threat_zone_for(not self.is_player)
|
||||||
|
|
||||||
def spend_budget(
|
def spend_budget(
|
||||||
self, budget: float,
|
self, budget: float, aircraft_requests: List[AircraftProcurementRequest]
|
||||||
aircraft_requests: List[AircraftProcurementRequest]) -> float:
|
) -> float:
|
||||||
if self.manage_runways:
|
if self.manage_runways:
|
||||||
budget = self.repair_runways(budget)
|
budget = self.repair_runways(budget)
|
||||||
if self.manage_front_line:
|
if self.manage_front_line:
|
||||||
@ -100,25 +107,30 @@ class ProcurementAi:
|
|||||||
budget -= db.RUNWAY_REPAIR_COST
|
budget -= db.RUNWAY_REPAIR_COST
|
||||||
if self.is_player:
|
if self.is_player:
|
||||||
self.game.message(
|
self.game.message(
|
||||||
"OPFOR has begun repairing the runway at "
|
"OPFOR has begun repairing the runway at " f"{control_point}"
|
||||||
f"{control_point}"
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.game.message(
|
self.game.message(
|
||||||
"We have begun repairing the runway at "
|
"We have begun repairing the runway at " f"{control_point}"
|
||||||
f"{control_point}"
|
|
||||||
)
|
)
|
||||||
return budget
|
return budget
|
||||||
|
|
||||||
def random_affordable_ground_unit(
|
def random_affordable_ground_unit(
|
||||||
self, budget: float,
|
self, budget: float, cp: ControlPoint
|
||||||
cp: ControlPoint) -> Optional[Type[VehicleType]]:
|
) -> Optional[Type[VehicleType]]:
|
||||||
affordable_units = [u for u in self.faction.frontline_units + self.faction.artillery_units if
|
affordable_units = [
|
||||||
db.PRICES[u] <= budget]
|
u
|
||||||
|
for u in self.faction.frontline_units + self.faction.artillery_units
|
||||||
|
if db.PRICES[u] <= budget
|
||||||
|
]
|
||||||
|
|
||||||
total_number_aa = cp.base.total_frontline_aa + cp.pending_frontline_aa_deliveries_count
|
total_number_aa = (
|
||||||
total_non_aa = cp.base.total_armor + cp.pending_deliveries_count - total_number_aa
|
cp.base.total_frontline_aa + cp.pending_frontline_aa_deliveries_count
|
||||||
max_aa = math.ceil(total_non_aa/8)
|
)
|
||||||
|
total_non_aa = (
|
||||||
|
cp.base.total_armor + cp.pending_deliveries_count - total_number_aa
|
||||||
|
)
|
||||||
|
max_aa = math.ceil(total_non_aa / 8)
|
||||||
|
|
||||||
# Limit the number of AA units the AI will buy
|
# Limit the number of AA units the AI will buy
|
||||||
if not total_number_aa < max_aa:
|
if not total_number_aa < max_aa:
|
||||||
@ -150,8 +162,12 @@ class ProcurementAi:
|
|||||||
return budget
|
return budget
|
||||||
|
|
||||||
def _affordable_aircraft_of_types(
|
def _affordable_aircraft_of_types(
|
||||||
self, types: List[Type[FlyingType]], airbase: ControlPoint,
|
self,
|
||||||
number: int, max_price: float) -> Optional[Type[FlyingType]]:
|
types: List[Type[FlyingType]],
|
||||||
|
airbase: ControlPoint,
|
||||||
|
number: int,
|
||||||
|
max_price: float,
|
||||||
|
) -> Optional[Type[FlyingType]]:
|
||||||
best_choice: Optional[Type[FlyingType]] = None
|
best_choice: Optional[Type[FlyingType]] = None
|
||||||
for unit in [u for u in self.faction.aircrafts if u in types]:
|
for unit in [u for u in self.faction.aircrafts if u in types]:
|
||||||
if db.PRICES[unit] * number > max_price:
|
if db.PRICES[unit] * number > max_price:
|
||||||
@ -168,15 +184,15 @@ class ProcurementAi:
|
|||||||
return best_choice
|
return best_choice
|
||||||
|
|
||||||
def affordable_aircraft_for(
|
def affordable_aircraft_for(
|
||||||
self, request: AircraftProcurementRequest,
|
self, request: AircraftProcurementRequest, airbase: ControlPoint, budget: float
|
||||||
airbase: ControlPoint, budget: float) -> Optional[Type[FlyingType]]:
|
) -> Optional[Type[FlyingType]]:
|
||||||
return self._affordable_aircraft_of_types(
|
return self._affordable_aircraft_of_types(
|
||||||
aircraft_for_task(request.task_capability),
|
aircraft_for_task(request.task_capability), airbase, request.number, budget
|
||||||
airbase, request.number, budget)
|
)
|
||||||
|
|
||||||
def purchase_aircraft(
|
def purchase_aircraft(
|
||||||
self, budget: float,
|
self, budget: float, aircraft_requests: List[AircraftProcurementRequest]
|
||||||
aircraft_requests: List[AircraftProcurementRequest]) -> float:
|
) -> float:
|
||||||
for request in aircraft_requests:
|
for request in aircraft_requests:
|
||||||
for airbase in self.best_airbases_for(request):
|
for airbase in self.best_airbases_for(request):
|
||||||
unit = self.affordable_aircraft_for(request, airbase, budget)
|
unit = self.affordable_aircraft_for(request, airbase, budget)
|
||||||
@ -201,11 +217,9 @@ class ProcurementAi:
|
|||||||
return self.game.theater.enemy_points()
|
return self.game.theater.enemy_points()
|
||||||
|
|
||||||
def best_airbases_for(
|
def best_airbases_for(
|
||||||
self,
|
self, request: AircraftProcurementRequest
|
||||||
request: AircraftProcurementRequest) -> Iterator[ControlPoint]:
|
) -> Iterator[ControlPoint]:
|
||||||
distance_cache = ObjectiveDistanceCache.get_closest_airfields(
|
distance_cache = ObjectiveDistanceCache.get_closest_airfields(request.near)
|
||||||
request.near
|
|
||||||
)
|
|
||||||
threatened = []
|
threatened = []
|
||||||
for cp in distance_cache.airfields_within(request.range):
|
for cp in distance_cache.airfields_within(request.range):
|
||||||
if not cp.is_friendly(self.is_player):
|
if not cp.is_friendly(self.is_player):
|
||||||
|
|||||||
@ -59,8 +59,7 @@ class Settings:
|
|||||||
def plugin_settings_key(identifier: str) -> str:
|
def plugin_settings_key(identifier: str) -> str:
|
||||||
return f"plugins.{identifier}"
|
return f"plugins.{identifier}"
|
||||||
|
|
||||||
def initialize_plugin_option(self, identifier: str,
|
def initialize_plugin_option(self, identifier: str, default_value: bool) -> None:
|
||||||
default_value: bool) -> None:
|
|
||||||
try:
|
try:
|
||||||
self.plugin_option(identifier)
|
self.plugin_option(identifier)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|||||||
@ -22,7 +22,6 @@ BASE_MIN_STRENGTH = 0
|
|||||||
|
|
||||||
|
|
||||||
class Base:
|
class Base:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.aircraft: Dict[Type[FlyingType], int] = {}
|
self.aircraft: Dict[Type[FlyingType], int] = {}
|
||||||
self.armor: Dict[Type[VehicleType], int] = {}
|
self.armor: Dict[Type[VehicleType], int] = {}
|
||||||
@ -57,23 +56,43 @@ class Base:
|
|||||||
return sum(self.aa.values())
|
return sum(self.aa.values())
|
||||||
|
|
||||||
def total_units(self, task: Task) -> int:
|
def total_units(self, task: Task) -> int:
|
||||||
return sum([c for t, c in itertools.chain(self.aircraft.items(), self.armor.items(), self.aa.items()) if t in db.UNIT_BY_TASK[task]])
|
return sum(
|
||||||
|
[
|
||||||
|
c
|
||||||
|
for t, c in itertools.chain(
|
||||||
|
self.aircraft.items(), self.armor.items(), self.aa.items()
|
||||||
|
)
|
||||||
|
if t in db.UNIT_BY_TASK[task]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
def total_units_of_type(self, unit_type) -> int:
|
def total_units_of_type(self, unit_type) -> int:
|
||||||
return sum([c for t, c in itertools.chain(self.aircraft.items(), self.armor.items(), self.aa.items()) if t == unit_type])
|
return sum(
|
||||||
|
[
|
||||||
|
c
|
||||||
|
for t, c in itertools.chain(
|
||||||
|
self.aircraft.items(), self.armor.items(), self.aa.items()
|
||||||
|
)
|
||||||
|
if t == unit_type
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def all_units(self):
|
def all_units(self):
|
||||||
return itertools.chain(self.aircraft.items(), self.armor.items(), self.aa.items())
|
return itertools.chain(
|
||||||
|
self.aircraft.items(), self.armor.items(), self.aa.items()
|
||||||
|
)
|
||||||
|
|
||||||
def _find_best_unit(self, available_units: Dict[UnitType, int],
|
def _find_best_unit(
|
||||||
for_type: Task, count: int) -> Dict[UnitType, int]:
|
self, available_units: Dict[UnitType, int], for_type: Task, count: int
|
||||||
|
) -> Dict[UnitType, int]:
|
||||||
if count <= 0:
|
if count <= 0:
|
||||||
logging.warning("{}: no units for {}".format(self, for_type))
|
logging.warning("{}: no units for {}".format(self, for_type))
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
sorted_units = [key for key in available_units if
|
sorted_units = [
|
||||||
key in db.UNIT_BY_TASK[for_type]]
|
key for key in available_units if key in db.UNIT_BY_TASK[for_type]
|
||||||
|
]
|
||||||
sorted_units.sort(key=lambda x: db.PRICES[x], reverse=True)
|
sorted_units.sort(key=lambda x: db.PRICES[x], reverse=True)
|
||||||
|
|
||||||
result: Dict[UnitType, int] = {}
|
result: Dict[UnitType, int] = {}
|
||||||
@ -94,14 +113,18 @@ class Base:
|
|||||||
logging.info("{} for {} ({}): {}".format(self, for_type, count, result))
|
logging.info("{} for {} ({}): {}".format(self, for_type, count, result))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _find_best_planes(self, for_type: Task, count: int) -> typing.Dict[FlyingType, int]:
|
def _find_best_planes(
|
||||||
|
self, for_type: Task, count: int
|
||||||
|
) -> typing.Dict[FlyingType, int]:
|
||||||
return self._find_best_unit(self.aircraft, for_type, count)
|
return self._find_best_unit(self.aircraft, for_type, count)
|
||||||
|
|
||||||
def _find_best_armor(self, for_type: Task, count: int) -> typing.Dict[Armor, int]:
|
def _find_best_armor(self, for_type: Task, count: int) -> typing.Dict[Armor, int]:
|
||||||
return self._find_best_unit(self.armor, for_type, count)
|
return self._find_best_unit(self.armor, for_type, count)
|
||||||
|
|
||||||
def append_commision_points(self, for_type, points: float) -> int:
|
def append_commision_points(self, for_type, points: float) -> int:
|
||||||
self.commision_points[for_type] = self.commision_points.get(for_type, 0) + points
|
self.commision_points[for_type] = (
|
||||||
|
self.commision_points.get(for_type, 0) + points
|
||||||
|
)
|
||||||
points = self.commision_points[for_type]
|
points = self.commision_points[for_type]
|
||||||
if points >= 1:
|
if points >= 1:
|
||||||
self.commision_points[for_type] = points - math.floor(points)
|
self.commision_points[for_type] = points - math.floor(points)
|
||||||
@ -110,7 +133,9 @@ class Base:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
def filter_units(self, applicable_units: typing.Collection):
|
def filter_units(self, applicable_units: typing.Collection):
|
||||||
self.aircraft = {k: v for k, v in self.aircraft.items() if k in applicable_units}
|
self.aircraft = {
|
||||||
|
k: v for k, v in self.aircraft.items() if k in applicable_units
|
||||||
|
}
|
||||||
self.armor = {k: v for k, v in self.armor.items() if k in applicable_units}
|
self.armor = {k: v for k, v in self.armor.items() if k in applicable_units}
|
||||||
|
|
||||||
def commision_units(self, units: typing.Dict[typing.Any, int]):
|
def commision_units(self, units: typing.Dict[typing.Any, int]):
|
||||||
@ -122,7 +147,12 @@ class Base:
|
|||||||
for_task = db.unit_task(unit_type)
|
for_task = db.unit_task(unit_type)
|
||||||
|
|
||||||
target_dict = None
|
target_dict = None
|
||||||
if for_task == AWACS or for_task == CAS or for_task == CAP or for_task == Embarking:
|
if (
|
||||||
|
for_task == AWACS
|
||||||
|
or for_task == CAS
|
||||||
|
or for_task == CAP
|
||||||
|
or for_task == Embarking
|
||||||
|
):
|
||||||
target_dict = self.aircraft
|
target_dict = self.aircraft
|
||||||
elif for_task == PinpointStrike:
|
elif for_task == PinpointStrike:
|
||||||
target_dict = self.armor
|
target_dict = self.armor
|
||||||
@ -166,12 +196,20 @@ class Base:
|
|||||||
|
|
||||||
def scramble_count(self, multiplier: float, task: Task = None) -> int:
|
def scramble_count(self, multiplier: float, task: Task = None) -> int:
|
||||||
if task:
|
if task:
|
||||||
count = sum([v for k, v in self.aircraft.items() if db.unit_task(k) == task])
|
count = sum(
|
||||||
|
[v for k, v in self.aircraft.items() if db.unit_task(k) == task]
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
count = self.total_aircraft
|
count = self.total_aircraft
|
||||||
|
|
||||||
count = int(math.ceil(count * PLANES_SCRAMBLE_FACTOR * self.strength))
|
count = int(math.ceil(count * PLANES_SCRAMBLE_FACTOR * self.strength))
|
||||||
return min(min(max(count, PLANES_SCRAMBLE_MIN_BASE), int(PLANES_SCRAMBLE_MAX_BASE * multiplier)), count)
|
return min(
|
||||||
|
min(
|
||||||
|
max(count, PLANES_SCRAMBLE_MIN_BASE),
|
||||||
|
int(PLANES_SCRAMBLE_MAX_BASE * multiplier),
|
||||||
|
),
|
||||||
|
count,
|
||||||
|
)
|
||||||
|
|
||||||
def assemble_count(self):
|
def assemble_count(self):
|
||||||
return int(self.total_armor * 0.5)
|
return int(self.total_armor * 0.5)
|
||||||
@ -202,4 +240,8 @@ class Base:
|
|||||||
return self._find_best_armor(PinpointStrike, count)
|
return self._find_best_armor(PinpointStrike, count)
|
||||||
|
|
||||||
def assemble_aa(self, count=None) -> typing.Dict[AirDefence, int]:
|
def assemble_aa(self, count=None) -> typing.Dict[AirDefence, int]:
|
||||||
return self._find_best_unit(self.aa, AirDefence, count and min(count, self.total_aa) or self.assemble_aa_count())
|
return self._find_best_unit(
|
||||||
|
self.aa,
|
||||||
|
AirDefence,
|
||||||
|
count and min(count, self.total_aa) or self.assemble_aa_count(),
|
||||||
|
)
|
||||||
|
|||||||
@ -158,7 +158,8 @@ class MizCampaignLoader:
|
|||||||
|
|
||||||
def country(self, blue: bool) -> Country:
|
def country(self, blue: bool) -> Country:
|
||||||
country = self.mission.country(
|
country = self.mission.country(
|
||||||
self.BLUE_COUNTRY.name if blue else self.RED_COUNTRY.name)
|
self.BLUE_COUNTRY.name if blue else self.RED_COUNTRY.name
|
||||||
|
)
|
||||||
# Should be guaranteed because we initialized them.
|
# Should be guaranteed because we initialized them.
|
||||||
assert country
|
assert country
|
||||||
return country
|
return country
|
||||||
@ -255,22 +256,23 @@ class MizCampaignLoader:
|
|||||||
|
|
||||||
for blue in (False, True):
|
for blue in (False, True):
|
||||||
for group in self.off_map_spawns(blue):
|
for group in self.off_map_spawns(blue):
|
||||||
control_point = OffMapSpawn(next(self.control_point_id),
|
control_point = OffMapSpawn(
|
||||||
str(group.name), group.position)
|
next(self.control_point_id), str(group.name), group.position
|
||||||
|
)
|
||||||
control_point.captured = blue
|
control_point.captured = blue
|
||||||
control_point.captured_invert = group.late_activation
|
control_point.captured_invert = group.late_activation
|
||||||
control_points[control_point.id] = control_point
|
control_points[control_point.id] = control_point
|
||||||
for group in self.carriers(blue):
|
for group in self.carriers(blue):
|
||||||
# TODO: Name the carrier.
|
# TODO: Name the carrier.
|
||||||
control_point = Carrier(
|
control_point = Carrier(
|
||||||
"carrier", group.position, next(self.control_point_id))
|
"carrier", group.position, next(self.control_point_id)
|
||||||
|
)
|
||||||
control_point.captured = blue
|
control_point.captured = blue
|
||||||
control_point.captured_invert = group.late_activation
|
control_point.captured_invert = group.late_activation
|
||||||
control_points[control_point.id] = control_point
|
control_points[control_point.id] = control_point
|
||||||
for group in self.lhas(blue):
|
for group in self.lhas(blue):
|
||||||
# TODO: Name the LHA.
|
# TODO: Name the LHA.
|
||||||
control_point = Lha(
|
control_point = Lha("lha", group.position, next(self.control_point_id))
|
||||||
"lha", group.position, next(self.control_point_id))
|
|
||||||
control_point.captured = blue
|
control_point.captured = blue
|
||||||
control_point.captured_invert = group.late_activation
|
control_point.captured_invert = group.late_activation
|
||||||
control_points[control_point.id] = control_point
|
control_points[control_point.id] = control_point
|
||||||
@ -302,21 +304,21 @@ class MizCampaignLoader:
|
|||||||
origin = self.theater.closest_control_point(waypoints[0])
|
origin = self.theater.closest_control_point(waypoints[0])
|
||||||
if origin is None:
|
if origin is None:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"No control point near the first waypoint of {group.name}")
|
f"No control point near the first waypoint of {group.name}"
|
||||||
|
)
|
||||||
destination = self.theater.closest_control_point(waypoints[-1])
|
destination = self.theater.closest_control_point(waypoints[-1])
|
||||||
if destination is None:
|
if destination is None:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"No control point near the final waypoint of {group.name}")
|
f"No control point near the final waypoint of {group.name}"
|
||||||
|
)
|
||||||
|
|
||||||
# Snap the begin and end points to the control points.
|
# Snap the begin and end points to the control points.
|
||||||
waypoints[0] = origin.position
|
waypoints[0] = origin.position
|
||||||
waypoints[-1] = destination.position
|
waypoints[-1] = destination.position
|
||||||
front_line_id = f"{origin.id}|{destination.id}"
|
front_line_id = f"{origin.id}|{destination.id}"
|
||||||
front_lines[front_line_id] = ComplexFrontLine(origin, waypoints)
|
front_lines[front_line_id] = ComplexFrontLine(origin, waypoints)
|
||||||
self.control_points[origin.id].connect(
|
self.control_points[origin.id].connect(self.control_points[destination.id])
|
||||||
self.control_points[destination.id])
|
self.control_points[destination.id].connect(self.control_points[origin.id])
|
||||||
self.control_points[destination.id].connect(
|
|
||||||
self.control_points[origin.id])
|
|
||||||
return front_lines
|
return front_lines
|
||||||
|
|
||||||
def objective_info(self, group: Group) -> Tuple[ControlPoint, Distance]:
|
def objective_info(self, group: Group) -> Tuple[ControlPoint, Distance]:
|
||||||
@ -329,52 +331,63 @@ class MizCampaignLoader:
|
|||||||
closest, distance = self.objective_info(group)
|
closest, distance = self.objective_info(group)
|
||||||
if distance < self.BASE_DEFENSE_RADIUS:
|
if distance < self.BASE_DEFENSE_RADIUS:
|
||||||
closest.preset_locations.base_garrisons.append(
|
closest.preset_locations.base_garrisons.append(
|
||||||
PointWithHeading.from_point(group.position, group.units[0].heading))
|
PointWithHeading.from_point(group.position, group.units[0].heading)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logging.warning(
|
logging.warning(f"Found garrison unit too far from base: {group.name}")
|
||||||
f"Found garrison unit too far from base: {group.name}")
|
|
||||||
|
|
||||||
for group in self.sams:
|
for group in self.sams:
|
||||||
closest, distance = self.objective_info(group)
|
closest, distance = self.objective_info(group)
|
||||||
if distance < self.BASE_DEFENSE_RADIUS:
|
if distance < self.BASE_DEFENSE_RADIUS:
|
||||||
closest.preset_locations.base_air_defense.append(
|
closest.preset_locations.base_air_defense.append(
|
||||||
PointWithHeading.from_point(group.position, group.units[0].heading))
|
PointWithHeading.from_point(group.position, group.units[0].heading)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
closest.preset_locations.strike_locations.append(
|
closest.preset_locations.strike_locations.append(
|
||||||
PointWithHeading.from_point(group.position, group.units[0].heading))
|
PointWithHeading.from_point(group.position, group.units[0].heading)
|
||||||
|
)
|
||||||
|
|
||||||
for group in self.ewrs:
|
for group in self.ewrs:
|
||||||
closest, distance = self.objective_info(group)
|
closest, distance = self.objective_info(group)
|
||||||
closest.preset_locations.ewrs.append(PointWithHeading.from_point(group.position, group.units[0].heading))
|
closest.preset_locations.ewrs.append(
|
||||||
|
PointWithHeading.from_point(group.position, group.units[0].heading)
|
||||||
|
)
|
||||||
|
|
||||||
for group in self.offshore_strike_targets:
|
for group in self.offshore_strike_targets:
|
||||||
closest, distance = self.objective_info(group)
|
closest, distance = self.objective_info(group)
|
||||||
closest.preset_locations.offshore_strike_locations.append(
|
closest.preset_locations.offshore_strike_locations.append(
|
||||||
PointWithHeading.from_point(group.position, group.units[0].heading))
|
PointWithHeading.from_point(group.position, group.units[0].heading)
|
||||||
|
)
|
||||||
|
|
||||||
for group in self.ships:
|
for group in self.ships:
|
||||||
closest, distance = self.objective_info(group)
|
closest, distance = self.objective_info(group)
|
||||||
closest.preset_locations.ships.append(PointWithHeading.from_point(group.position, group.units[0].heading))
|
closest.preset_locations.ships.append(
|
||||||
|
PointWithHeading.from_point(group.position, group.units[0].heading)
|
||||||
|
)
|
||||||
|
|
||||||
for group in self.missile_sites:
|
for group in self.missile_sites:
|
||||||
closest, distance = self.objective_info(group)
|
closest, distance = self.objective_info(group)
|
||||||
closest.preset_locations.missile_sites.append(
|
closest.preset_locations.missile_sites.append(
|
||||||
PointWithHeading.from_point(group.position, group.units[0].heading))
|
PointWithHeading.from_point(group.position, group.units[0].heading)
|
||||||
|
)
|
||||||
|
|
||||||
for group in self.coastal_defenses:
|
for group in self.coastal_defenses:
|
||||||
closest, distance = self.objective_info(group)
|
closest, distance = self.objective_info(group)
|
||||||
closest.preset_locations.coastal_defenses.append(
|
closest.preset_locations.coastal_defenses.append(
|
||||||
PointWithHeading.from_point(group.position, group.units[0].heading))
|
PointWithHeading.from_point(group.position, group.units[0].heading)
|
||||||
|
)
|
||||||
|
|
||||||
for group in self.required_long_range_sams:
|
for group in self.required_long_range_sams:
|
||||||
closest, distance = self.objective_info(group)
|
closest, distance = self.objective_info(group)
|
||||||
closest.preset_locations.required_long_range_sams.append(
|
closest.preset_locations.required_long_range_sams.append(
|
||||||
PointWithHeading.from_point(group.position, group.units[0].heading))
|
PointWithHeading.from_point(group.position, group.units[0].heading)
|
||||||
|
)
|
||||||
|
|
||||||
for group in self.required_medium_range_sams:
|
for group in self.required_medium_range_sams:
|
||||||
closest, distance = self.objective_info(group)
|
closest, distance = self.objective_info(group)
|
||||||
closest.preset_locations.required_medium_range_sams.append(
|
closest.preset_locations.required_medium_range_sams.append(
|
||||||
PointWithHeading.from_point(group.position, group.units[0].heading))
|
PointWithHeading.from_point(group.position, group.units[0].heading)
|
||||||
|
)
|
||||||
|
|
||||||
def populate_theater(self) -> None:
|
def populate_theater(self) -> None:
|
||||||
for control_point in self.control_points.values():
|
for control_point in self.control_points.values():
|
||||||
@ -428,8 +441,9 @@ class ConflictTheater:
|
|||||||
logging.warning("Replacing existing frontline data")
|
logging.warning("Replacing existing frontline data")
|
||||||
self._frontline_data = data
|
self._frontline_data = data
|
||||||
|
|
||||||
def add_controlpoint(self, point: ControlPoint,
|
def add_controlpoint(
|
||||||
connected_to: Optional[List[ControlPoint]] = None):
|
self, point: ControlPoint, connected_to: Optional[List[ControlPoint]] = None
|
||||||
|
):
|
||||||
if connected_to is None:
|
if connected_to is None:
|
||||||
connected_to = []
|
connected_to = []
|
||||||
for connected_point in connected_to:
|
for connected_point in connected_to:
|
||||||
@ -503,7 +517,7 @@ class ConflictTheater:
|
|||||||
nearest_point = Point(nearest_point.x, nearest_point.y)
|
nearest_point = Point(nearest_point.x, nearest_point.y)
|
||||||
new_point = point.point_from_heading(
|
new_point = point.point_from_heading(
|
||||||
point.heading_between_point(nearest_point),
|
point.heading_between_point(nearest_point),
|
||||||
point.distance_to_point(nearest_point) + extend_dist
|
point.distance_to_point(nearest_point) + extend_dist,
|
||||||
)
|
)
|
||||||
return new_point
|
return new_point
|
||||||
|
|
||||||
@ -517,7 +531,9 @@ class ConflictTheater:
|
|||||||
|
|
||||||
def conflicts(self, from_player=True) -> Iterator[FrontLine]:
|
def conflicts(self, from_player=True) -> Iterator[FrontLine]:
|
||||||
for cp in [x for x in self.controlpoints if x.captured == from_player]:
|
for cp in [x for x in self.controlpoints if x.captured == from_player]:
|
||||||
for connected_point in [x for x in cp.connected_points if x.captured != from_player]:
|
for connected_point in [
|
||||||
|
x for x in cp.connected_points if x.captured != from_player
|
||||||
|
]:
|
||||||
yield FrontLine(cp, connected_point, self)
|
yield FrontLine(cp, connected_point, self)
|
||||||
|
|
||||||
def enemy_points(self) -> List[ControlPoint]:
|
def enemy_points(self) -> List[ControlPoint]:
|
||||||
@ -572,17 +588,22 @@ class ConflictTheater:
|
|||||||
distances[cp.id] = dist
|
distances[cp.id] = dist
|
||||||
closest_cp_id = min(distances, key=distances.get) # type: ignore
|
closest_cp_id = min(distances, key=distances.get) # type: ignore
|
||||||
|
|
||||||
all_cp_min_distances[(control_point.id, closest_cp_id)] = distances[closest_cp_id]
|
all_cp_min_distances[(control_point.id, closest_cp_id)] = distances[
|
||||||
|
closest_cp_id
|
||||||
|
]
|
||||||
closest_opposing_cps = [
|
closest_opposing_cps = [
|
||||||
self.find_control_point_by_id(i)
|
self.find_control_point_by_id(i)
|
||||||
for i
|
for i in min(
|
||||||
in min(all_cp_min_distances, key=all_cp_min_distances.get) # type: ignore
|
all_cp_min_distances, key=all_cp_min_distances.get
|
||||||
|
) # type: ignore
|
||||||
] # type: List[ControlPoint]
|
] # type: List[ControlPoint]
|
||||||
assert len(closest_opposing_cps) == 2
|
assert len(closest_opposing_cps) == 2
|
||||||
if closest_opposing_cps[0].captured:
|
if closest_opposing_cps[0].captured:
|
||||||
return cast(Tuple[ControlPoint, ControlPoint], tuple(closest_opposing_cps))
|
return cast(Tuple[ControlPoint, ControlPoint], tuple(closest_opposing_cps))
|
||||||
else:
|
else:
|
||||||
return cast(Tuple[ControlPoint, ControlPoint], tuple(reversed(closest_opposing_cps)))
|
return cast(
|
||||||
|
Tuple[ControlPoint, ControlPoint], tuple(reversed(closest_opposing_cps))
|
||||||
|
)
|
||||||
|
|
||||||
def find_control_point_by_id(self, id: int) -> ControlPoint:
|
def find_control_point_by_id(self, id: int) -> ControlPoint:
|
||||||
for i in self.controlpoints:
|
for i in self.controlpoints:
|
||||||
@ -677,8 +698,7 @@ class PersianGulfTheater(ConflictTheater):
|
|||||||
terrain = persiangulf.PersianGulf()
|
terrain = persiangulf.PersianGulf()
|
||||||
overview_image = "persiangulf.gif"
|
overview_image = "persiangulf.gif"
|
||||||
reference_points = (
|
reference_points = (
|
||||||
ReferencePoint(persiangulf.Jiroft_Airport.position,
|
ReferencePoint(persiangulf.Jiroft_Airport.position, Point(1692, 1343)),
|
||||||
Point(1692, 1343)),
|
|
||||||
ReferencePoint(persiangulf.Liwa_Airbase.position, Point(358, 3238)),
|
ReferencePoint(persiangulf.Liwa_Airbase.position, Point(358, 3238)),
|
||||||
)
|
)
|
||||||
landmap = load_landmap("resources\\gulflandmap.p")
|
landmap = load_landmap("resources\\gulflandmap.p")
|
||||||
@ -727,7 +747,7 @@ class TheChannelTheater(ConflictTheater):
|
|||||||
overview_image = "thechannel.gif"
|
overview_image = "thechannel.gif"
|
||||||
reference_points = (
|
reference_points = (
|
||||||
ReferencePoint(thechannel.Abbeville_Drucat.position, Point(2005, 2390)),
|
ReferencePoint(thechannel.Abbeville_Drucat.position, Point(2005, 2390)),
|
||||||
ReferencePoint(thechannel.Detling.position, Point(706, 382))
|
ReferencePoint(thechannel.Detling.position, Point(706, 382)),
|
||||||
)
|
)
|
||||||
landmap = load_landmap("resources\\channellandmap.p")
|
landmap = load_landmap("resources\\channellandmap.p")
|
||||||
daytime_map = {
|
daytime_map = {
|
||||||
@ -796,7 +816,7 @@ class FrontLine(MissionTarget):
|
|||||||
self,
|
self,
|
||||||
control_point_a: ControlPoint,
|
control_point_a: ControlPoint,
|
||||||
control_point_b: ControlPoint,
|
control_point_b: ControlPoint,
|
||||||
theater: ConflictTheater
|
theater: ConflictTheater,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.control_point_a = control_point_a
|
self.control_point_a = control_point_a
|
||||||
self.control_point_b = control_point_b
|
self.control_point_b = control_point_b
|
||||||
@ -943,7 +963,7 @@ class FrontLine(MissionTarget):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load_json_frontlines(
|
def load_json_frontlines(
|
||||||
theater: ConflictTheater
|
theater: ConflictTheater,
|
||||||
) -> Optional[Dict[str, ComplexFrontLine]]:
|
) -> Optional[Dict[str, ComplexFrontLine]]:
|
||||||
"""Load complex frontlines from json"""
|
"""Load complex frontlines from json"""
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -71,6 +71,7 @@ class LocationType(Enum):
|
|||||||
Shorad = "SHORAD"
|
Shorad = "SHORAD"
|
||||||
StrikeTarget = "strike target"
|
StrikeTarget = "strike target"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PresetLocations:
|
class PresetLocations:
|
||||||
"""Defines the preset locations loaded from the campaign mission file."""
|
"""Defines the preset locations loaded from the campaign mission file."""
|
||||||
@ -230,10 +231,17 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
# TODO: Only airbases have IDs.
|
# TODO: Only airbases have IDs.
|
||||||
# TODO: has_frontline is only reasonable for airbases.
|
# TODO: has_frontline is only reasonable for airbases.
|
||||||
# TODO: cptype is obsolete.
|
# TODO: cptype is obsolete.
|
||||||
def __init__(self, cp_id: int, name: str, position: Point,
|
def __init__(
|
||||||
at: db.StartingPosition, size: int,
|
self,
|
||||||
importance: float, has_frontline=True,
|
cp_id: int,
|
||||||
cptype=ControlPointType.AIRBASE):
|
name: str,
|
||||||
|
position: Point,
|
||||||
|
at: db.StartingPosition,
|
||||||
|
size: int,
|
||||||
|
importance: float,
|
||||||
|
has_frontline=True,
|
||||||
|
cptype=ControlPointType.AIRBASE,
|
||||||
|
):
|
||||||
super().__init__(name, position)
|
super().__init__(name, position)
|
||||||
# TODO: Should be Airbase specific.
|
# TODO: Should be Airbase specific.
|
||||||
self.id = cp_id
|
self.id = cp_id
|
||||||
@ -256,6 +264,7 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
# TODO: Should be Airbase specific.
|
# TODO: Should be Airbase specific.
|
||||||
self.stances: Dict[int, CombatStance] = {}
|
self.stances: Dict[int, CombatStance] = {}
|
||||||
from ..event import UnitsDeliveryEvent
|
from ..event import UnitsDeliveryEvent
|
||||||
|
|
||||||
self.pending_unit_deliveries = UnitsDeliveryEvent(self)
|
self.pending_unit_deliveries = UnitsDeliveryEvent(self)
|
||||||
|
|
||||||
self.target_position: Optional[Point] = None
|
self.target_position: Optional[Point] = None
|
||||||
@ -265,8 +274,7 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def ground_objects(self) -> List[TheaterGroundObject]:
|
def ground_objects(self) -> List[TheaterGroundObject]:
|
||||||
return list(
|
return list(itertools.chain(self.connected_objectives, self.base_defenses))
|
||||||
itertools.chain(self.connected_objectives, self.base_defenses))
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@ -341,15 +349,18 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
Get the carrier group name if the airbase is a carrier
|
Get the carrier group name if the airbase is a carrier
|
||||||
:return: Carrier group name
|
:return: Carrier group name
|
||||||
"""
|
"""
|
||||||
if self.cptype in [ControlPointType.AIRCRAFT_CARRIER_GROUP,
|
if self.cptype in [
|
||||||
ControlPointType.LHA_GROUP]:
|
ControlPointType.AIRCRAFT_CARRIER_GROUP,
|
||||||
|
ControlPointType.LHA_GROUP,
|
||||||
|
]:
|
||||||
for g in self.ground_objects:
|
for g in self.ground_objects:
|
||||||
if g.dcs_identifier == "CARRIER":
|
if g.dcs_identifier == "CARRIER":
|
||||||
for group in g.groups:
|
for group in g.groups:
|
||||||
for u in group.units:
|
for u in group.units:
|
||||||
if db.unit_type_from_name(u.type) in [
|
if db.unit_type_from_name(u.type) in [
|
||||||
CVN_74_John_C__Stennis,
|
CVN_74_John_C__Stennis,
|
||||||
CV_1143_5_Admiral_Kuznetsov]:
|
CV_1143_5_Admiral_Kuznetsov,
|
||||||
|
]:
|
||||||
return group.name
|
return group.name
|
||||||
elif g.dcs_identifier == "LHA":
|
elif g.dcs_identifier == "LHA":
|
||||||
for group in g.groups:
|
for group in g.groups:
|
||||||
@ -385,7 +396,8 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
else:
|
else:
|
||||||
logging.error(
|
logging.error(
|
||||||
"Could not determine preset location type for "
|
"Could not determine preset location type for "
|
||||||
f"{base_defense}. Assuming garrison type.")
|
f"{base_defense}. Assuming garrison type."
|
||||||
|
)
|
||||||
self.preset_locations.base_garrisons.append(p)
|
self.preset_locations.base_garrisons.append(p)
|
||||||
self.base_defenses = []
|
self.base_defenses = []
|
||||||
|
|
||||||
@ -395,15 +407,18 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
game.adjust_budget(total, player=not self.captured)
|
game.adjust_budget(total, player=not self.captured)
|
||||||
game.message(
|
game.message(
|
||||||
f"{self.name} is not connected to any friendly points. Ground "
|
f"{self.name} is not connected to any friendly points. Ground "
|
||||||
f"vehicles have been captured and sold for ${total}M.")
|
f"vehicles have been captured and sold for ${total}M."
|
||||||
|
)
|
||||||
|
|
||||||
def retreat_ground_units(self, game: Game):
|
def retreat_ground_units(self, game: Game):
|
||||||
# When there are multiple valid destinations, deliver units to whichever
|
# When there are multiple valid destinations, deliver units to whichever
|
||||||
# base is least defended first. The closest approximation of unit
|
# base is least defended first. The closest approximation of unit
|
||||||
# strength we have is price
|
# strength we have is price
|
||||||
destinations = [GroundUnitDestination(cp)
|
destinations = [
|
||||||
|
GroundUnitDestination(cp)
|
||||||
for cp in self.connected_points
|
for cp in self.connected_points
|
||||||
if cp.captured == self.captured]
|
if cp.captured == self.captured
|
||||||
|
]
|
||||||
if not destinations:
|
if not destinations:
|
||||||
self.capture_equipment(game)
|
self.capture_equipment(game)
|
||||||
return
|
return
|
||||||
@ -416,8 +431,9 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
destination.control_point.base.commision_units({unit_type: 1})
|
destination.control_point.base.commision_units({unit_type: 1})
|
||||||
destination = heapq.heappushpop(destinations, destination)
|
destination = heapq.heappushpop(destinations, destination)
|
||||||
|
|
||||||
def capture_aircraft(self, game: Game, airframe: Type[FlyingType],
|
def capture_aircraft(
|
||||||
count: int) -> None:
|
self, game: Game, airframe: Type[FlyingType], count: int
|
||||||
|
) -> None:
|
||||||
try:
|
try:
|
||||||
value = PRICES[airframe] * count
|
value = PRICES[airframe] * count
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -428,11 +444,12 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
game.message(
|
game.message(
|
||||||
f"No valid retreat destination in range of {self.name} for "
|
f"No valid retreat destination in range of {self.name} for "
|
||||||
f"{airframe.id}. {count} aircraft have been captured and sold for "
|
f"{airframe.id}. {count} aircraft have been captured and sold for "
|
||||||
f"${value}M.")
|
f"${value}M."
|
||||||
|
)
|
||||||
|
|
||||||
def aircraft_retreat_destination(
|
def aircraft_retreat_destination(
|
||||||
self, game: Game,
|
self, game: Game, airframe: Type[FlyingType]
|
||||||
airframe: Type[FlyingType]) -> Optional[ControlPoint]:
|
) -> Optional[ControlPoint]:
|
||||||
closest = ObjectiveDistanceCache.get_closest_airfields(self)
|
closest = ObjectiveDistanceCache.get_closest_airfields(self)
|
||||||
# TODO: Should be airframe dependent.
|
# TODO: Should be airframe dependent.
|
||||||
max_retreat_distance = nautical_miles(200)
|
max_retreat_distance = nautical_miles(200)
|
||||||
@ -448,8 +465,9 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
return airbase
|
return airbase
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _retreat_air_units(self, game: Game, airframe: Type[FlyingType],
|
def _retreat_air_units(
|
||||||
count: int) -> None:
|
self, game: Game, airframe: Type[FlyingType], count: int
|
||||||
|
) -> None:
|
||||||
while count:
|
while count:
|
||||||
logging.debug(f"Retreating {count} {airframe.id} from {self.name}")
|
logging.debug(f"Retreating {count} {airframe.id} from {self.name}")
|
||||||
destination = self.aircraft_retreat_destination(game, airframe)
|
destination = self.aircraft_retreat_destination(game, airframe)
|
||||||
@ -482,6 +500,7 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
|
|
||||||
self.clear_base_defenses()
|
self.clear_base_defenses()
|
||||||
from .start_generator import BaseDefenseGenerator
|
from .start_generator import BaseDefenseGenerator
|
||||||
|
|
||||||
BaseDefenseGenerator(game, self).generate()
|
BaseDefenseGenerator(game, self).generate()
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@ -511,16 +530,19 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
if issubclass(unit_bought, FlyingType):
|
if issubclass(unit_bought, FlyingType):
|
||||||
on_order += self.pending_unit_deliveries.units[unit_bought]
|
on_order += self.pending_unit_deliveries.units[unit_bought]
|
||||||
|
|
||||||
return PendingOccupancy(self.base.total_aircraft, on_order,
|
return PendingOccupancy(
|
||||||
self.aircraft_transferring(game))
|
self.base.total_aircraft, on_order, self.aircraft_transferring(game)
|
||||||
|
)
|
||||||
|
|
||||||
def unclaimed_parking(self, game: Game) -> int:
|
def unclaimed_parking(self, game: Game) -> int:
|
||||||
return (self.total_aircraft_parking -
|
return (
|
||||||
self.expected_aircraft_next_turn(game).total)
|
self.total_aircraft_parking - self.expected_aircraft_next_turn(game).total
|
||||||
|
)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def active_runway(self, conditions: Conditions,
|
def active_runway(
|
||||||
dynamic_runways: Dict[str, RunwayData]) -> RunwayData:
|
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
|
||||||
|
) -> RunwayData:
|
||||||
...
|
...
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -571,7 +593,13 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
Get number of pending frontline aa units
|
Get number of pending frontline aa units
|
||||||
"""
|
"""
|
||||||
if self.pending_unit_deliveries:
|
if self.pending_unit_deliveries:
|
||||||
return sum([v for k,v in self.pending_unit_deliveries.units.items() if k in TYPE_SHORAD])
|
return sum(
|
||||||
|
[
|
||||||
|
v
|
||||||
|
for k, v in self.pending_unit_deliveries.units.items()
|
||||||
|
if k in TYPE_SHORAD
|
||||||
|
]
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@ -595,9 +623,12 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
continue
|
continue
|
||||||
on_order += self.pending_unit_deliveries.units[unit_bought]
|
on_order += self.pending_unit_deliveries.units[unit_bought]
|
||||||
|
|
||||||
return PendingOccupancy(self.base.total_armor, on_order,
|
return PendingOccupancy(
|
||||||
|
self.base.total_armor,
|
||||||
|
on_order,
|
||||||
# Ground unit transfers not yet implemented.
|
# Ground unit transfers not yet implemented.
|
||||||
transferring=0)
|
transferring=0,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def income_per_turn(self) -> int:
|
def income_per_turn(self) -> int:
|
||||||
@ -605,6 +636,7 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
|
|
||||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||||
from gen.flights.flight import FlightType
|
from gen.flights.flight import FlightType
|
||||||
|
|
||||||
if self.is_friendly(for_player):
|
if self.is_friendly(for_player):
|
||||||
yield from [
|
yield from [
|
||||||
FlightType.AEWC,
|
FlightType.AEWC,
|
||||||
@ -613,17 +645,23 @@ class ControlPoint(MissionTarget, ABC):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def has_active_frontline(self) -> bool:
|
def has_active_frontline(self) -> bool:
|
||||||
return any(
|
return any(not c.is_friendly(self.captured) for c in self.connected_points)
|
||||||
not c.is_friendly(self.captured) for c in self.connected_points)
|
|
||||||
|
|
||||||
|
|
||||||
class Airfield(ControlPoint):
|
class Airfield(ControlPoint):
|
||||||
|
def __init__(
|
||||||
def __init__(self, airport: Airport, size: int,
|
self, airport: Airport, size: int, importance: float, has_frontline=True
|
||||||
importance: float, has_frontline=True):
|
):
|
||||||
super().__init__(airport.id, airport.name, airport.position, airport,
|
super().__init__(
|
||||||
size, importance, has_frontline,
|
airport.id,
|
||||||
cptype=ControlPointType.AIRBASE)
|
airport.name,
|
||||||
|
airport.position,
|
||||||
|
airport,
|
||||||
|
size,
|
||||||
|
importance,
|
||||||
|
has_frontline,
|
||||||
|
cptype=ControlPointType.AIRBASE,
|
||||||
|
)
|
||||||
self.airport = airport
|
self.airport = airport
|
||||||
self._runway_status = RunwayStatus()
|
self._runway_status = RunwayStatus()
|
||||||
|
|
||||||
@ -637,6 +675,7 @@ class Airfield(ControlPoint):
|
|||||||
|
|
||||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||||
from gen.flights.flight import FlightType
|
from gen.flights.flight import FlightType
|
||||||
|
|
||||||
if self.is_friendly(for_player):
|
if self.is_friendly(for_player):
|
||||||
yield from [
|
yield from [
|
||||||
# TODO: FlightType.INTERCEPTION
|
# TODO: FlightType.INTERCEPTION
|
||||||
@ -667,8 +706,9 @@ class Airfield(ControlPoint):
|
|||||||
def damage_runway(self) -> None:
|
def damage_runway(self) -> None:
|
||||||
self.runway_status.damage()
|
self.runway_status.damage()
|
||||||
|
|
||||||
def active_runway(self, conditions: Conditions,
|
def active_runway(
|
||||||
dynamic_runways: Dict[str, RunwayData]) -> RunwayData:
|
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
|
||||||
|
) -> RunwayData:
|
||||||
assigner = RunwayAssigner(conditions)
|
assigner = RunwayAssigner(conditions)
|
||||||
return assigner.get_preferred_runway(self.airport)
|
return assigner.get_preferred_runway(self.airport)
|
||||||
|
|
||||||
@ -686,13 +726,13 @@ class Airfield(ControlPoint):
|
|||||||
|
|
||||||
|
|
||||||
class NavalControlPoint(ControlPoint, ABC):
|
class NavalControlPoint(ControlPoint, ABC):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_fleet(self) -> bool:
|
def is_fleet(self) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||||
from gen.flights.flight import FlightType
|
from gen.flights.flight import FlightType
|
||||||
|
|
||||||
if self.is_friendly(for_player):
|
if self.is_friendly(for_player):
|
||||||
yield from [
|
yield from [
|
||||||
# TODO: FlightType.INTERCEPTION
|
# TODO: FlightType.INTERCEPTION
|
||||||
@ -716,14 +756,17 @@ class NavalControlPoint(ControlPoint, ABC):
|
|||||||
for group in g.groups:
|
for group in g.groups:
|
||||||
for u in group.units:
|
for u in group.units:
|
||||||
if db.unit_type_from_name(u.type) in [
|
if db.unit_type_from_name(u.type) in [
|
||||||
CVN_74_John_C__Stennis, LHA_1_Tarawa,
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
CV_1143_5_Admiral_Kuznetsov,
|
CV_1143_5_Admiral_Kuznetsov,
|
||||||
Type_071_Amphibious_Transport_Dock]:
|
Type_071_Amphibious_Transport_Dock,
|
||||||
|
]:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def active_runway(self, conditions: Conditions,
|
def active_runway(
|
||||||
dynamic_runways: Dict[str, RunwayData]) -> RunwayData:
|
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
|
||||||
|
) -> RunwayData:
|
||||||
# TODO: Assign TACAN and ICLS earlier so we don't need this.
|
# TODO: Assign TACAN and ICLS earlier so we don't need this.
|
||||||
fallback = RunwayData(self.full_name, runway_heading=0, runway_name="")
|
fallback = RunwayData(self.full_name, runway_heading=0, runway_name="")
|
||||||
return dynamic_runways.get(self.name, fallback)
|
return dynamic_runways.get(self.name, fallback)
|
||||||
@ -746,12 +789,19 @@ class NavalControlPoint(ControlPoint, ABC):
|
|||||||
|
|
||||||
|
|
||||||
class Carrier(NavalControlPoint):
|
class Carrier(NavalControlPoint):
|
||||||
|
|
||||||
def __init__(self, name: str, at: Point, cp_id: int):
|
def __init__(self, name: str, at: Point, cp_id: int):
|
||||||
import game.theater.conflicttheater
|
import game.theater.conflicttheater
|
||||||
super().__init__(cp_id, name, at, at,
|
|
||||||
game.theater.conflicttheater.SIZE_SMALL, 1,
|
super().__init__(
|
||||||
has_frontline=False, cptype=ControlPointType.AIRCRAFT_CARRIER_GROUP)
|
cp_id,
|
||||||
|
name,
|
||||||
|
at,
|
||||||
|
at,
|
||||||
|
game.theater.conflicttheater.SIZE_SMALL,
|
||||||
|
1,
|
||||||
|
has_frontline=False,
|
||||||
|
cptype=ControlPointType.AIRCRAFT_CARRIER_GROUP,
|
||||||
|
)
|
||||||
|
|
||||||
def capture(self, game: Game, for_player: bool) -> None:
|
def capture(self, game: Game, for_player: bool) -> None:
|
||||||
raise RuntimeError("Carriers cannot be captured")
|
raise RuntimeError("Carriers cannot be captured")
|
||||||
@ -769,12 +819,19 @@ class Carrier(NavalControlPoint):
|
|||||||
|
|
||||||
|
|
||||||
class Lha(NavalControlPoint):
|
class Lha(NavalControlPoint):
|
||||||
|
|
||||||
def __init__(self, name: str, at: Point, cp_id: int):
|
def __init__(self, name: str, at: Point, cp_id: int):
|
||||||
import game.theater.conflicttheater
|
import game.theater.conflicttheater
|
||||||
super().__init__(cp_id, name, at, at,
|
|
||||||
game.theater.conflicttheater.SIZE_SMALL, 1,
|
super().__init__(
|
||||||
has_frontline=False, cptype=ControlPointType.LHA_GROUP)
|
cp_id,
|
||||||
|
name,
|
||||||
|
at,
|
||||||
|
at,
|
||||||
|
game.theater.conflicttheater.SIZE_SMALL,
|
||||||
|
1,
|
||||||
|
has_frontline=False,
|
||||||
|
cptype=ControlPointType.LHA_GROUP,
|
||||||
|
)
|
||||||
|
|
||||||
def capture(self, game: Game, for_player: bool) -> None:
|
def capture(self, game: Game, for_player: bool) -> None:
|
||||||
raise RuntimeError("LHAs cannot be captured")
|
raise RuntimeError("LHAs cannot be captured")
|
||||||
@ -792,15 +849,22 @@ class Lha(NavalControlPoint):
|
|||||||
|
|
||||||
|
|
||||||
class OffMapSpawn(ControlPoint):
|
class OffMapSpawn(ControlPoint):
|
||||||
|
|
||||||
def runway_is_operational(self) -> bool:
|
def runway_is_operational(self) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __init__(self, cp_id: int, name: str, position: Point):
|
def __init__(self, cp_id: int, name: str, position: Point):
|
||||||
from . import IMPORTANCE_MEDIUM, SIZE_REGULAR
|
from . import IMPORTANCE_MEDIUM, SIZE_REGULAR
|
||||||
super().__init__(cp_id, name, position, at=position,
|
|
||||||
size=SIZE_REGULAR, importance=IMPORTANCE_MEDIUM,
|
super().__init__(
|
||||||
has_frontline=False, cptype=ControlPointType.OFF_MAP)
|
cp_id,
|
||||||
|
name,
|
||||||
|
position,
|
||||||
|
at=position,
|
||||||
|
size=SIZE_REGULAR,
|
||||||
|
importance=IMPORTANCE_MEDIUM,
|
||||||
|
has_frontline=False,
|
||||||
|
cptype=ControlPointType.OFF_MAP,
|
||||||
|
)
|
||||||
|
|
||||||
def capture(self, game: Game, for_player: bool) -> None:
|
def capture(self, game: Game, for_player: bool) -> None:
|
||||||
raise RuntimeError("Off map control points cannot be captured")
|
raise RuntimeError("Off map control points cannot be captured")
|
||||||
@ -819,8 +883,9 @@ class OffMapSpawn(ControlPoint):
|
|||||||
def heading(self) -> int:
|
def heading(self) -> int:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def active_runway(self, conditions: Conditions,
|
def active_runway(
|
||||||
dynamic_runways: Dict[str, RunwayData]) -> RunwayData:
|
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
|
||||||
|
) -> RunwayData:
|
||||||
logging.warning("TODO: Off map spawns have no runways.")
|
logging.warning("TODO: Off map spawns have no runways.")
|
||||||
return RunwayData(self.full_name, runway_heading=0, runway_name="")
|
return RunwayData(self.full_name, runway_heading=0, runway_name="")
|
||||||
|
|
||||||
@ -834,19 +899,27 @@ class OffMapSpawn(ControlPoint):
|
|||||||
|
|
||||||
|
|
||||||
class Fob(ControlPoint):
|
class Fob(ControlPoint):
|
||||||
|
|
||||||
def __init__(self, name: str, at: Point, cp_id: int):
|
def __init__(self, name: str, at: Point, cp_id: int):
|
||||||
import game.theater.conflicttheater
|
import game.theater.conflicttheater
|
||||||
super().__init__(cp_id, name, at, at,
|
|
||||||
game.theater.conflicttheater.SIZE_SMALL, 1,
|
super().__init__(
|
||||||
has_frontline=True, cptype=ControlPointType.FOB)
|
cp_id,
|
||||||
|
name,
|
||||||
|
at,
|
||||||
|
at,
|
||||||
|
game.theater.conflicttheater.SIZE_SMALL,
|
||||||
|
1,
|
||||||
|
has_frontline=True,
|
||||||
|
cptype=ControlPointType.FOB,
|
||||||
|
)
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
def runway_is_operational(self) -> bool:
|
def runway_is_operational(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def active_runway(self, conditions: Conditions,
|
def active_runway(
|
||||||
dynamic_runways: Dict[str, RunwayData]) -> RunwayData:
|
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
|
||||||
|
) -> RunwayData:
|
||||||
logging.warning("TODO: FOBs have no runways.")
|
logging.warning("TODO: FOBs have no runways.")
|
||||||
return RunwayData(self.full_name, runway_heading=0, runway_name="")
|
return RunwayData(self.full_name, runway_heading=0, runway_name="")
|
||||||
|
|
||||||
@ -856,6 +929,7 @@ class Fob(ControlPoint):
|
|||||||
|
|
||||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||||
from gen.flights.flight import FlightType
|
from gen.flights.flight import FlightType
|
||||||
|
|
||||||
if self.is_friendly(for_player):
|
if self.is_friendly(for_player):
|
||||||
yield from [
|
yield from [
|
||||||
FlightType.BARCAP,
|
FlightType.BARCAP,
|
||||||
|
|||||||
@ -46,4 +46,3 @@ def poly_centroid(poly) -> Tuple[float, float]:
|
|||||||
x = sum(x_list) / len(poly)
|
x = sum(x_list) / len(poly)
|
||||||
y = sum(y_list) / len(poly)
|
y = sum(y_list) / len(poly)
|
||||||
return (x, y)
|
return (x, y)
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,7 @@ class MissionTarget:
|
|||||||
|
|
||||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||||
from gen.flights.flight import FlightType
|
from gen.flights.flight import FlightType
|
||||||
|
|
||||||
if self.is_friendly(for_player):
|
if self.is_friendly(for_player):
|
||||||
yield FlightType.BARCAP
|
yield FlightType.BARCAP
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -22,7 +22,8 @@ from game.theater.theatergroundobject import (
|
|||||||
MissileSiteGroundObject,
|
MissileSiteGroundObject,
|
||||||
SamGroundObject,
|
SamGroundObject,
|
||||||
ShipGroundObject,
|
ShipGroundObject,
|
||||||
VehicleGroupGroundObject, CoastalSiteGroundObject,
|
VehicleGroupGroundObject,
|
||||||
|
CoastalSiteGroundObject,
|
||||||
)
|
)
|
||||||
from game.version import VERSION
|
from game.version import VERSION
|
||||||
from gen import namegen
|
from gen import namegen
|
||||||
@ -77,9 +78,14 @@ class GeneratorSettings:
|
|||||||
|
|
||||||
|
|
||||||
class GameGenerator:
|
class GameGenerator:
|
||||||
def __init__(self, player: str, enemy: str, theater: ConflictTheater,
|
def __init__(
|
||||||
|
self,
|
||||||
|
player: str,
|
||||||
|
enemy: str,
|
||||||
|
theater: ConflictTheater,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
generator_settings: GeneratorSettings) -> None:
|
generator_settings: GeneratorSettings,
|
||||||
|
) -> None:
|
||||||
self.player = player
|
self.player = player
|
||||||
self.enemy = enemy
|
self.enemy = enemy
|
||||||
self.theater = theater
|
self.theater = theater
|
||||||
@ -97,7 +103,7 @@ class GameGenerator:
|
|||||||
start_date=self.generator_settings.start_date,
|
start_date=self.generator_settings.start_date,
|
||||||
settings=self.settings,
|
settings=self.settings,
|
||||||
player_budget=self.generator_settings.player_budget,
|
player_budget=self.generator_settings.player_budget,
|
||||||
enemy_budget=self.generator_settings.enemy_budget
|
enemy_budget=self.generator_settings.enemy_budget,
|
||||||
)
|
)
|
||||||
|
|
||||||
GroundObjectGenerator(game, self.generator_settings).generate()
|
GroundObjectGenerator(game, self.generator_settings).generate()
|
||||||
@ -109,7 +115,7 @@ class GameGenerator:
|
|||||||
# Auto-capture half the bases if midgame.
|
# Auto-capture half the bases if midgame.
|
||||||
if self.generator_settings.midgame:
|
if self.generator_settings.midgame:
|
||||||
control_points = self.theater.controlpoints
|
control_points = self.theater.controlpoints
|
||||||
for control_point in control_points[:len(control_points) // 2]:
|
for control_point in control_points[: len(control_points) // 2]:
|
||||||
control_point.captured = True
|
control_point.captured = True
|
||||||
|
|
||||||
# Remove carrier and lha, invert situation if needed
|
# Remove carrier and lha, invert situation if needed
|
||||||
@ -141,28 +147,37 @@ class LocationFinder:
|
|||||||
self.game = game
|
self.game = game
|
||||||
self.control_point = control_point
|
self.control_point = control_point
|
||||||
self.miz_data = MizDataLocationFinder.compute_possible_locations(
|
self.miz_data = MizDataLocationFinder.compute_possible_locations(
|
||||||
game.theater.terrain.name, control_point.full_name)
|
game.theater.terrain.name, control_point.full_name
|
||||||
|
)
|
||||||
|
|
||||||
def location_for(self, location_type: LocationType) -> Optional[PointWithHeading]:
|
def location_for(self, location_type: LocationType) -> Optional[PointWithHeading]:
|
||||||
position = self.control_point.preset_locations.random_for(location_type)
|
position = self.control_point.preset_locations.random_for(location_type)
|
||||||
if position is not None:
|
if position is not None:
|
||||||
return position
|
return position
|
||||||
|
|
||||||
logging.warning(f"No campaign location for %s Mat %s",
|
logging.warning(
|
||||||
location_type.value, self.control_point)
|
f"No campaign location for %s Mat %s",
|
||||||
|
location_type.value,
|
||||||
|
self.control_point,
|
||||||
|
)
|
||||||
position = self.random_from_miz_data(
|
position = self.random_from_miz_data(
|
||||||
location_type == LocationType.OffshoreStrikeTarget)
|
location_type == LocationType.OffshoreStrikeTarget
|
||||||
|
)
|
||||||
if position is not None:
|
if position is not None:
|
||||||
return position
|
return position
|
||||||
|
|
||||||
logging.debug(f"No mizdata location for %s at %s", location_type.value,
|
logging.debug(
|
||||||
self.control_point)
|
f"No mizdata location for %s at %s", location_type.value, self.control_point
|
||||||
|
)
|
||||||
position = self.random_position(location_type)
|
position = self.random_position(location_type)
|
||||||
if position is not None:
|
if position is not None:
|
||||||
return position
|
return position
|
||||||
|
|
||||||
logging.error(f"Could not find position for %s at %s",
|
logging.error(
|
||||||
location_type.value, self.control_point)
|
f"Could not find position for %s at %s",
|
||||||
|
location_type.value,
|
||||||
|
self.control_point,
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def random_from_miz_data(self, offshore: bool) -> Optional[PointWithHeading]:
|
def random_from_miz_data(self, offshore: bool) -> Optional[PointWithHeading]:
|
||||||
@ -176,15 +191,20 @@ class LocationFinder:
|
|||||||
return PointWithHeading.from_point(preset.position, preset.heading)
|
return PointWithHeading.from_point(preset.position, preset.heading)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def random_position(self, location_type: LocationType) -> Optional[PointWithHeading]:
|
def random_position(
|
||||||
|
self, location_type: LocationType
|
||||||
|
) -> Optional[PointWithHeading]:
|
||||||
# TODO: Flesh out preset locations so we never hit this case.
|
# TODO: Flesh out preset locations so we never hit this case.
|
||||||
|
|
||||||
if location_type == LocationType.Coastal:
|
if location_type == LocationType.Coastal:
|
||||||
# No coastal locations generated randomly
|
# No coastal locations generated randomly
|
||||||
return None
|
return None
|
||||||
|
|
||||||
logging.warning("Falling back to random location for %s at %s",
|
logging.warning(
|
||||||
location_type.value, self.control_point)
|
"Falling back to random location for %s at %s",
|
||||||
|
location_type.value,
|
||||||
|
self.control_point,
|
||||||
|
)
|
||||||
|
|
||||||
is_base_defense = location_type in {
|
is_base_defense = location_type in {
|
||||||
LocationType.BaseAirDefense,
|
LocationType.BaseAirDefense,
|
||||||
@ -218,23 +238,28 @@ class LocationFinder:
|
|||||||
min_range = 10000
|
min_range = 10000
|
||||||
max_range = 40000
|
max_range = 40000
|
||||||
|
|
||||||
position = self._find_random_position(min_range, max_range,
|
position = self._find_random_position(
|
||||||
on_land, is_base_defense,
|
min_range, max_range, on_land, is_base_defense, avoid_others
|
||||||
avoid_others)
|
)
|
||||||
|
|
||||||
# Retry once, searching a bit further (On some big airbases, 3200 is too
|
# Retry once, searching a bit further (On some big airbases, 3200 is too
|
||||||
# short (Ex : Incirlik)), but searching farther on every base would be
|
# short (Ex : Incirlik)), but searching farther on every base would be
|
||||||
# problematic, as some base defense units would end up very far away
|
# problematic, as some base defense units would end up very far away
|
||||||
# from small airfields.
|
# from small airfields.
|
||||||
if position is None and is_base_defense:
|
if position is None and is_base_defense:
|
||||||
position = self._find_random_position(3200, 4800,
|
position = self._find_random_position(
|
||||||
on_land, is_base_defense,
|
3200, 4800, on_land, is_base_defense, avoid_others
|
||||||
avoid_others)
|
)
|
||||||
return position
|
return position
|
||||||
|
|
||||||
def _find_random_position(self, min_range: int, max_range: int,
|
def _find_random_position(
|
||||||
on_ground: bool, is_base_defense: bool,
|
self,
|
||||||
avoid_others: bool) -> Optional[PointWithHeading]:
|
min_range: int,
|
||||||
|
max_range: int,
|
||||||
|
on_ground: bool,
|
||||||
|
is_base_defense: bool,
|
||||||
|
avoid_others: bool,
|
||||||
|
) -> Optional[PointWithHeading]:
|
||||||
"""
|
"""
|
||||||
Find a valid ground object location
|
Find a valid ground object location
|
||||||
:param on_ground: Whether it should be on ground or on sea (True = on
|
:param on_ground: Whether it should be on ground or on sea (True = on
|
||||||
@ -278,15 +303,21 @@ class LocationFinder:
|
|||||||
|
|
||||||
for _ in range(300):
|
for _ in range(300):
|
||||||
# Check if on land or sea
|
# Check if on land or sea
|
||||||
p = PointWithHeading.from_point(near.random_point_within(max_range, min_range), random.randint(0, 360))
|
p = PointWithHeading.from_point(
|
||||||
|
near.random_point_within(max_range, min_range), random.randint(0, 360)
|
||||||
|
)
|
||||||
if is_valid(p):
|
if is_valid(p):
|
||||||
return p
|
return p
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class ControlPointGroundObjectGenerator:
|
class ControlPointGroundObjectGenerator:
|
||||||
def __init__(self, game: Game, generator_settings: GeneratorSettings,
|
def __init__(
|
||||||
control_point: ControlPoint) -> None:
|
self,
|
||||||
|
game: Game,
|
||||||
|
generator_settings: GeneratorSettings,
|
||||||
|
control_point: ControlPoint,
|
||||||
|
) -> None:
|
||||||
self.game = game
|
self.game = game
|
||||||
self.generator_settings = generator_settings
|
self.generator_settings = generator_settings
|
||||||
self.control_point = control_point
|
self.control_point = control_point
|
||||||
@ -327,15 +358,15 @@ class ControlPointGroundObjectGenerator:
|
|||||||
self.generate_ship()
|
self.generate_ship()
|
||||||
|
|
||||||
def generate_ship(self) -> None:
|
def generate_ship(self) -> None:
|
||||||
point = self.location_finder.location_for(
|
point = self.location_finder.location_for(LocationType.OffshoreStrikeTarget)
|
||||||
LocationType.OffshoreStrikeTarget)
|
|
||||||
if point is None:
|
if point is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
group_id = self.game.next_group_id()
|
group_id = self.game.next_group_id()
|
||||||
|
|
||||||
g = ShipGroundObject(namegen.random_objective_name(), group_id, point,
|
g = ShipGroundObject(
|
||||||
self.control_point)
|
namegen.random_objective_name(), group_id, point, self.control_point
|
||||||
|
)
|
||||||
|
|
||||||
group = generate_ship_group(self.game, g, self.faction_name)
|
group = generate_ship_group(self.game, g, self.faction_name)
|
||||||
g.groups = []
|
g.groups = []
|
||||||
@ -358,13 +389,15 @@ class CarrierGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
|||||||
if not carrier_names:
|
if not carrier_names:
|
||||||
logging.info(
|
logging.info(
|
||||||
f"Skipping generation of {self.control_point.name} because "
|
f"Skipping generation of {self.control_point.name} because "
|
||||||
f"{self.faction_name} has no carriers")
|
f"{self.faction_name} has no carriers"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Create ground object group
|
# Create ground object group
|
||||||
group_id = self.game.next_group_id()
|
group_id = self.game.next_group_id()
|
||||||
g = CarrierGroundObject(namegen.random_objective_name(), group_id,
|
g = CarrierGroundObject(
|
||||||
self.control_point)
|
namegen.random_objective_name(), group_id, self.control_point
|
||||||
|
)
|
||||||
group = generate_carrier_group(self.faction_name, self.game, g)
|
group = generate_carrier_group(self.faction_name, self.game, g)
|
||||||
g.groups = []
|
g.groups = []
|
||||||
if group is not None:
|
if group is not None:
|
||||||
@ -383,13 +416,15 @@ class LhaGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
|||||||
if not lha_names:
|
if not lha_names:
|
||||||
logging.info(
|
logging.info(
|
||||||
f"Skipping generation of {self.control_point.name} because "
|
f"Skipping generation of {self.control_point.name} because "
|
||||||
f"{self.faction_name} has no LHAs")
|
f"{self.faction_name} has no LHAs"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Create ground object group
|
# Create ground object group
|
||||||
group_id = self.game.next_group_id()
|
group_id = self.game.next_group_id()
|
||||||
g = LhaGroundObject(namegen.random_objective_name(), group_id,
|
g = LhaGroundObject(
|
||||||
self.control_point)
|
namegen.random_objective_name(), group_id, self.control_point
|
||||||
|
)
|
||||||
group = generate_lha_group(self.faction_name, self.game, g)
|
group = generate_lha_group(self.faction_name, self.game, g)
|
||||||
g.groups = []
|
g.groups = []
|
||||||
if group is not None:
|
if group is not None:
|
||||||
@ -428,8 +463,9 @@ class BaseDefenseGenerator:
|
|||||||
|
|
||||||
group_id = self.game.next_group_id()
|
group_id = self.game.next_group_id()
|
||||||
|
|
||||||
g = EwrGroundObject(namegen.random_objective_name(), group_id,
|
g = EwrGroundObject(
|
||||||
position, self.control_point)
|
namegen.random_objective_name(), group_id, position, self.control_point
|
||||||
|
)
|
||||||
|
|
||||||
group = generate_ewr_group(self.game, g, self.faction)
|
group = generate_ewr_group(self.game, g, self.faction)
|
||||||
if group is None:
|
if group is None:
|
||||||
@ -460,28 +496,35 @@ class BaseDefenseGenerator:
|
|||||||
|
|
||||||
group_id = self.game.next_group_id()
|
group_id = self.game.next_group_id()
|
||||||
|
|
||||||
g = VehicleGroupGroundObject(namegen.random_objective_name(), group_id,
|
g = VehicleGroupGroundObject(
|
||||||
position, self.control_point,
|
namegen.random_objective_name(),
|
||||||
for_airbase=True)
|
group_id,
|
||||||
|
position,
|
||||||
|
self.control_point,
|
||||||
|
for_airbase=True,
|
||||||
|
)
|
||||||
|
|
||||||
group = generate_armor_group(self.faction_name, self.game, g)
|
group = generate_armor_group(self.faction_name, self.game, g)
|
||||||
if group is None:
|
if group is None:
|
||||||
logging.error(
|
logging.error(f"Could not generate garrison at {self.control_point}")
|
||||||
f"Could not generate garrison at {self.control_point}")
|
|
||||||
return
|
return
|
||||||
g.groups.append(group)
|
g.groups.append(group)
|
||||||
self.control_point.base_defenses.append(g)
|
self.control_point.base_defenses.append(g)
|
||||||
|
|
||||||
def generate_sam(self) -> None:
|
def generate_sam(self) -> None:
|
||||||
position = self.location_finder.location_for(
|
position = self.location_finder.location_for(LocationType.BaseAirDefense)
|
||||||
LocationType.BaseAirDefense)
|
|
||||||
if position is None:
|
if position is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
group_id = self.game.next_group_id()
|
group_id = self.game.next_group_id()
|
||||||
|
|
||||||
g = SamGroundObject(namegen.random_objective_name(), group_id,
|
g = SamGroundObject(
|
||||||
position, self.control_point, for_airbase=True)
|
namegen.random_objective_name(),
|
||||||
|
group_id,
|
||||||
|
position,
|
||||||
|
self.control_point,
|
||||||
|
for_airbase=True,
|
||||||
|
)
|
||||||
|
|
||||||
groups = generate_anti_air_group(self.game, g, self.faction)
|
groups = generate_anti_air_group(self.game, g, self.faction)
|
||||||
if not groups:
|
if not groups:
|
||||||
@ -491,21 +534,25 @@ class BaseDefenseGenerator:
|
|||||||
self.control_point.base_defenses.append(g)
|
self.control_point.base_defenses.append(g)
|
||||||
|
|
||||||
def generate_shorad(self) -> None:
|
def generate_shorad(self) -> None:
|
||||||
position = self.location_finder.location_for(
|
position = self.location_finder.location_for(LocationType.BaseAirDefense)
|
||||||
LocationType.BaseAirDefense)
|
|
||||||
if position is None:
|
if position is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
group_id = self.game.next_group_id()
|
group_id = self.game.next_group_id()
|
||||||
|
|
||||||
g = SamGroundObject(namegen.random_objective_name(), group_id,
|
g = SamGroundObject(
|
||||||
position, self.control_point, for_airbase=True)
|
namegen.random_objective_name(),
|
||||||
|
group_id,
|
||||||
|
position,
|
||||||
|
self.control_point,
|
||||||
|
for_airbase=True,
|
||||||
|
)
|
||||||
|
|
||||||
groups = generate_anti_air_group(self.game, g, self.faction,
|
groups = generate_anti_air_group(
|
||||||
ranges=[{AirDefenseRange.Short}])
|
self.game, g, self.faction, ranges=[{AirDefenseRange.Short}]
|
||||||
|
)
|
||||||
if not groups:
|
if not groups:
|
||||||
logging.error(
|
logging.error(f"Could not generate SHORAD group at {self.control_point}")
|
||||||
f"Could not generate SHORAD group at {self.control_point}")
|
|
||||||
return
|
return
|
||||||
g.groups = groups
|
g.groups = groups
|
||||||
self.control_point.base_defenses.append(g)
|
self.control_point.base_defenses.append(g)
|
||||||
@ -534,9 +581,13 @@ class FobDefenseGenerator(BaseDefenseGenerator):
|
|||||||
|
|
||||||
|
|
||||||
class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||||
def __init__(self, game: Game, generator_settings: GeneratorSettings,
|
def __init__(
|
||||||
|
self,
|
||||||
|
game: Game,
|
||||||
|
generator_settings: GeneratorSettings,
|
||||||
control_point: ControlPoint,
|
control_point: ControlPoint,
|
||||||
templates: GroundObjectTemplates) -> None:
|
templates: GroundObjectTemplates,
|
||||||
|
) -> None:
|
||||||
super().__init__(game, generator_settings, control_point)
|
super().__init__(game, generator_settings, control_point)
|
||||||
self.templates = templates
|
self.templates = templates
|
||||||
|
|
||||||
@ -585,18 +636,25 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
|||||||
"""
|
"""
|
||||||
presets = self.control_point.preset_locations
|
presets = self.control_point.preset_locations
|
||||||
for position in presets.required_long_range_sams:
|
for position in presets.required_long_range_sams:
|
||||||
self.generate_aa_at(position, ranges=[
|
self.generate_aa_at(
|
||||||
|
position,
|
||||||
|
ranges=[
|
||||||
{AirDefenseRange.Long},
|
{AirDefenseRange.Long},
|
||||||
{AirDefenseRange.Medium},
|
{AirDefenseRange.Medium},
|
||||||
{AirDefenseRange.Short},
|
{AirDefenseRange.Short},
|
||||||
])
|
],
|
||||||
|
)
|
||||||
for position in presets.required_medium_range_sams:
|
for position in presets.required_medium_range_sams:
|
||||||
self.generate_aa_at(position, ranges=[
|
self.generate_aa_at(
|
||||||
|
position,
|
||||||
|
ranges=[
|
||||||
{AirDefenseRange.Medium},
|
{AirDefenseRange.Medium},
|
||||||
{AirDefenseRange.Short},
|
{AirDefenseRange.Short},
|
||||||
])
|
],
|
||||||
return (len(presets.required_long_range_sams) +
|
)
|
||||||
len(presets.required_medium_range_sams))
|
return len(presets.required_long_range_sams) + len(
|
||||||
|
presets.required_medium_range_sams
|
||||||
|
)
|
||||||
|
|
||||||
def generate_ground_point(self) -> None:
|
def generate_ground_point(self) -> None:
|
||||||
try:
|
try:
|
||||||
@ -627,8 +685,15 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
|||||||
|
|
||||||
template_point = Point(unit["offset"].x, unit["offset"].y)
|
template_point = Point(unit["offset"].x, unit["offset"].y)
|
||||||
g = BuildingGroundObject(
|
g = BuildingGroundObject(
|
||||||
obj_name, category, group_id, object_id, point + template_point,
|
obj_name,
|
||||||
unit["heading"], self.control_point, unit["type"])
|
category,
|
||||||
|
group_id,
|
||||||
|
object_id,
|
||||||
|
point + template_point,
|
||||||
|
unit["heading"],
|
||||||
|
self.control_point,
|
||||||
|
unit["type"],
|
||||||
|
)
|
||||||
|
|
||||||
self.control_point.connected_objectives.append(g)
|
self.control_point.connected_objectives.append(g)
|
||||||
|
|
||||||
@ -636,23 +701,34 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
|||||||
position = self.location_finder.location_for(LocationType.Sam)
|
position = self.location_finder.location_for(LocationType.Sam)
|
||||||
if position is None:
|
if position is None:
|
||||||
return
|
return
|
||||||
self.generate_aa_at(position, ranges=[
|
self.generate_aa_at(
|
||||||
|
position,
|
||||||
|
ranges=[
|
||||||
# Prefer to use proper SAMs, but fall back to SHORADs if needed.
|
# Prefer to use proper SAMs, but fall back to SHORADs if needed.
|
||||||
{AirDefenseRange.Long, AirDefenseRange.Medium},
|
{AirDefenseRange.Long, AirDefenseRange.Medium},
|
||||||
{AirDefenseRange.Short},
|
{AirDefenseRange.Short},
|
||||||
])
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def generate_aa_at(
|
def generate_aa_at(
|
||||||
self, position: Point,
|
self, position: Point, ranges: Iterable[Set[AirDefenseRange]]
|
||||||
ranges: Iterable[Set[AirDefenseRange]]) -> None:
|
) -> None:
|
||||||
group_id = self.game.next_group_id()
|
group_id = self.game.next_group_id()
|
||||||
|
|
||||||
g = SamGroundObject(namegen.random_objective_name(), group_id,
|
g = SamGroundObject(
|
||||||
position, self.control_point, for_airbase=False)
|
namegen.random_objective_name(),
|
||||||
|
group_id,
|
||||||
|
position,
|
||||||
|
self.control_point,
|
||||||
|
for_airbase=False,
|
||||||
|
)
|
||||||
groups = generate_anti_air_group(self.game, g, self.faction, ranges)
|
groups = generate_anti_air_group(self.game, g, self.faction, ranges)
|
||||||
if not groups:
|
if not groups:
|
||||||
logging.error("Could not generate air defense group for %s at %s",
|
logging.error(
|
||||||
g.name, self.control_point)
|
"Could not generate air defense group for %s at %s",
|
||||||
|
g.name,
|
||||||
|
self.control_point,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
g.groups = groups
|
g.groups = groups
|
||||||
self.control_point.connected_objectives.append(g)
|
self.control_point.connected_objectives.append(g)
|
||||||
@ -668,8 +744,9 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
|||||||
|
|
||||||
group_id = self.game.next_group_id()
|
group_id = self.game.next_group_id()
|
||||||
|
|
||||||
g = MissileSiteGroundObject(namegen.random_objective_name(), group_id,
|
g = MissileSiteGroundObject(
|
||||||
position, self.control_point)
|
namegen.random_objective_name(), group_id, position, self.control_point
|
||||||
|
)
|
||||||
group = generate_missile_group(self.game, g, self.faction_name)
|
group = generate_missile_group(self.game, g, self.faction_name)
|
||||||
g.groups = []
|
g.groups = []
|
||||||
if group is not None:
|
if group is not None:
|
||||||
@ -688,8 +765,13 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
|||||||
|
|
||||||
group_id = self.game.next_group_id()
|
group_id = self.game.next_group_id()
|
||||||
|
|
||||||
g = CoastalSiteGroundObject(namegen.random_objective_name(), group_id,
|
g = CoastalSiteGroundObject(
|
||||||
position, self.control_point, position.heading)
|
namegen.random_objective_name(),
|
||||||
|
group_id,
|
||||||
|
position,
|
||||||
|
self.control_point,
|
||||||
|
position.heading,
|
||||||
|
)
|
||||||
group = generate_coastal_group(self.game, g, self.faction_name)
|
group = generate_coastal_group(self.game, g, self.faction_name)
|
||||||
g.groups = []
|
g.groups = []
|
||||||
if group is not None:
|
if group is not None:
|
||||||
@ -707,7 +789,7 @@ class FobGroundObjectGenerator(AirbaseGroundObjectGenerator):
|
|||||||
|
|
||||||
def generate_fob(self) -> None:
|
def generate_fob(self) -> None:
|
||||||
try:
|
try:
|
||||||
category = self.faction.building_set[self.faction.building_set.index('fob')]
|
category = self.faction.building_set[self.faction.building_set.index("fob")]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
logging.exception("Faction has no fob buildings defined")
|
logging.exception("Faction has no fob buildings defined")
|
||||||
return
|
return
|
||||||
@ -725,14 +807,21 @@ class FobGroundObjectGenerator(AirbaseGroundObjectGenerator):
|
|||||||
|
|
||||||
template_point = Point(unit["offset"].x, unit["offset"].y)
|
template_point = Point(unit["offset"].x, unit["offset"].y)
|
||||||
g = BuildingGroundObject(
|
g = BuildingGroundObject(
|
||||||
obj_name, category, group_id, object_id, point + template_point,
|
obj_name,
|
||||||
unit["heading"], self.control_point, unit["type"], airbase_group=True)
|
category,
|
||||||
|
group_id,
|
||||||
|
object_id,
|
||||||
|
point + template_point,
|
||||||
|
unit["heading"],
|
||||||
|
self.control_point,
|
||||||
|
unit["type"],
|
||||||
|
airbase_group=True,
|
||||||
|
)
|
||||||
self.control_point.connected_objectives.append(g)
|
self.control_point.connected_objectives.append(g)
|
||||||
|
|
||||||
|
|
||||||
class GroundObjectGenerator:
|
class GroundObjectGenerator:
|
||||||
def __init__(self, game: Game,
|
def __init__(self, game: Game, generator_settings: GeneratorSettings) -> None:
|
||||||
generator_settings: GeneratorSettings) -> None:
|
|
||||||
self.game = game
|
self.game = game
|
||||||
self.generator_settings = generator_settings
|
self.generator_settings = generator_settings
|
||||||
with open("resources/groundobject_templates.p", "rb") as f:
|
with open("resources/groundobject_templates.p", "rb") as f:
|
||||||
@ -750,19 +839,22 @@ class GroundObjectGenerator:
|
|||||||
generator: ControlPointGroundObjectGenerator
|
generator: ControlPointGroundObjectGenerator
|
||||||
if control_point.cptype == ControlPointType.AIRCRAFT_CARRIER_GROUP:
|
if control_point.cptype == ControlPointType.AIRCRAFT_CARRIER_GROUP:
|
||||||
generator = CarrierGroundObjectGenerator(
|
generator = CarrierGroundObjectGenerator(
|
||||||
self.game, self.generator_settings, control_point)
|
self.game, self.generator_settings, control_point
|
||||||
|
)
|
||||||
elif control_point.cptype == ControlPointType.LHA_GROUP:
|
elif control_point.cptype == ControlPointType.LHA_GROUP:
|
||||||
generator = LhaGroundObjectGenerator(
|
generator = LhaGroundObjectGenerator(
|
||||||
self.game, self.generator_settings, control_point)
|
self.game, self.generator_settings, control_point
|
||||||
|
)
|
||||||
elif isinstance(control_point, OffMapSpawn):
|
elif isinstance(control_point, OffMapSpawn):
|
||||||
generator = NoOpGroundObjectGenerator(
|
generator = NoOpGroundObjectGenerator(
|
||||||
self.game, self.generator_settings, control_point)
|
self.game, self.generator_settings, control_point
|
||||||
|
)
|
||||||
elif isinstance(control_point, Fob):
|
elif isinstance(control_point, Fob):
|
||||||
generator = FobGroundObjectGenerator(
|
generator = FobGroundObjectGenerator(
|
||||||
self.game, self.generator_settings, control_point,
|
self.game, self.generator_settings, control_point, self.templates
|
||||||
self.templates)
|
)
|
||||||
else:
|
else:
|
||||||
generator = AirbaseGroundObjectGenerator(
|
generator = AirbaseGroundObjectGenerator(
|
||||||
self.game, self.generator_settings, control_point,
|
self.game, self.generator_settings, control_point, self.templates
|
||||||
self.templates)
|
)
|
||||||
return generator.generate()
|
return generator.generate()
|
||||||
|
|||||||
@ -33,7 +33,7 @@ NAME_BY_CATEGORY = {
|
|||||||
"ww2bunker": "Bunker",
|
"ww2bunker": "Bunker",
|
||||||
"village": "Village",
|
"village": "Village",
|
||||||
"allycamp": "Camp",
|
"allycamp": "Camp",
|
||||||
"EWR":"EWR",
|
"EWR": "EWR",
|
||||||
}
|
}
|
||||||
|
|
||||||
ABBREV_NAME = {
|
ABBREV_NAME = {
|
||||||
@ -54,34 +54,59 @@ ABBREV_NAME = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CATEGORY_MAP = {
|
CATEGORY_MAP = {
|
||||||
|
|
||||||
# Special cases
|
# Special cases
|
||||||
"CARRIER": ["CARRIER"],
|
"CARRIER": ["CARRIER"],
|
||||||
"LHA": ["LHA"],
|
"LHA": ["LHA"],
|
||||||
"aa": ["AA"],
|
"aa": ["AA"],
|
||||||
|
|
||||||
# Buildings
|
# Buildings
|
||||||
"power": ["Workshop A", "Electric power box", "Garage small A", "Farm B", "Repair workshop", "Garage B"],
|
"power": [
|
||||||
|
"Workshop A",
|
||||||
|
"Electric power box",
|
||||||
|
"Garage small A",
|
||||||
|
"Farm B",
|
||||||
|
"Repair workshop",
|
||||||
|
"Garage B",
|
||||||
|
],
|
||||||
"ware": ["Warehouse", "Hangar A"],
|
"ware": ["Warehouse", "Hangar A"],
|
||||||
"fuel": ["Tank", "Tank 2", "Tank 3", "Fuel tank"],
|
"fuel": ["Tank", "Tank 2", "Tank 3", "Fuel tank"],
|
||||||
"ammo": [".Ammunition depot", "Hangar B"],
|
"ammo": [".Ammunition depot", "Hangar B"],
|
||||||
"farp": ["FARP Tent", "FARP Ammo Dump Coating", "FARP Fuel Depot", "FARP Command Post", "FARP CP Blindage"],
|
"farp": [
|
||||||
|
"FARP Tent",
|
||||||
|
"FARP Ammo Dump Coating",
|
||||||
|
"FARP Fuel Depot",
|
||||||
|
"FARP Command Post",
|
||||||
|
"FARP CP Blindage",
|
||||||
|
],
|
||||||
"fob": ["Bunker 2", "Bunker 1", "Garage small B", ".Command Center", "Barracks 2"],
|
"fob": ["Bunker 2", "Bunker 1", "Garage small B", ".Command Center", "Barracks 2"],
|
||||||
"factory": ["Tech combine", "Tech hangar A"],
|
"factory": ["Tech combine", "Tech hangar A"],
|
||||||
"comms": ["TV tower", "Comms tower M"],
|
"comms": ["TV tower", "Comms tower M"],
|
||||||
"oil": ["Oil platform"],
|
"oil": ["Oil platform"],
|
||||||
"derrick": ["Oil derrick", "Pump station", "Subsidiary structure 2"],
|
"derrick": ["Oil derrick", "Pump station", "Subsidiary structure 2"],
|
||||||
"ww2bunker": ["Siegfried Line", "Fire Control Bunker", "SK_C_28_naval_gun", "Concertina Wire", "Czech hedgehogs 1"],
|
"ww2bunker": [
|
||||||
|
"Siegfried Line",
|
||||||
|
"Fire Control Bunker",
|
||||||
|
"SK_C_28_naval_gun",
|
||||||
|
"Concertina Wire",
|
||||||
|
"Czech hedgehogs 1",
|
||||||
|
],
|
||||||
"village": ["Small house 1B", "Small House 1A", "Small warehouse 1"],
|
"village": ["Small house 1B", "Small House 1A", "Small warehouse 1"],
|
||||||
"allycamp": [],
|
"allycamp": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TheaterGroundObject(MissionTarget):
|
class TheaterGroundObject(MissionTarget):
|
||||||
|
def __init__(
|
||||||
def __init__(self, name: str, category: str, group_id: int, position: Point,
|
self,
|
||||||
heading: int, control_point: ControlPoint, dcs_identifier: str,
|
name: str,
|
||||||
airbase_group: bool, sea_object: bool) -> None:
|
category: str,
|
||||||
|
group_id: int,
|
||||||
|
position: Point,
|
||||||
|
heading: int,
|
||||||
|
control_point: ControlPoint,
|
||||||
|
dcs_identifier: str,
|
||||||
|
airbase_group: bool,
|
||||||
|
sea_object: bool,
|
||||||
|
) -> None:
|
||||||
super().__init__(name, position)
|
super().__init__(name, position)
|
||||||
self.category = category
|
self.category = category
|
||||||
self.group_id = group_id
|
self.group_id = group_id
|
||||||
@ -131,6 +156,7 @@ class TheaterGroundObject(MissionTarget):
|
|||||||
|
|
||||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||||
from gen.flights.flight import FlightType
|
from gen.flights.flight import FlightType
|
||||||
|
|
||||||
if self.is_friendly(for_player):
|
if self.is_friendly(for_player):
|
||||||
yield from [
|
yield from [
|
||||||
# TODO: FlightType.LOGISTICS
|
# TODO: FlightType.LOGISTICS
|
||||||
@ -193,9 +219,18 @@ class TheaterGroundObject(MissionTarget):
|
|||||||
|
|
||||||
|
|
||||||
class BuildingGroundObject(TheaterGroundObject):
|
class BuildingGroundObject(TheaterGroundObject):
|
||||||
def __init__(self, name: str, category: str, group_id: int, object_id: int,
|
def __init__(
|
||||||
position: Point, heading: int, control_point: ControlPoint,
|
self,
|
||||||
dcs_identifier: str, airbase_group=False) -> None:
|
name: str,
|
||||||
|
category: str,
|
||||||
|
group_id: int,
|
||||||
|
object_id: int,
|
||||||
|
position: Point,
|
||||||
|
heading: int,
|
||||||
|
control_point: ControlPoint,
|
||||||
|
dcs_identifier: str,
|
||||||
|
airbase_group=False,
|
||||||
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=name,
|
name=name,
|
||||||
category=category,
|
category=category,
|
||||||
@ -205,7 +240,7 @@ class BuildingGroundObject(TheaterGroundObject):
|
|||||||
control_point=control_point,
|
control_point=control_point,
|
||||||
dcs_identifier=dcs_identifier,
|
dcs_identifier=dcs_identifier,
|
||||||
airbase_group=airbase_group,
|
airbase_group=airbase_group,
|
||||||
sea_object=False
|
sea_object=False,
|
||||||
)
|
)
|
||||||
self.object_id = object_id
|
self.object_id = object_id
|
||||||
# Other TGOs track deadness based on the number of alive units, but
|
# Other TGOs track deadness based on the number of alive units, but
|
||||||
@ -234,6 +269,7 @@ class BuildingGroundObject(TheaterGroundObject):
|
|||||||
class NavalGroundObject(TheaterGroundObject):
|
class NavalGroundObject(TheaterGroundObject):
|
||||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||||
from gen.flights.flight import FlightType
|
from gen.flights.flight import FlightType
|
||||||
|
|
||||||
if not self.is_friendly(for_player):
|
if not self.is_friendly(for_player):
|
||||||
yield FlightType.ANTISHIP
|
yield FlightType.ANTISHIP
|
||||||
yield from super().mission_types(for_player)
|
yield from super().mission_types(for_player)
|
||||||
@ -249,8 +285,7 @@ class GenericCarrierGroundObject(NavalGroundObject):
|
|||||||
|
|
||||||
# TODO: Why is this both a CP and a TGO?
|
# TODO: Why is this both a CP and a TGO?
|
||||||
class CarrierGroundObject(GenericCarrierGroundObject):
|
class CarrierGroundObject(GenericCarrierGroundObject):
|
||||||
def __init__(self, name: str, group_id: int,
|
def __init__(self, name: str, group_id: int, control_point: ControlPoint) -> None:
|
||||||
control_point: ControlPoint) -> None:
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=name,
|
name=name,
|
||||||
category="CARRIER",
|
category="CARRIER",
|
||||||
@ -260,7 +295,7 @@ class CarrierGroundObject(GenericCarrierGroundObject):
|
|||||||
control_point=control_point,
|
control_point=control_point,
|
||||||
dcs_identifier="CARRIER",
|
dcs_identifier="CARRIER",
|
||||||
airbase_group=True,
|
airbase_group=True,
|
||||||
sea_object=True
|
sea_object=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -272,8 +307,7 @@ class CarrierGroundObject(GenericCarrierGroundObject):
|
|||||||
|
|
||||||
# TODO: Why is this both a CP and a TGO?
|
# TODO: Why is this both a CP and a TGO?
|
||||||
class LhaGroundObject(GenericCarrierGroundObject):
|
class LhaGroundObject(GenericCarrierGroundObject):
|
||||||
def __init__(self, name: str, group_id: int,
|
def __init__(self, name: str, group_id: int, control_point: ControlPoint) -> None:
|
||||||
control_point: ControlPoint) -> None:
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=name,
|
name=name,
|
||||||
category="LHA",
|
category="LHA",
|
||||||
@ -283,7 +317,7 @@ class LhaGroundObject(GenericCarrierGroundObject):
|
|||||||
control_point=control_point,
|
control_point=control_point,
|
||||||
dcs_identifier="LHA",
|
dcs_identifier="LHA",
|
||||||
airbase_group=True,
|
airbase_group=True,
|
||||||
sea_object=True
|
sea_object=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -294,8 +328,9 @@ class LhaGroundObject(GenericCarrierGroundObject):
|
|||||||
|
|
||||||
|
|
||||||
class MissileSiteGroundObject(TheaterGroundObject):
|
class MissileSiteGroundObject(TheaterGroundObject):
|
||||||
def __init__(self, name: str, group_id: int, position: Point,
|
def __init__(
|
||||||
control_point: ControlPoint) -> None:
|
self, name: str, group_id: int, position: Point, control_point: ControlPoint
|
||||||
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=name,
|
name=name,
|
||||||
category="aa",
|
category="aa",
|
||||||
@ -305,13 +340,19 @@ class MissileSiteGroundObject(TheaterGroundObject):
|
|||||||
control_point=control_point,
|
control_point=control_point,
|
||||||
dcs_identifier="AA",
|
dcs_identifier="AA",
|
||||||
airbase_group=False,
|
airbase_group=False,
|
||||||
sea_object=False
|
sea_object=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CoastalSiteGroundObject(TheaterGroundObject):
|
class CoastalSiteGroundObject(TheaterGroundObject):
|
||||||
def __init__(self, name: str, group_id: int, position: Point,
|
def __init__(
|
||||||
control_point: ControlPoint, heading) -> None:
|
self,
|
||||||
|
name: str,
|
||||||
|
group_id: int,
|
||||||
|
position: Point,
|
||||||
|
control_point: ControlPoint,
|
||||||
|
heading,
|
||||||
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=name,
|
name=name,
|
||||||
category="aa",
|
category="aa",
|
||||||
@ -321,11 +362,10 @@ class CoastalSiteGroundObject(TheaterGroundObject):
|
|||||||
control_point=control_point,
|
control_point=control_point,
|
||||||
dcs_identifier="AA",
|
dcs_identifier="AA",
|
||||||
airbase_group=False,
|
airbase_group=False,
|
||||||
sea_object=False
|
sea_object=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BaseDefenseGroundObject(TheaterGroundObject):
|
class BaseDefenseGroundObject(TheaterGroundObject):
|
||||||
"""Base type for all base defenses."""
|
"""Base type for all base defenses."""
|
||||||
|
|
||||||
@ -334,8 +374,14 @@ class BaseDefenseGroundObject(TheaterGroundObject):
|
|||||||
# This type gets used both for AA sites (SAM, AAA, or SHORAD). These should each
|
# This type gets used both for AA sites (SAM, AAA, or SHORAD). These should each
|
||||||
# be split into their own types.
|
# be split into their own types.
|
||||||
class SamGroundObject(BaseDefenseGroundObject):
|
class SamGroundObject(BaseDefenseGroundObject):
|
||||||
def __init__(self, name: str, group_id: int, position: Point,
|
def __init__(
|
||||||
control_point: ControlPoint, for_airbase: bool) -> None:
|
self,
|
||||||
|
name: str,
|
||||||
|
group_id: int,
|
||||||
|
position: Point,
|
||||||
|
control_point: ControlPoint,
|
||||||
|
for_airbase: bool,
|
||||||
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=name,
|
name=name,
|
||||||
category="aa",
|
category="aa",
|
||||||
@ -345,7 +391,7 @@ class SamGroundObject(BaseDefenseGroundObject):
|
|||||||
control_point=control_point,
|
control_point=control_point,
|
||||||
dcs_identifier="AA",
|
dcs_identifier="AA",
|
||||||
airbase_group=for_airbase,
|
airbase_group=for_airbase,
|
||||||
sea_object=False
|
sea_object=False,
|
||||||
)
|
)
|
||||||
# Set by the SAM unit generator if the generated group is compatible
|
# Set by the SAM unit generator if the generated group is compatible
|
||||||
# with Skynet.
|
# with Skynet.
|
||||||
@ -362,6 +408,7 @@ class SamGroundObject(BaseDefenseGroundObject):
|
|||||||
|
|
||||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||||
from gen.flights.flight import FlightType
|
from gen.flights.flight import FlightType
|
||||||
|
|
||||||
if not self.is_friendly(for_player):
|
if not self.is_friendly(for_player):
|
||||||
yield FlightType.DEAD
|
yield FlightType.DEAD
|
||||||
yield from super().mission_types(for_player)
|
yield from super().mission_types(for_player)
|
||||||
@ -372,8 +419,14 @@ class SamGroundObject(BaseDefenseGroundObject):
|
|||||||
|
|
||||||
|
|
||||||
class VehicleGroupGroundObject(BaseDefenseGroundObject):
|
class VehicleGroupGroundObject(BaseDefenseGroundObject):
|
||||||
def __init__(self, name: str, group_id: int, position: Point,
|
def __init__(
|
||||||
control_point: ControlPoint, for_airbase: bool) -> None:
|
self,
|
||||||
|
name: str,
|
||||||
|
group_id: int,
|
||||||
|
position: Point,
|
||||||
|
control_point: ControlPoint,
|
||||||
|
for_airbase: bool,
|
||||||
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=name,
|
name=name,
|
||||||
category="aa",
|
category="aa",
|
||||||
@ -383,13 +436,14 @@ class VehicleGroupGroundObject(BaseDefenseGroundObject):
|
|||||||
control_point=control_point,
|
control_point=control_point,
|
||||||
dcs_identifier="AA",
|
dcs_identifier="AA",
|
||||||
airbase_group=for_airbase,
|
airbase_group=for_airbase,
|
||||||
sea_object=False
|
sea_object=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class EwrGroundObject(BaseDefenseGroundObject):
|
class EwrGroundObject(BaseDefenseGroundObject):
|
||||||
def __init__(self, name: str, group_id: int, position: Point,
|
def __init__(
|
||||||
control_point: ControlPoint) -> None:
|
self, name: str, group_id: int, position: Point, control_point: ControlPoint
|
||||||
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=name,
|
name=name,
|
||||||
category="EWR",
|
category="EWR",
|
||||||
@ -399,7 +453,7 @@ class EwrGroundObject(BaseDefenseGroundObject):
|
|||||||
control_point=control_point,
|
control_point=control_point,
|
||||||
dcs_identifier="EWR",
|
dcs_identifier="EWR",
|
||||||
airbase_group=True,
|
airbase_group=True,
|
||||||
sea_object=False
|
sea_object=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -409,6 +463,7 @@ class EwrGroundObject(BaseDefenseGroundObject):
|
|||||||
|
|
||||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||||
from gen.flights.flight import FlightType
|
from gen.flights.flight import FlightType
|
||||||
|
|
||||||
if not self.is_friendly(for_player):
|
if not self.is_friendly(for_player):
|
||||||
yield FlightType.DEAD
|
yield FlightType.DEAD
|
||||||
yield from super().mission_types(for_player)
|
yield from super().mission_types(for_player)
|
||||||
@ -419,8 +474,9 @@ class EwrGroundObject(BaseDefenseGroundObject):
|
|||||||
|
|
||||||
|
|
||||||
class ShipGroundObject(NavalGroundObject):
|
class ShipGroundObject(NavalGroundObject):
|
||||||
def __init__(self, name: str, group_id: int, position: Point,
|
def __init__(
|
||||||
control_point: ControlPoint) -> None:
|
self, name: str, group_id: int, position: Point, control_point: ControlPoint
|
||||||
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=name,
|
name=name,
|
||||||
category="aa",
|
category="aa",
|
||||||
@ -430,7 +486,7 @@ class ShipGroundObject(NavalGroundObject):
|
|||||||
control_point=control_point,
|
control_point=control_point,
|
||||||
dcs_identifier="AA",
|
dcs_identifier="AA",
|
||||||
airbase_group=False,
|
airbase_group=False,
|
||||||
sea_object=True
|
sea_object=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@ -32,8 +32,9 @@ class ThreatZones:
|
|||||||
self.all = unary_union([airbases, air_defenses])
|
self.all = unary_union([airbases, air_defenses])
|
||||||
|
|
||||||
def closest_boundary(self, point: DcsPoint) -> DcsPoint:
|
def closest_boundary(self, point: DcsPoint) -> DcsPoint:
|
||||||
boundary, _ = nearest_points(self.all.boundary,
|
boundary, _ = nearest_points(
|
||||||
self.dcs_to_shapely_point(point))
|
self.all.boundary, self.dcs_to_shapely_point(point)
|
||||||
|
)
|
||||||
return DcsPoint(boundary.x, boundary.y)
|
return DcsPoint(boundary.x, boundary.y)
|
||||||
|
|
||||||
@singledispatchmethod
|
@singledispatchmethod
|
||||||
@ -49,8 +50,9 @@ class ThreatZones:
|
|||||||
return self.all.intersects(self.dcs_to_shapely_point(position))
|
return self.all.intersects(self.dcs_to_shapely_point(position))
|
||||||
|
|
||||||
def path_threatened(self, a: DcsPoint, b: DcsPoint) -> bool:
|
def path_threatened(self, a: DcsPoint, b: DcsPoint) -> bool:
|
||||||
return self.threatened(LineString(
|
return self.threatened(
|
||||||
[self.dcs_to_shapely_point(a), self.dcs_to_shapely_point(b)]))
|
LineString([self.dcs_to_shapely_point(a), self.dcs_to_shapely_point(b)])
|
||||||
|
)
|
||||||
|
|
||||||
@singledispatchmethod
|
@singledispatchmethod
|
||||||
def threatened_by_aircraft(self, target) -> bool:
|
def threatened_by_aircraft(self, target) -> bool:
|
||||||
@ -62,9 +64,9 @@ class ThreatZones:
|
|||||||
|
|
||||||
@threatened_by_aircraft.register
|
@threatened_by_aircraft.register
|
||||||
def _threatened_by_aircraft_flight(self, flight: Flight) -> bool:
|
def _threatened_by_aircraft_flight(self, flight: Flight) -> bool:
|
||||||
return self.threatened_by_aircraft(LineString((
|
return self.threatened_by_aircraft(
|
||||||
self.dcs_to_shapely_point(p.position) for p in flight.points
|
LineString((self.dcs_to_shapely_point(p.position) for p in flight.points))
|
||||||
)))
|
)
|
||||||
|
|
||||||
@singledispatchmethod
|
@singledispatchmethod
|
||||||
def threatened_by_air_defense(self, target) -> bool:
|
def threatened_by_air_defense(self, target) -> bool:
|
||||||
@ -76,13 +78,14 @@ class ThreatZones:
|
|||||||
|
|
||||||
@threatened_by_air_defense.register
|
@threatened_by_air_defense.register
|
||||||
def _threatened_by_air_defense_flight(self, flight: Flight) -> bool:
|
def _threatened_by_air_defense_flight(self, flight: Flight) -> bool:
|
||||||
return self.threatened_by_air_defense(LineString((
|
return self.threatened_by_air_defense(
|
||||||
self.dcs_to_shapely_point(p.position) for p in flight.points
|
LineString((self.dcs_to_shapely_point(p.position) for p in flight.points))
|
||||||
)))
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def closest_enemy_airbase(cls, location: ControlPoint,
|
def closest_enemy_airbase(
|
||||||
max_distance: Distance) -> Optional[ControlPoint]:
|
cls, location: ControlPoint, max_distance: Distance
|
||||||
|
) -> Optional[ControlPoint]:
|
||||||
airfields = ObjectiveDistanceCache.get_closest_airfields(location)
|
airfields = ObjectiveDistanceCache.get_closest_airfields(location)
|
||||||
for airfield in airfields.airfields_within(max_distance):
|
for airfield in airfields.airfields_within(max_distance):
|
||||||
if airfield.captured != location.captured:
|
if airfield.captured != location.captured:
|
||||||
@ -90,13 +93,14 @@ class ThreatZones:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def barcap_threat_range(cls, game: Game,
|
def barcap_threat_range(cls, game: Game, control_point: ControlPoint) -> Distance:
|
||||||
control_point: ControlPoint) -> Distance:
|
|
||||||
doctrine = game.faction_for(control_point.captured).doctrine
|
doctrine = game.faction_for(control_point.captured).doctrine
|
||||||
cap_threat_range = (doctrine.cap_max_distance_from_cp +
|
cap_threat_range = (
|
||||||
doctrine.cap_engagement_range)
|
doctrine.cap_max_distance_from_cp + doctrine.cap_engagement_range
|
||||||
opposing_airfield = cls.closest_enemy_airbase(control_point,
|
)
|
||||||
cap_threat_range * 2)
|
opposing_airfield = cls.closest_enemy_airbase(
|
||||||
|
control_point, cap_threat_range * 2
|
||||||
|
)
|
||||||
if opposing_airfield is None:
|
if opposing_airfield is None:
|
||||||
return cap_threat_range
|
return cap_threat_range
|
||||||
|
|
||||||
@ -133,8 +137,7 @@ class ThreatZones:
|
|||||||
if control_point.captured != player:
|
if control_point.captured != player:
|
||||||
continue
|
continue
|
||||||
if control_point.runway_is_operational():
|
if control_point.runway_is_operational():
|
||||||
point = ShapelyPoint(control_point.position.x,
|
point = ShapelyPoint(control_point.position.x, control_point.position.y)
|
||||||
control_point.position.y)
|
|
||||||
cap_threat_range = cls.barcap_threat_range(game, control_point)
|
cap_threat_range = cls.barcap_threat_range(game, control_point)
|
||||||
airbases.append(point.buffer(cap_threat_range.meters))
|
airbases.append(point.buffer(cap_threat_range.meters))
|
||||||
|
|
||||||
@ -149,8 +152,7 @@ class ThreatZones:
|
|||||||
air_defenses.append(threat_zone)
|
air_defenses.append(threat_zone)
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
airbases=unary_union(airbases),
|
airbases=unary_union(airbases), air_defenses=unary_union(air_defenses)
|
||||||
air_defenses=unary_union(air_defenses)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@ -70,15 +70,19 @@ class UnitMap:
|
|||||||
raise RuntimeError(f"Unknown unit type: {unit.type}")
|
raise RuntimeError(f"Unknown unit type: {unit.type}")
|
||||||
if not issubclass(unit_type, VehicleType):
|
if not issubclass(unit_type, VehicleType):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"{name} is a {unit_type.__name__}, expected a VehicleType")
|
f"{name} is a {unit_type.__name__}, expected a VehicleType"
|
||||||
|
)
|
||||||
self.front_line_units[name] = FrontLineUnit(unit_type, origin)
|
self.front_line_units[name] = FrontLineUnit(unit_type, origin)
|
||||||
|
|
||||||
def front_line_unit(self, name: str) -> Optional[FrontLineUnit]:
|
def front_line_unit(self, name: str) -> Optional[FrontLineUnit]:
|
||||||
return self.front_line_units.get(name, None)
|
return self.front_line_units.get(name, None)
|
||||||
|
|
||||||
def add_ground_object_units(self, ground_object: TheaterGroundObject,
|
def add_ground_object_units(
|
||||||
|
self,
|
||||||
|
ground_object: TheaterGroundObject,
|
||||||
persistence_group: Group,
|
persistence_group: Group,
|
||||||
miz_group: Group) -> None:
|
miz_group: Group,
|
||||||
|
) -> None:
|
||||||
"""Adds a group associated with a TGO to the unit map.
|
"""Adds a group associated with a TGO to the unit map.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -103,13 +107,13 @@ class UnitMap:
|
|||||||
if name in self.ground_object_units:
|
if name in self.ground_object_units:
|
||||||
raise RuntimeError(f"Duplicate TGO unit: {name}")
|
raise RuntimeError(f"Duplicate TGO unit: {name}")
|
||||||
self.ground_object_units[name] = GroundObjectUnit(
|
self.ground_object_units[name] = GroundObjectUnit(
|
||||||
ground_object, persistence_group, persistent_unit)
|
ground_object, persistence_group, persistent_unit
|
||||||
|
)
|
||||||
|
|
||||||
def ground_object_unit(self, name: str) -> Optional[GroundObjectUnit]:
|
def ground_object_unit(self, name: str) -> Optional[GroundObjectUnit]:
|
||||||
return self.ground_object_units.get(name, None)
|
return self.ground_object_units.get(name, None)
|
||||||
|
|
||||||
def add_building(self, ground_object: BuildingGroundObject,
|
def add_building(self, ground_object: BuildingGroundObject, group: Group) -> None:
|
||||||
group: Group) -> None:
|
|
||||||
# The actual name is a String (the pydcs translatable string), which
|
# The actual name is a String (the pydcs translatable string), which
|
||||||
# doesn't define __eq__.
|
# doesn't define __eq__.
|
||||||
# The name of the initiator in the DCS dead event will have " object"
|
# The name of the initiator in the DCS dead event will have " object"
|
||||||
@ -119,8 +123,9 @@ class UnitMap:
|
|||||||
raise RuntimeError(f"Duplicate TGO unit: {name}")
|
raise RuntimeError(f"Duplicate TGO unit: {name}")
|
||||||
self.buildings[name] = Building(ground_object)
|
self.buildings[name] = Building(ground_object)
|
||||||
|
|
||||||
def add_fortification(self, ground_object: BuildingGroundObject,
|
def add_fortification(
|
||||||
group: VehicleGroup) -> None:
|
self, ground_object: BuildingGroundObject, group: VehicleGroup
|
||||||
|
) -> None:
|
||||||
if len(group.units) != 1:
|
if len(group.units) != 1:
|
||||||
raise ValueError("Fortification groups must have exactly one unit.")
|
raise ValueError("Fortification groups must have exactly one unit.")
|
||||||
unit = group.units[0]
|
unit = group.units[0]
|
||||||
|
|||||||
@ -58,7 +58,7 @@ class Weather:
|
|||||||
return None
|
return None
|
||||||
return Fog(
|
return Fog(
|
||||||
visibility=meters(random.randint(2500, 5000)),
|
visibility=meters(random.randint(2500, 5000)),
|
||||||
thickness=random.randint(100, 500)
|
thickness=random.randint(100, 500),
|
||||||
)
|
)
|
||||||
|
|
||||||
def generate_wind(self) -> WindConditions:
|
def generate_wind(self) -> WindConditions:
|
||||||
@ -76,7 +76,7 @@ class Weather:
|
|||||||
# Always some wind to make the smoke move a bit.
|
# Always some wind to make the smoke move a bit.
|
||||||
at_0m=Wind(wind_direction, max(1, base_wind * at_0m_factor)),
|
at_0m=Wind(wind_direction, max(1, base_wind * at_0m_factor)),
|
||||||
at_2000m=Wind(wind_direction, base_wind * at_2000m_factor),
|
at_2000m=Wind(wind_direction, base_wind * at_2000m_factor),
|
||||||
at_8000m=Wind(wind_direction, base_wind * at_8000m_factor)
|
at_8000m=Wind(wind_direction, base_wind * at_8000m_factor),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -105,7 +105,7 @@ class Cloudy(Weather):
|
|||||||
base=self.random_cloud_base(),
|
base=self.random_cloud_base(),
|
||||||
density=random.randint(1, 8),
|
density=random.randint(1, 8),
|
||||||
thickness=self.random_cloud_thickness(),
|
thickness=self.random_cloud_thickness(),
|
||||||
precipitation=PydcsWeather.Preceptions.None_
|
precipitation=PydcsWeather.Preceptions.None_,
|
||||||
)
|
)
|
||||||
|
|
||||||
def generate_wind(self) -> WindConditions:
|
def generate_wind(self) -> WindConditions:
|
||||||
@ -118,7 +118,7 @@ class Raining(Weather):
|
|||||||
base=self.random_cloud_base(),
|
base=self.random_cloud_base(),
|
||||||
density=random.randint(5, 8),
|
density=random.randint(5, 8),
|
||||||
thickness=self.random_cloud_thickness(),
|
thickness=self.random_cloud_thickness(),
|
||||||
precipitation=PydcsWeather.Preceptions.Rain
|
precipitation=PydcsWeather.Preceptions.Rain,
|
||||||
)
|
)
|
||||||
|
|
||||||
def generate_wind(self) -> WindConditions:
|
def generate_wind(self) -> WindConditions:
|
||||||
@ -131,7 +131,7 @@ class Thunderstorm(Weather):
|
|||||||
base=self.random_cloud_base(),
|
base=self.random_cloud_base(),
|
||||||
density=random.randint(9, 10),
|
density=random.randint(9, 10),
|
||||||
thickness=self.random_cloud_thickness(),
|
thickness=self.random_cloud_thickness(),
|
||||||
precipitation=PydcsWeather.Preceptions.Thunderstorm
|
precipitation=PydcsWeather.Preceptions.Thunderstorm,
|
||||||
)
|
)
|
||||||
|
|
||||||
def generate_wind(self) -> WindConditions:
|
def generate_wind(self) -> WindConditions:
|
||||||
@ -145,20 +145,29 @@ class Conditions:
|
|||||||
weather: Weather
|
weather: Weather
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def generate(cls, theater: ConflictTheater, day: datetime.date,
|
def generate(
|
||||||
time_of_day: TimeOfDay, settings: Settings) -> Conditions:
|
cls,
|
||||||
|
theater: ConflictTheater,
|
||||||
|
day: datetime.date,
|
||||||
|
time_of_day: TimeOfDay,
|
||||||
|
settings: Settings,
|
||||||
|
) -> Conditions:
|
||||||
return cls(
|
return cls(
|
||||||
time_of_day=time_of_day,
|
time_of_day=time_of_day,
|
||||||
start_time=cls.generate_start_time(
|
start_time=cls.generate_start_time(
|
||||||
theater, day, time_of_day, settings.night_disabled
|
theater, day, time_of_day, settings.night_disabled
|
||||||
),
|
),
|
||||||
weather=cls.generate_weather()
|
weather=cls.generate_weather(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def generate_start_time(cls, theater: ConflictTheater, day: datetime.date,
|
def generate_start_time(
|
||||||
|
cls,
|
||||||
|
theater: ConflictTheater,
|
||||||
|
day: datetime.date,
|
||||||
time_of_day: TimeOfDay,
|
time_of_day: TimeOfDay,
|
||||||
night_disabled: bool) -> datetime.datetime:
|
night_disabled: bool,
|
||||||
|
) -> datetime.datetime:
|
||||||
if night_disabled:
|
if night_disabled:
|
||||||
logging.info("Skip Night mission due to user settings")
|
logging.info("Skip Night mission due to user settings")
|
||||||
time_range = {
|
time_range = {
|
||||||
@ -181,6 +190,7 @@ class Conditions:
|
|||||||
Cloudy: 60,
|
Cloudy: 60,
|
||||||
ClearSkies: 20,
|
ClearSkies: 20,
|
||||||
}
|
}
|
||||||
weather_type = random.choices(list(chances.keys()),
|
weather_type = random.choices(
|
||||||
weights=list(chances.values()))[0]
|
list(chances.keys()), weights=list(chances.values())
|
||||||
|
)[0]
|
||||||
return weather_type()
|
return weather_type()
|
||||||
|
|||||||
680
gen/aircraft.py
680
gen/aircraft.py
File diff suppressed because it is too large
Load Diff
160
gen/airfields.py
160
gen/airfields.py
File diff suppressed because it is too large
Load Diff
@ -22,8 +22,6 @@ from .radios import RadioFrequency, RadioRegistry
|
|||||||
from .tacan import TacanBand, TacanChannel, TacanRegistry
|
from .tacan import TacanBand, TacanChannel, TacanRegistry
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
TANKER_DISTANCE = 15000
|
TANKER_DISTANCE = 15000
|
||||||
TANKER_ALT = 4572
|
TANKER_ALT = 4572
|
||||||
TANKER_HEADING_OFFSET = 45
|
TANKER_HEADING_OFFSET = 45
|
||||||
@ -35,6 +33,7 @@ AWACS_ALT = 13000
|
|||||||
@dataclass
|
@dataclass
|
||||||
class AwacsInfo:
|
class AwacsInfo:
|
||||||
"""AWACS information for the kneeboard."""
|
"""AWACS information for the kneeboard."""
|
||||||
|
|
||||||
dcsGroupName: str
|
dcsGroupName: str
|
||||||
callsign: str
|
callsign: str
|
||||||
freq: RadioFrequency
|
freq: RadioFrequency
|
||||||
@ -43,6 +42,7 @@ class AwacsInfo:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class TankerInfo:
|
class TankerInfo:
|
||||||
"""Tanker information for the kneeboard."""
|
"""Tanker information for the kneeboard."""
|
||||||
|
|
||||||
dcsGroupName: str
|
dcsGroupName: str
|
||||||
callsign: str
|
callsign: str
|
||||||
variant: str
|
variant: str
|
||||||
@ -57,10 +57,14 @@ class AirSupport:
|
|||||||
|
|
||||||
|
|
||||||
class AirSupportConflictGenerator:
|
class AirSupportConflictGenerator:
|
||||||
|
def __init__(
|
||||||
def __init__(self, mission: Mission, conflict: Conflict, game,
|
self,
|
||||||
|
mission: Mission,
|
||||||
|
conflict: Conflict,
|
||||||
|
game,
|
||||||
radio_registry: RadioRegistry,
|
radio_registry: RadioRegistry,
|
||||||
tacan_registry: TacanRegistry) -> None:
|
tacan_registry: TacanRegistry,
|
||||||
|
) -> None:
|
||||||
self.mission = mission
|
self.mission = mission
|
||||||
self.conflict = conflict
|
self.conflict = conflict
|
||||||
self.game = game
|
self.game = game
|
||||||
@ -83,20 +87,35 @@ class AirSupportConflictGenerator:
|
|||||||
return (TANKER_ALT, 574)
|
return (TANKER_ALT, 574)
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
player_cp = self.conflict.from_cp if self.conflict.from_cp.captured else self.conflict.to_cp
|
player_cp = (
|
||||||
|
self.conflict.from_cp
|
||||||
|
if self.conflict.from_cp.captured
|
||||||
|
else self.conflict.to_cp
|
||||||
|
)
|
||||||
|
|
||||||
fallback_tanker_number = 0
|
fallback_tanker_number = 0
|
||||||
|
|
||||||
for i, tanker_unit_type in enumerate(db.find_unittype(Refueling, self.conflict.attackers_side)):
|
for i, tanker_unit_type in enumerate(
|
||||||
|
db.find_unittype(Refueling, self.conflict.attackers_side)
|
||||||
|
):
|
||||||
alt, airspeed = self._get_tanker_params(tanker_unit_type)
|
alt, airspeed = self._get_tanker_params(tanker_unit_type)
|
||||||
variant = db.unit_type_name(tanker_unit_type)
|
variant = db.unit_type_name(tanker_unit_type)
|
||||||
freq = self.radio_registry.alloc_uhf()
|
freq = self.radio_registry.alloc_uhf()
|
||||||
tacan = self.tacan_registry.alloc_for_band(TacanBand.Y)
|
tacan = self.tacan_registry.alloc_for_band(TacanBand.Y)
|
||||||
tanker_heading = self.conflict.to_cp.position.heading_between_point(self.conflict.from_cp.position) + TANKER_HEADING_OFFSET * i
|
tanker_heading = (
|
||||||
tanker_position = player_cp.position.point_from_heading(tanker_heading, TANKER_DISTANCE)
|
self.conflict.to_cp.position.heading_between_point(
|
||||||
|
self.conflict.from_cp.position
|
||||||
|
)
|
||||||
|
+ TANKER_HEADING_OFFSET * i
|
||||||
|
)
|
||||||
|
tanker_position = player_cp.position.point_from_heading(
|
||||||
|
tanker_heading, TANKER_DISTANCE
|
||||||
|
)
|
||||||
tanker_group = self.mission.refuel_flight(
|
tanker_group = self.mission.refuel_flight(
|
||||||
country=self.mission.country(self.game.player_country),
|
country=self.mission.country(self.game.player_country),
|
||||||
name=namegen.next_tanker_name(self.mission.country(self.game.player_country), tanker_unit_type),
|
name=namegen.next_tanker_name(
|
||||||
|
self.mission.country(self.game.player_country), tanker_unit_type
|
||||||
|
),
|
||||||
airport=None,
|
airport=None,
|
||||||
plane_type=tanker_unit_type,
|
plane_type=tanker_unit_type,
|
||||||
position=tanker_position,
|
position=tanker_position,
|
||||||
@ -127,14 +146,23 @@ class AirSupportConflictGenerator:
|
|||||||
if tanker_unit_type != IL_78M:
|
if tanker_unit_type != IL_78M:
|
||||||
# Override PyDCS tacan channel.
|
# Override PyDCS tacan channel.
|
||||||
tanker_group.points[0].tasks.pop()
|
tanker_group.points[0].tasks.pop()
|
||||||
tanker_group.points[0].tasks.append(ActivateBeaconCommand(
|
tanker_group.points[0].tasks.append(
|
||||||
tacan.number, tacan.band.value, tacan_callsign, True,
|
ActivateBeaconCommand(
|
||||||
tanker_group.units[0].id, True))
|
tacan.number,
|
||||||
|
tacan.band.value,
|
||||||
|
tacan_callsign,
|
||||||
|
True,
|
||||||
|
tanker_group.units[0].id,
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
tanker_group.points[0].tasks.append(SetInvisibleCommand(True))
|
tanker_group.points[0].tasks.append(SetInvisibleCommand(True))
|
||||||
tanker_group.points[0].tasks.append(SetImmortalCommand(True))
|
tanker_group.points[0].tasks.append(SetImmortalCommand(True))
|
||||||
|
|
||||||
self.air_support.tankers.append(TankerInfo(str(tanker_group.name), callsign, variant, freq, tacan))
|
self.air_support.tankers.append(
|
||||||
|
TankerInfo(str(tanker_group.name), callsign, variant, freq, tacan)
|
||||||
|
)
|
||||||
|
|
||||||
if not self.game.settings.disable_legacy_aewc:
|
if not self.game.settings.disable_legacy_aewc:
|
||||||
possible_awacs = db.find_unittype(AWACS, self.conflict.attackers_side)
|
possible_awacs = db.find_unittype(AWACS, self.conflict.attackers_side)
|
||||||
@ -145,11 +173,15 @@ class AirSupportConflictGenerator:
|
|||||||
|
|
||||||
awacs_flight = self.mission.awacs_flight(
|
awacs_flight = self.mission.awacs_flight(
|
||||||
country=self.mission.country(self.game.player_country),
|
country=self.mission.country(self.game.player_country),
|
||||||
name=namegen.next_awacs_name(self.mission.country(self.game.player_country)),
|
name=namegen.next_awacs_name(
|
||||||
|
self.mission.country(self.game.player_country)
|
||||||
|
),
|
||||||
plane_type=awacs_unit,
|
plane_type=awacs_unit,
|
||||||
altitude=AWACS_ALT,
|
altitude=AWACS_ALT,
|
||||||
airport=None,
|
airport=None,
|
||||||
position=self.conflict.position.random_point_within(AWACS_DISTANCE, AWACS_DISTANCE),
|
position=self.conflict.position.random_point_within(
|
||||||
|
AWACS_DISTANCE, AWACS_DISTANCE
|
||||||
|
),
|
||||||
frequency=freq.mhz,
|
frequency=freq.mhz,
|
||||||
start_type=StartType.Warm,
|
start_type=StartType.Warm,
|
||||||
)
|
)
|
||||||
@ -158,7 +190,12 @@ class AirSupportConflictGenerator:
|
|||||||
awacs_flight.points[0].tasks.append(SetInvisibleCommand(True))
|
awacs_flight.points[0].tasks.append(SetInvisibleCommand(True))
|
||||||
awacs_flight.points[0].tasks.append(SetImmortalCommand(True))
|
awacs_flight.points[0].tasks.append(SetImmortalCommand(True))
|
||||||
|
|
||||||
self.air_support.awacs.append(AwacsInfo(
|
self.air_support.awacs.append(
|
||||||
str(awacs_flight.name), callsign_for_support_unit(awacs_flight), freq))
|
AwacsInfo(
|
||||||
|
str(awacs_flight.name),
|
||||||
|
callsign_for_support_unit(awacs_flight),
|
||||||
|
freq,
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logging.warning("No AWACS for faction")
|
logging.warning("No AWACS for faction")
|
||||||
310
gen/armor.py
310
gen/armor.py
@ -12,9 +12,17 @@ from dcs.country import Country
|
|||||||
from dcs.mapping import Point
|
from dcs.mapping import Point
|
||||||
from dcs.planes import MQ_9_Reaper
|
from dcs.planes import MQ_9_Reaper
|
||||||
from dcs.point import PointAction
|
from dcs.point import PointAction
|
||||||
from dcs.task import (EPLRS, AttackGroup, ControlledTask, FireAtPoint,
|
from dcs.task import (
|
||||||
GoToWaypoint, Hold, OrbitAction, SetImmortalCommand,
|
EPLRS,
|
||||||
SetInvisibleCommand)
|
AttackGroup,
|
||||||
|
ControlledTask,
|
||||||
|
FireAtPoint,
|
||||||
|
GoToWaypoint,
|
||||||
|
Hold,
|
||||||
|
OrbitAction,
|
||||||
|
SetImmortalCommand,
|
||||||
|
SetInvisibleCommand,
|
||||||
|
)
|
||||||
from dcs.triggers import Event, TriggerOnce
|
from dcs.triggers import Event, TriggerOnce
|
||||||
from dcs.unit import Vehicle
|
from dcs.unit import Vehicle
|
||||||
from dcs.unitgroup import VehicleGroup
|
from dcs.unitgroup import VehicleGroup
|
||||||
@ -24,8 +32,11 @@ from game.unitmap import UnitMap
|
|||||||
from game.utils import heading_sum, opposite_heading
|
from game.utils import heading_sum, opposite_heading
|
||||||
from game.theater.controlpoint import ControlPoint
|
from game.theater.controlpoint import ControlPoint
|
||||||
|
|
||||||
from gen.ground_forces.ai_ground_planner import (DISTANCE_FROM_FRONTLINE,
|
from gen.ground_forces.ai_ground_planner import (
|
||||||
CombatGroup, CombatGroupRole)
|
DISTANCE_FROM_FRONTLINE,
|
||||||
|
CombatGroup,
|
||||||
|
CombatGroupRole,
|
||||||
|
)
|
||||||
|
|
||||||
from .callsigns import callsign_for_support_unit
|
from .callsigns import callsign_for_support_unit
|
||||||
from .conflictgen import Conflict
|
from .conflictgen import Conflict
|
||||||
@ -56,6 +67,7 @@ INFANTRY_GROUP_SIZE = 5
|
|||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class JtacInfo:
|
class JtacInfo:
|
||||||
"""JTAC information."""
|
"""JTAC information."""
|
||||||
|
|
||||||
dcsGroupName: str
|
dcsGroupName: str
|
||||||
unit_name: str
|
unit_name: str
|
||||||
callsign: str
|
callsign: str
|
||||||
@ -65,7 +77,6 @@ class JtacInfo:
|
|||||||
|
|
||||||
|
|
||||||
class GroundConflictGenerator:
|
class GroundConflictGenerator:
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
mission: Mission,
|
mission: Mission,
|
||||||
@ -74,7 +85,8 @@ class GroundConflictGenerator:
|
|||||||
player_planned_combat_groups: List[CombatGroup],
|
player_planned_combat_groups: List[CombatGroup],
|
||||||
enemy_planned_combat_groups: List[CombatGroup],
|
enemy_planned_combat_groups: List[CombatGroup],
|
||||||
player_stance: CombatStance,
|
player_stance: CombatStance,
|
||||||
unit_map: UnitMap) -> None:
|
unit_map: UnitMap,
|
||||||
|
) -> None:
|
||||||
self.mission = mission
|
self.mission = mission
|
||||||
self.conflict = conflict
|
self.conflict = conflict
|
||||||
self.enemy_planned_combat_groups = enemy_planned_combat_groups
|
self.enemy_planned_combat_groups = enemy_planned_combat_groups
|
||||||
@ -87,14 +99,16 @@ class GroundConflictGenerator:
|
|||||||
|
|
||||||
def _enemy_stance(self):
|
def _enemy_stance(self):
|
||||||
"""Picks the enemy stance according to the number of planned groups on the frontline for each side"""
|
"""Picks the enemy stance according to the number of planned groups on the frontline for each side"""
|
||||||
if len(self.enemy_planned_combat_groups) > len(self.player_planned_combat_groups):
|
if len(self.enemy_planned_combat_groups) > len(
|
||||||
|
self.player_planned_combat_groups
|
||||||
|
):
|
||||||
return random.choice(
|
return random.choice(
|
||||||
[
|
[
|
||||||
CombatStance.AGGRESSIVE,
|
CombatStance.AGGRESSIVE,
|
||||||
CombatStance.AGGRESSIVE,
|
CombatStance.AGGRESSIVE,
|
||||||
CombatStance.AGGRESSIVE,
|
CombatStance.AGGRESSIVE,
|
||||||
CombatStance.ELIMINATION,
|
CombatStance.ELIMINATION,
|
||||||
CombatStance.BREAKTHROUGH
|
CombatStance.BREAKTHROUGH,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -104,7 +118,7 @@ class GroundConflictGenerator:
|
|||||||
CombatStance.DEFENSIVE,
|
CombatStance.DEFENSIVE,
|
||||||
CombatStance.DEFENSIVE,
|
CombatStance.DEFENSIVE,
|
||||||
CombatStance.AMBUSH,
|
CombatStance.AMBUSH,
|
||||||
CombatStance.AGGRESSIVE
|
CombatStance.AGGRESSIVE,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -114,21 +128,27 @@ class GroundConflictGenerator:
|
|||||||
int(base_distance * SPREAD_DISTANCE_FACTOR[0]),
|
int(base_distance * SPREAD_DISTANCE_FACTOR[0]),
|
||||||
int(base_distance * SPREAD_DISTANCE_FACTOR[1]),
|
int(base_distance * SPREAD_DISTANCE_FACTOR[1]),
|
||||||
)
|
)
|
||||||
return point.random_point_within(distance, base_distance * SPREAD_DISTANCE_SIZE_FACTOR)
|
return point.random_point_within(
|
||||||
|
distance, base_distance * SPREAD_DISTANCE_SIZE_FACTOR
|
||||||
|
)
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
position = Conflict.frontline_position(self.conflict.from_cp, self.conflict.to_cp, self.game.theater)
|
position = Conflict.frontline_position(
|
||||||
|
self.conflict.from_cp, self.conflict.to_cp, self.game.theater
|
||||||
|
)
|
||||||
frontline_vector = Conflict.frontline_vector(
|
frontline_vector = Conflict.frontline_vector(
|
||||||
self.conflict.from_cp,
|
self.conflict.from_cp, self.conflict.to_cp, self.game.theater
|
||||||
self.conflict.to_cp,
|
|
||||||
self.game.theater
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create player groups at random position
|
# Create player groups at random position
|
||||||
player_groups = self._generate_groups(self.player_planned_combat_groups, frontline_vector, True)
|
player_groups = self._generate_groups(
|
||||||
|
self.player_planned_combat_groups, frontline_vector, True
|
||||||
|
)
|
||||||
|
|
||||||
# Create enemy groups at random position
|
# Create enemy groups at random position
|
||||||
enemy_groups = self._generate_groups(self.enemy_planned_combat_groups, frontline_vector, False)
|
enemy_groups = self._generate_groups(
|
||||||
|
self.enemy_planned_combat_groups, frontline_vector, False
|
||||||
|
)
|
||||||
|
|
||||||
# Plan combat actions for groups
|
# Plan combat actions for groups
|
||||||
self.plan_action_for_groups(
|
self.plan_action_for_groups(
|
||||||
@ -137,7 +157,7 @@ class GroundConflictGenerator:
|
|||||||
enemy_groups,
|
enemy_groups,
|
||||||
self.conflict.heading + 90,
|
self.conflict.heading + 90,
|
||||||
self.conflict.from_cp,
|
self.conflict.from_cp,
|
||||||
self.conflict.to_cp
|
self.conflict.to_cp,
|
||||||
)
|
)
|
||||||
self.plan_action_for_groups(
|
self.plan_action_for_groups(
|
||||||
self.enemy_stance,
|
self.enemy_stance,
|
||||||
@ -145,7 +165,7 @@ class GroundConflictGenerator:
|
|||||||
player_groups,
|
player_groups,
|
||||||
self.conflict.heading - 90,
|
self.conflict.heading - 90,
|
||||||
self.conflict.to_cp,
|
self.conflict.to_cp,
|
||||||
self.conflict.from_cp
|
self.conflict.from_cp,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add JTAC
|
# Add JTAC
|
||||||
@ -157,33 +177,37 @@ class GroundConflictGenerator:
|
|||||||
if self.game.player_faction.jtac_unit is not None:
|
if self.game.player_faction.jtac_unit is not None:
|
||||||
utype = self.game.player_faction.jtac_unit
|
utype = self.game.player_faction.jtac_unit
|
||||||
|
|
||||||
jtac = self.mission.flight_group(country=self.mission.country(self.game.player_country),
|
jtac = self.mission.flight_group(
|
||||||
|
country=self.mission.country(self.game.player_country),
|
||||||
name=n,
|
name=n,
|
||||||
aircraft_type=utype,
|
aircraft_type=utype,
|
||||||
position=position[0],
|
position=position[0],
|
||||||
airport=None,
|
airport=None,
|
||||||
altitude=5000)
|
altitude=5000,
|
||||||
|
)
|
||||||
jtac.points[0].tasks.append(SetInvisibleCommand(True))
|
jtac.points[0].tasks.append(SetInvisibleCommand(True))
|
||||||
jtac.points[0].tasks.append(SetImmortalCommand(True))
|
jtac.points[0].tasks.append(SetImmortalCommand(True))
|
||||||
jtac.points[0].tasks.append(OrbitAction(5000, 300, OrbitAction.OrbitPattern.Circle))
|
jtac.points[0].tasks.append(
|
||||||
frontline = f"Frontline {self.conflict.from_cp.name}/{self.conflict.to_cp.name}"
|
OrbitAction(5000, 300, OrbitAction.OrbitPattern.Circle)
|
||||||
|
)
|
||||||
|
frontline = (
|
||||||
|
f"Frontline {self.conflict.from_cp.name}/{self.conflict.to_cp.name}"
|
||||||
|
)
|
||||||
# Note: Will need to change if we ever add ground based JTAC.
|
# Note: Will need to change if we ever add ground based JTAC.
|
||||||
callsign = callsign_for_support_unit(jtac)
|
callsign = callsign_for_support_unit(jtac)
|
||||||
self.jtacs.append(JtacInfo(str(jtac.name), n, callsign, frontline, str(code)))
|
self.jtacs.append(
|
||||||
|
JtacInfo(str(jtac.name), n, callsign, frontline, str(code))
|
||||||
|
)
|
||||||
|
|
||||||
def gen_infantry_group_for_group(
|
def gen_infantry_group_for_group(
|
||||||
self,
|
self, group: VehicleGroup, is_player: bool, side: Country, forward_heading: int
|
||||||
group: VehicleGroup,
|
|
||||||
is_player: bool,
|
|
||||||
side: Country,
|
|
||||||
forward_heading: int
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
infantry_position = self.conflict.find_ground_position(
|
infantry_position = self.conflict.find_ground_position(
|
||||||
group.points[0].position.random_point_within(250, 50),
|
group.points[0].position.random_point_within(250, 50),
|
||||||
500,
|
500,
|
||||||
forward_heading,
|
forward_heading,
|
||||||
self.conflict.theater
|
self.conflict.theater,
|
||||||
)
|
)
|
||||||
if not infantry_position:
|
if not infantry_position:
|
||||||
logging.warning("Could not find infantry position")
|
logging.warning("Could not find infantry position")
|
||||||
@ -208,41 +232,47 @@ class GroundConflictGenerator:
|
|||||||
u = random.choice(manpads)
|
u = random.choice(manpads)
|
||||||
self.mission.vehicle_group(
|
self.mission.vehicle_group(
|
||||||
side,
|
side,
|
||||||
namegen.next_infantry_name(side, cp.id, u), u,
|
namegen.next_infantry_name(side, cp.id, u),
|
||||||
|
u,
|
||||||
position=infantry_position,
|
position=infantry_position,
|
||||||
group_size=1,
|
group_size=1,
|
||||||
heading=forward_heading,
|
heading=forward_heading,
|
||||||
move_formation=PointAction.OffRoad)
|
move_formation=PointAction.OffRoad,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
possible_infantry_units = db.find_infantry(faction, allow_manpad=self.game.settings.manpads)
|
possible_infantry_units = db.find_infantry(
|
||||||
|
faction, allow_manpad=self.game.settings.manpads
|
||||||
|
)
|
||||||
if len(possible_infantry_units) == 0:
|
if len(possible_infantry_units) == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
u = random.choice(possible_infantry_units)
|
u = random.choice(possible_infantry_units)
|
||||||
self.mission.vehicle_group(
|
self.mission.vehicle_group(
|
||||||
side,
|
side,
|
||||||
namegen.next_infantry_name(side, cp.id, u), u,
|
namegen.next_infantry_name(side, cp.id, u),
|
||||||
|
u,
|
||||||
position=infantry_position,
|
position=infantry_position,
|
||||||
group_size=1,
|
group_size=1,
|
||||||
heading=forward_heading,
|
heading=forward_heading,
|
||||||
move_formation=PointAction.OffRoad)
|
move_formation=PointAction.OffRoad,
|
||||||
|
)
|
||||||
|
|
||||||
for i in range(INFANTRY_GROUP_SIZE):
|
for i in range(INFANTRY_GROUP_SIZE):
|
||||||
u = random.choice(possible_infantry_units)
|
u = random.choice(possible_infantry_units)
|
||||||
position = infantry_position.random_point_within(55, 5)
|
position = infantry_position.random_point_within(55, 5)
|
||||||
self.mission.vehicle_group(
|
self.mission.vehicle_group(
|
||||||
side,
|
side,
|
||||||
namegen.next_infantry_name(side, cp.id, u), u,
|
namegen.next_infantry_name(side, cp.id, u),
|
||||||
|
u,
|
||||||
position=position,
|
position=position,
|
||||||
group_size=1,
|
group_size=1,
|
||||||
heading=forward_heading,
|
heading=forward_heading,
|
||||||
move_formation=PointAction.OffRoad)
|
move_formation=PointAction.OffRoad,
|
||||||
|
)
|
||||||
|
|
||||||
def _set_reform_waypoint(
|
def _set_reform_waypoint(
|
||||||
self,
|
self, dcs_group: VehicleGroup, forward_heading: int
|
||||||
dcs_group: VehicleGroup,
|
|
||||||
forward_heading: int
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Setting a waypoint close to the spawn position allows the group to reform gracefully
|
"""Setting a waypoint close to the spawn position allows the group to reform gracefully
|
||||||
rather than spin
|
rather than spin
|
||||||
@ -256,7 +286,7 @@ class GroundConflictGenerator:
|
|||||||
gen_group: CombatGroup,
|
gen_group: CombatGroup,
|
||||||
dcs_group: VehicleGroup,
|
dcs_group: VehicleGroup,
|
||||||
forward_heading: int,
|
forward_heading: int,
|
||||||
target: Point
|
target: Point,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Handles adding the DCS tasks for artillery groups for all combat stances.
|
Handles adding the DCS tasks for artillery groups for all combat stances.
|
||||||
@ -269,7 +299,9 @@ class GroundConflictGenerator:
|
|||||||
dcs_group.add_trigger_action(hold_task)
|
dcs_group.add_trigger_action(hold_task)
|
||||||
|
|
||||||
# Artillery strike random start
|
# Artillery strike random start
|
||||||
artillery_trigger = TriggerOnce(Event.NoEvent, "ArtilleryFireTask #" + str(dcs_group.id))
|
artillery_trigger = TriggerOnce(
|
||||||
|
Event.NoEvent, "ArtilleryFireTask #" + str(dcs_group.id)
|
||||||
|
)
|
||||||
artillery_trigger.add_condition(TimeAfter(seconds=random.randint(1, 45) * 60))
|
artillery_trigger.add_condition(TimeAfter(seconds=random.randint(1, 45) * 60))
|
||||||
# TODO: Update to fire at group instead of point
|
# TODO: Update to fire at group instead of point
|
||||||
fire_task = FireAtPoint(target, len(gen_group.units) * 10, 100)
|
fire_task = FireAtPoint(target, len(gen_group.units) * 10, 100)
|
||||||
@ -283,12 +315,19 @@ class GroundConflictGenerator:
|
|||||||
|
|
||||||
# Hold position
|
# Hold position
|
||||||
dcs_group.points[1].tasks.append(Hold())
|
dcs_group.points[1].tasks.append(Hold())
|
||||||
retreat = self.find_retreat_point(dcs_group, forward_heading, (int)(RETREAT_DISTANCE/3))
|
retreat = self.find_retreat_point(
|
||||||
dcs_group.add_waypoint(dcs_group.position.point_from_heading(forward_heading, 1), PointAction.OffRoad)
|
dcs_group, forward_heading, (int)(RETREAT_DISTANCE / 3)
|
||||||
|
)
|
||||||
|
dcs_group.add_waypoint(
|
||||||
|
dcs_group.position.point_from_heading(forward_heading, 1),
|
||||||
|
PointAction.OffRoad,
|
||||||
|
)
|
||||||
dcs_group.points[2].tasks.append(Hold())
|
dcs_group.points[2].tasks.append(Hold())
|
||||||
dcs_group.add_waypoint(retreat, PointAction.OffRoad)
|
dcs_group.add_waypoint(retreat, PointAction.OffRoad)
|
||||||
|
|
||||||
artillery_fallback = TriggerOnce(Event.NoEvent, "ArtilleryRetreat #" + str(dcs_group.id))
|
artillery_fallback = TriggerOnce(
|
||||||
|
Event.NoEvent, "ArtilleryRetreat #" + str(dcs_group.id)
|
||||||
|
)
|
||||||
for i, u in enumerate(dcs_group.units):
|
for i, u in enumerate(dcs_group.units):
|
||||||
artillery_fallback.add_condition(UnitDamaged(u.id))
|
artillery_fallback.add_condition(UnitDamaged(u.id))
|
||||||
if i < len(dcs_group.units) - 1:
|
if i < len(dcs_group.units) - 1:
|
||||||
@ -302,7 +341,9 @@ class GroundConflictGenerator:
|
|||||||
retreat_task.number = 4
|
retreat_task.number = 4
|
||||||
dcs_group.add_trigger_action(retreat_task)
|
dcs_group.add_trigger_action(retreat_task)
|
||||||
|
|
||||||
artillery_fallback.add_action(AITaskPush(dcs_group.id, len(dcs_group.tasks)))
|
artillery_fallback.add_action(
|
||||||
|
AITaskPush(dcs_group.id, len(dcs_group.tasks))
|
||||||
|
)
|
||||||
self.mission.triggerrules.triggers.append(artillery_fallback)
|
self.mission.triggerrules.triggers.append(artillery_fallback)
|
||||||
|
|
||||||
for u in dcs_group.units:
|
for u in dcs_group.units:
|
||||||
@ -330,12 +371,8 @@ class GroundConflictGenerator:
|
|||||||
target = self.find_nearest_enemy_group(dcs_group, enemy_groups)
|
target = self.find_nearest_enemy_group(dcs_group, enemy_groups)
|
||||||
if target is not None:
|
if target is not None:
|
||||||
rand_offset = Point(
|
rand_offset = Point(
|
||||||
random.randint(
|
random.randint(-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK),
|
||||||
-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK
|
random.randint(-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK),
|
||||||
),
|
|
||||||
random.randint(
|
|
||||||
-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
target_point = self.conflict.theater.nearest_land_pos(
|
target_point = self.conflict.theater.nearest_land_pos(
|
||||||
target.points[0].position + rand_offset
|
target.points[0].position + rand_offset
|
||||||
@ -345,8 +382,7 @@ class GroundConflictGenerator:
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
to_cp.position.distance_to_point(dcs_group.points[0].position)
|
to_cp.position.distance_to_point(dcs_group.points[0].position)
|
||||||
<=
|
<= AGGRESIVE_MOVE_DISTANCE
|
||||||
AGGRESIVE_MOVE_DISTANCE
|
|
||||||
):
|
):
|
||||||
attack_point = self.conflict.theater.nearest_land_pos(
|
attack_point = self.conflict.theater.nearest_land_pos(
|
||||||
to_cp.position.random_point_within(500, 0)
|
to_cp.position.random_point_within(500, 0)
|
||||||
@ -358,16 +394,16 @@ class GroundConflictGenerator:
|
|||||||
if offset_heading < 0:
|
if offset_heading < 0:
|
||||||
offset_heading = 358
|
offset_heading = 358
|
||||||
attack_point = self.find_offensive_point(
|
attack_point = self.find_offensive_point(
|
||||||
dcs_group,
|
dcs_group, offset_heading, AGGRESIVE_MOVE_DISTANCE
|
||||||
offset_heading,
|
|
||||||
AGGRESIVE_MOVE_DISTANCE
|
|
||||||
)
|
)
|
||||||
dcs_group.add_waypoint(attack_point, PointAction.OffRoad)
|
dcs_group.add_waypoint(attack_point, PointAction.OffRoad)
|
||||||
elif stance == CombatStance.BREAKTHROUGH:
|
elif stance == CombatStance.BREAKTHROUGH:
|
||||||
# In breakthrough mode, the units will move forward
|
# In breakthrough mode, the units will move forward
|
||||||
# If the enemy base is close enough, the units will attack the base
|
# If the enemy base is close enough, the units will attack the base
|
||||||
if to_cp.position.distance_to_point(
|
if (
|
||||||
dcs_group.points[0].position) <= BREAKTHROUGH_OFFENSIVE_DISTANCE:
|
to_cp.position.distance_to_point(dcs_group.points[0].position)
|
||||||
|
<= BREAKTHROUGH_OFFENSIVE_DISTANCE
|
||||||
|
):
|
||||||
attack_point = self.conflict.theater.nearest_land_pos(
|
attack_point = self.conflict.theater.nearest_land_pos(
|
||||||
to_cp.position.random_point_within(500, 0)
|
to_cp.position.random_point_within(500, 0)
|
||||||
)
|
)
|
||||||
@ -377,27 +413,27 @@ class GroundConflictGenerator:
|
|||||||
offset_heading = forward_heading - 1
|
offset_heading = forward_heading - 1
|
||||||
if offset_heading < 0:
|
if offset_heading < 0:
|
||||||
offset_heading = 359
|
offset_heading = 359
|
||||||
attack_point = self.find_offensive_point(dcs_group, offset_heading, BREAKTHROUGH_OFFENSIVE_DISTANCE)
|
attack_point = self.find_offensive_point(
|
||||||
|
dcs_group, offset_heading, BREAKTHROUGH_OFFENSIVE_DISTANCE
|
||||||
|
)
|
||||||
dcs_group.add_waypoint(attack_point, PointAction.OffRoad)
|
dcs_group.add_waypoint(attack_point, PointAction.OffRoad)
|
||||||
elif stance == CombatStance.ELIMINATION:
|
elif stance == CombatStance.ELIMINATION:
|
||||||
# In elimination mode, the units focus on destroying as much enemy groups as possible
|
# In elimination mode, the units focus on destroying as much enemy groups as possible
|
||||||
targets = self.find_n_nearest_enemy_groups(dcs_group, enemy_groups, 3)
|
targets = self.find_n_nearest_enemy_groups(dcs_group, enemy_groups, 3)
|
||||||
for i, target in enumerate(targets, start=1):
|
for i, target in enumerate(targets, start=1):
|
||||||
rand_offset = Point(
|
rand_offset = Point(
|
||||||
random.randint(
|
random.randint(-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK),
|
||||||
-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK
|
random.randint(-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK),
|
||||||
),
|
|
||||||
random.randint(
|
|
||||||
-RANDOM_OFFSET_ATTACK,
|
|
||||||
RANDOM_OFFSET_ATTACK
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
target_point = self.conflict.theater.nearest_land_pos(
|
target_point = self.conflict.theater.nearest_land_pos(
|
||||||
target.points[0].position+rand_offset
|
target.points[0].position + rand_offset
|
||||||
)
|
)
|
||||||
dcs_group.add_waypoint(target_point, PointAction.OffRoad)
|
dcs_group.add_waypoint(target_point, PointAction.OffRoad)
|
||||||
dcs_group.points[i + 1].tasks.append(AttackGroup(target.id))
|
dcs_group.points[i + 1].tasks.append(AttackGroup(target.id))
|
||||||
if to_cp.position.distance_to_point(dcs_group.points[0].position) <= AGGRESIVE_MOVE_DISTANCE:
|
if (
|
||||||
|
to_cp.position.distance_to_point(dcs_group.points[0].position)
|
||||||
|
<= AGGRESIVE_MOVE_DISTANCE
|
||||||
|
):
|
||||||
attack_point = self.conflict.theater.nearest_land_pos(
|
attack_point = self.conflict.theater.nearest_land_pos(
|
||||||
to_cp.position.random_point_within(500, 0)
|
to_cp.position.random_point_within(500, 0)
|
||||||
)
|
)
|
||||||
@ -420,12 +456,23 @@ class GroundConflictGenerator:
|
|||||||
Returns True if tasking was added, returns False if the stance was not a combat stance.
|
Returns True if tasking was added, returns False if the stance was not a combat stance.
|
||||||
"""
|
"""
|
||||||
self._set_reform_waypoint(dcs_group, forward_heading)
|
self._set_reform_waypoint(dcs_group, forward_heading)
|
||||||
if stance in [CombatStance.AGGRESSIVE, CombatStance.BREAKTHROUGH, CombatStance.ELIMINATION]:
|
if stance in [
|
||||||
|
CombatStance.AGGRESSIVE,
|
||||||
|
CombatStance.BREAKTHROUGH,
|
||||||
|
CombatStance.ELIMINATION,
|
||||||
|
]:
|
||||||
# APC & ATGM will never move too much forward, but will follow along any offensive
|
# APC & ATGM will never move too much forward, but will follow along any offensive
|
||||||
if to_cp.position.distance_to_point(dcs_group.points[0].position) <= AGGRESIVE_MOVE_DISTANCE:
|
if (
|
||||||
attack_point = self.conflict.theater.nearest_land_pos(to_cp.position.random_point_within(500, 0))
|
to_cp.position.distance_to_point(dcs_group.points[0].position)
|
||||||
|
<= AGGRESIVE_MOVE_DISTANCE
|
||||||
|
):
|
||||||
|
attack_point = self.conflict.theater.nearest_land_pos(
|
||||||
|
to_cp.position.random_point_within(500, 0)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
attack_point = self.find_offensive_point(dcs_group, forward_heading, AGGRESIVE_MOVE_DISTANCE)
|
attack_point = self.find_offensive_point(
|
||||||
|
dcs_group, forward_heading, AGGRESIVE_MOVE_DISTANCE
|
||||||
|
)
|
||||||
dcs_group.add_waypoint(attack_point, PointAction.OffRoad)
|
dcs_group.add_waypoint(attack_point, PointAction.OffRoad)
|
||||||
|
|
||||||
if stance != CombatStance.RETREAT:
|
if stance != CombatStance.RETREAT:
|
||||||
@ -434,29 +481,36 @@ class GroundConflictGenerator:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def plan_action_for_groups(
|
def plan_action_for_groups(
|
||||||
self, stance: CombatStance,
|
self,
|
||||||
|
stance: CombatStance,
|
||||||
ally_groups: List[Tuple[VehicleGroup, CombatGroup]],
|
ally_groups: List[Tuple[VehicleGroup, CombatGroup]],
|
||||||
enemy_groups: List[Tuple[VehicleGroup, CombatGroup]],
|
enemy_groups: List[Tuple[VehicleGroup, CombatGroup]],
|
||||||
forward_heading: int,
|
forward_heading: int,
|
||||||
from_cp: ControlPoint,
|
from_cp: ControlPoint,
|
||||||
to_cp: ControlPoint
|
to_cp: ControlPoint,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
if not self.game.settings.perf_moving_units:
|
if not self.game.settings.perf_moving_units:
|
||||||
return
|
return
|
||||||
|
|
||||||
for dcs_group, group in ally_groups:
|
for dcs_group, group in ally_groups:
|
||||||
if hasattr(group.units[0], 'eplrs') and group.units[0].eplrs:
|
if hasattr(group.units[0], "eplrs") and group.units[0].eplrs:
|
||||||
dcs_group.points[0].tasks.append(EPLRS(dcs_group.id))
|
dcs_group.points[0].tasks.append(EPLRS(dcs_group.id))
|
||||||
|
|
||||||
if group.role == CombatGroupRole.ARTILLERY:
|
if group.role == CombatGroupRole.ARTILLERY:
|
||||||
if self.game.settings.perf_artillery:
|
if self.game.settings.perf_artillery:
|
||||||
target = self.get_artillery_target_in_range(dcs_group, group, enemy_groups)
|
target = self.get_artillery_target_in_range(
|
||||||
|
dcs_group, group, enemy_groups
|
||||||
|
)
|
||||||
if target is not None:
|
if target is not None:
|
||||||
self._plan_artillery_action(stance, group, dcs_group, forward_heading, target)
|
self._plan_artillery_action(
|
||||||
|
stance, group, dcs_group, forward_heading, target
|
||||||
|
)
|
||||||
|
|
||||||
elif group.role in [CombatGroupRole.TANK, CombatGroupRole.IFV]:
|
elif group.role in [CombatGroupRole.TANK, CombatGroupRole.IFV]:
|
||||||
self._plan_tank_ifv_action(stance, enemy_groups, dcs_group, forward_heading, to_cp)
|
self._plan_tank_ifv_action(
|
||||||
|
stance, enemy_groups, dcs_group, forward_heading, to_cp
|
||||||
|
)
|
||||||
|
|
||||||
elif group.role in [CombatGroupRole.APC, CombatGroupRole.ATGM]:
|
elif group.role in [CombatGroupRole.APC, CombatGroupRole.ATGM]:
|
||||||
self._plan_apc_atgm_action(stance, dcs_group, forward_heading, to_cp)
|
self._plan_apc_atgm_action(stance, dcs_group, forward_heading, to_cp)
|
||||||
@ -464,11 +518,16 @@ class GroundConflictGenerator:
|
|||||||
if stance == CombatStance.RETREAT:
|
if stance == CombatStance.RETREAT:
|
||||||
# In retreat mode, the units will fall back
|
# In retreat mode, the units will fall back
|
||||||
# If the ally base is close enough, the units will even regroup there
|
# If the ally base is close enough, the units will even regroup there
|
||||||
if from_cp.position.distance_to_point(dcs_group.points[0].position) <= RETREAT_DISTANCE:
|
if (
|
||||||
|
from_cp.position.distance_to_point(dcs_group.points[0].position)
|
||||||
|
<= RETREAT_DISTANCE
|
||||||
|
):
|
||||||
retreat_point = from_cp.position.random_point_within(500, 250)
|
retreat_point = from_cp.position.random_point_within(500, 250)
|
||||||
else:
|
else:
|
||||||
retreat_point = self.find_retreat_point(dcs_group, forward_heading)
|
retreat_point = self.find_retreat_point(dcs_group, forward_heading)
|
||||||
reposition_point = retreat_point.point_from_heading(forward_heading, 10) # Another point to make the unit face the enemy
|
reposition_point = retreat_point.point_from_heading(
|
||||||
|
forward_heading, 10
|
||||||
|
) # Another point to make the unit face the enemy
|
||||||
dcs_group.add_waypoint(retreat_point, PointAction.OffRoad)
|
dcs_group.add_waypoint(retreat_point, PointAction.OffRoad)
|
||||||
dcs_group.add_waypoint(reposition_point, PointAction.OffRoad)
|
dcs_group.add_waypoint(reposition_point, PointAction.OffRoad)
|
||||||
|
|
||||||
@ -490,8 +549,10 @@ class GroundConflictGenerator:
|
|||||||
|
|
||||||
# We add a new retreat waypoint
|
# We add a new retreat waypoint
|
||||||
dcs_group.add_waypoint(
|
dcs_group.add_waypoint(
|
||||||
self.find_retreat_point(dcs_group, forward_heading, (int)(RETREAT_DISTANCE / 8)),
|
self.find_retreat_point(
|
||||||
PointAction.OffRoad
|
dcs_group, forward_heading, (int)(RETREAT_DISTANCE / 8)
|
||||||
|
),
|
||||||
|
PointAction.OffRoad,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Fallback task
|
# Fallback task
|
||||||
@ -515,7 +576,7 @@ class GroundConflictGenerator:
|
|||||||
self,
|
self,
|
||||||
dcs_group: VehicleGroup,
|
dcs_group: VehicleGroup,
|
||||||
frontline_heading: int,
|
frontline_heading: int,
|
||||||
distance: int = RETREAT_DISTANCE
|
distance: int = RETREAT_DISTANCE,
|
||||||
) -> Point:
|
) -> Point:
|
||||||
"""
|
"""
|
||||||
Find a point to retreat to
|
Find a point to retreat to
|
||||||
@ -523,17 +584,15 @@ class GroundConflictGenerator:
|
|||||||
:param frontline_heading: Heading of the frontline
|
:param frontline_heading: Heading of the frontline
|
||||||
:return: dcs.mapping.Point object with the desired position
|
:return: dcs.mapping.Point object with the desired position
|
||||||
"""
|
"""
|
||||||
desired_point = dcs_group.points[0].position.point_from_heading(heading_sum(frontline_heading, +180), distance)
|
desired_point = dcs_group.points[0].position.point_from_heading(
|
||||||
|
heading_sum(frontline_heading, +180), distance
|
||||||
|
)
|
||||||
if self.conflict.theater.is_on_land(desired_point):
|
if self.conflict.theater.is_on_land(desired_point):
|
||||||
return desired_point
|
return desired_point
|
||||||
return self.conflict.theater.nearest_land_pos(desired_point)
|
return self.conflict.theater.nearest_land_pos(desired_point)
|
||||||
|
|
||||||
|
|
||||||
def find_offensive_point(
|
def find_offensive_point(
|
||||||
self,
|
self, dcs_group: VehicleGroup, frontline_heading: int, distance: int
|
||||||
dcs_group: VehicleGroup,
|
|
||||||
frontline_heading: int,
|
|
||||||
distance: int
|
|
||||||
) -> Point:
|
) -> Point:
|
||||||
"""
|
"""
|
||||||
Find a point to attack
|
Find a point to attack
|
||||||
@ -542,7 +601,9 @@ class GroundConflictGenerator:
|
|||||||
:param distance: Distance of the offensive (how far unit should move)
|
:param distance: Distance of the offensive (how far unit should move)
|
||||||
:return: dcs.mapping.Point object with the desired position
|
:return: dcs.mapping.Point object with the desired position
|
||||||
"""
|
"""
|
||||||
desired_point = dcs_group.points[0].position.point_from_heading(frontline_heading, distance)
|
desired_point = dcs_group.points[0].position.point_from_heading(
|
||||||
|
frontline_heading, distance
|
||||||
|
)
|
||||||
if self.conflict.theater.is_on_land(desired_point):
|
if self.conflict.theater.is_on_land(desired_point):
|
||||||
return desired_point
|
return desired_point
|
||||||
return self.conflict.theater.nearest_land_pos(desired_point)
|
return self.conflict.theater.nearest_land_pos(desired_point)
|
||||||
@ -551,7 +612,7 @@ class GroundConflictGenerator:
|
|||||||
def find_n_nearest_enemy_groups(
|
def find_n_nearest_enemy_groups(
|
||||||
player_group: VehicleGroup,
|
player_group: VehicleGroup,
|
||||||
enemy_groups: List[Tuple[VehicleGroup, CombatGroup]],
|
enemy_groups: List[Tuple[VehicleGroup, CombatGroup]],
|
||||||
n: int
|
n: int,
|
||||||
) -> List[VehicleGroup]:
|
) -> List[VehicleGroup]:
|
||||||
"""
|
"""
|
||||||
Return the nearest enemy group for the player group
|
Return the nearest enemy group for the player group
|
||||||
@ -562,7 +623,9 @@ class GroundConflictGenerator:
|
|||||||
targets = [] # type: List[Optional[VehicleGroup]]
|
targets = [] # type: List[Optional[VehicleGroup]]
|
||||||
sorted_list = sorted(
|
sorted_list = sorted(
|
||||||
enemy_groups,
|
enemy_groups,
|
||||||
key=lambda group: player_group.points[0].position.distance_to_point(group[0].points[0].position)
|
key=lambda group: player_group.points[0].position.distance_to_point(
|
||||||
|
group[0].points[0].position
|
||||||
|
),
|
||||||
)
|
)
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
# TODO: Is this supposed to return no groups if enemy_groups is less than n?
|
# TODO: Is this supposed to return no groups if enemy_groups is less than n?
|
||||||
@ -574,8 +637,7 @@ class GroundConflictGenerator:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def find_nearest_enemy_group(
|
def find_nearest_enemy_group(
|
||||||
player_group: VehicleGroup,
|
player_group: VehicleGroup, enemy_groups: List[Tuple[VehicleGroup, CombatGroup]]
|
||||||
enemy_groups: List[Tuple[VehicleGroup, CombatGroup]]
|
|
||||||
) -> Optional[VehicleGroup]:
|
) -> Optional[VehicleGroup]:
|
||||||
"""
|
"""
|
||||||
Search the enemy groups for a potential target suitable to armored assault
|
Search the enemy groups for a potential target suitable to armored assault
|
||||||
@ -585,7 +647,9 @@ class GroundConflictGenerator:
|
|||||||
min_distance = 99999999
|
min_distance = 99999999
|
||||||
target = None
|
target = None
|
||||||
for dcs_group, _ in enemy_groups:
|
for dcs_group, _ in enemy_groups:
|
||||||
dist = player_group.points[0].position.distance_to_point(dcs_group.points[0].position)
|
dist = player_group.points[0].position.distance_to_point(
|
||||||
|
dcs_group.points[0].position
|
||||||
|
)
|
||||||
if dist < min_distance:
|
if dist < min_distance:
|
||||||
min_distance = dist
|
min_distance = dist
|
||||||
target = dcs_group
|
target = dcs_group
|
||||||
@ -595,7 +659,7 @@ class GroundConflictGenerator:
|
|||||||
def get_artillery_target_in_range(
|
def get_artillery_target_in_range(
|
||||||
dcs_group: VehicleGroup,
|
dcs_group: VehicleGroup,
|
||||||
group: CombatGroup,
|
group: CombatGroup,
|
||||||
enemy_groups: List[Tuple[VehicleGroup, CombatGroup]]
|
enemy_groups: List[Tuple[VehicleGroup, CombatGroup]],
|
||||||
) -> Optional[Point]:
|
) -> Optional[Point]:
|
||||||
"""
|
"""
|
||||||
Search the enemy groups for a potential target suitable to an artillery unit
|
Search the enemy groups for a potential target suitable to an artillery unit
|
||||||
@ -606,7 +670,9 @@ class GroundConflictGenerator:
|
|||||||
return None
|
return None
|
||||||
for _ in range(10):
|
for _ in range(10):
|
||||||
potential_target = random.choice(enemy_groups)[0]
|
potential_target = random.choice(enemy_groups)[0]
|
||||||
distance_to_target = dcs_group.points[0].position.distance_to_point(potential_target.points[0].position)
|
distance_to_target = dcs_group.points[0].position.distance_to_point(
|
||||||
|
potential_target.points[0].position
|
||||||
|
)
|
||||||
if distance_to_target < rng:
|
if distance_to_target < rng:
|
||||||
return potential_target.points[0].position
|
return potential_target.points[0].position
|
||||||
return None
|
return None
|
||||||
@ -620,12 +686,12 @@ class GroundConflictGenerator:
|
|||||||
if rg > DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][1]:
|
if rg > DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][1]:
|
||||||
rg = random.randint(
|
rg = random.randint(
|
||||||
DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][0],
|
DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][0],
|
||||||
DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][1]
|
DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][1],
|
||||||
)
|
)
|
||||||
elif rg < DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][1]:
|
elif rg < DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][1]:
|
||||||
rg = random.randint(
|
rg = random.randint(
|
||||||
DISTANCE_FROM_FRONTLINE[CombatGroupRole.TANK][0],
|
DISTANCE_FROM_FRONTLINE[CombatGroupRole.TANK][0],
|
||||||
DISTANCE_FROM_FRONTLINE[CombatGroupRole.TANK][1]
|
DISTANCE_FROM_FRONTLINE[CombatGroupRole.TANK][1],
|
||||||
)
|
)
|
||||||
return rg
|
return rg
|
||||||
|
|
||||||
@ -635,42 +701,46 @@ class GroundConflictGenerator:
|
|||||||
combat_width: int,
|
combat_width: int,
|
||||||
distance_from_frontline: int,
|
distance_from_frontline: int,
|
||||||
heading: int,
|
heading: int,
|
||||||
spawn_heading: int
|
spawn_heading: int,
|
||||||
):
|
):
|
||||||
shifted = conflict_position.point_from_heading(heading, random.randint(0, combat_width))
|
shifted = conflict_position.point_from_heading(
|
||||||
desired_point = shifted.point_from_heading(
|
heading, random.randint(0, combat_width)
|
||||||
spawn_heading,
|
)
|
||||||
distance_from_frontline
|
desired_point = shifted.point_from_heading(
|
||||||
|
spawn_heading, distance_from_frontline
|
||||||
|
)
|
||||||
|
return Conflict.find_ground_position(
|
||||||
|
desired_point, combat_width, heading, self.conflict.theater
|
||||||
)
|
)
|
||||||
return Conflict.find_ground_position(desired_point, combat_width, heading, self.conflict.theater)
|
|
||||||
|
|
||||||
|
|
||||||
def _generate_groups(
|
def _generate_groups(
|
||||||
self,
|
self,
|
||||||
groups: List[CombatGroup],
|
groups: List[CombatGroup],
|
||||||
frontline_vector: Tuple[Point, int, int],
|
frontline_vector: Tuple[Point, int, int],
|
||||||
is_player: bool
|
is_player: bool,
|
||||||
) -> List[Tuple[VehicleGroup, CombatGroup]]:
|
) -> List[Tuple[VehicleGroup, CombatGroup]]:
|
||||||
"""Finds valid positions for planned groups and generates a pydcs group for them"""
|
"""Finds valid positions for planned groups and generates a pydcs group for them"""
|
||||||
positioned_groups = []
|
positioned_groups = []
|
||||||
position, heading, combat_width = frontline_vector
|
position, heading, combat_width = frontline_vector
|
||||||
spawn_heading = int(heading_sum(heading, -90)) if is_player else int(heading_sum(heading, 90))
|
spawn_heading = (
|
||||||
|
int(heading_sum(heading, -90))
|
||||||
|
if is_player
|
||||||
|
else int(heading_sum(heading, 90))
|
||||||
|
)
|
||||||
country = self.game.player_country if is_player else self.game.enemy_country
|
country = self.game.player_country if is_player else self.game.enemy_country
|
||||||
for group in groups:
|
for group in groups:
|
||||||
if group.role == CombatGroupRole.ARTILLERY:
|
if group.role == CombatGroupRole.ARTILLERY:
|
||||||
distance_from_frontline = self.get_artilery_group_distance_from_frontline(group)
|
distance_from_frontline = (
|
||||||
|
self.get_artilery_group_distance_from_frontline(group)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
distance_from_frontline = random.randint(
|
distance_from_frontline = random.randint(
|
||||||
DISTANCE_FROM_FRONTLINE[group.role][0],
|
DISTANCE_FROM_FRONTLINE[group.role][0],
|
||||||
DISTANCE_FROM_FRONTLINE[group.role][1]
|
DISTANCE_FROM_FRONTLINE[group.role][1],
|
||||||
)
|
)
|
||||||
|
|
||||||
final_position = self.get_valid_position_for_group(
|
final_position = self.get_valid_position_for_group(
|
||||||
position,
|
position, combat_width, distance_from_frontline, heading, spawn_heading
|
||||||
combat_width,
|
|
||||||
distance_from_frontline,
|
|
||||||
heading,
|
|
||||||
spawn_heading
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if final_position is not None:
|
if final_position is not None:
|
||||||
@ -693,7 +763,7 @@ class GroundConflictGenerator:
|
|||||||
g,
|
g,
|
||||||
is_player,
|
is_player,
|
||||||
self.mission.country(country),
|
self.mission.country(country),
|
||||||
opposite_heading(spawn_heading)
|
opposite_heading(spawn_heading),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logging.warning(f"Unable to get valid position for {group}")
|
logging.warning(f"Unable to get valid position for {group}")
|
||||||
@ -719,11 +789,13 @@ class GroundConflictGenerator:
|
|||||||
logging.info("armorgen: {} for {}".format(unit, side.id))
|
logging.info("armorgen: {} for {}".format(unit, side.id))
|
||||||
group = self.mission.vehicle_group(
|
group = self.mission.vehicle_group(
|
||||||
side,
|
side,
|
||||||
namegen.next_unit_name(side, cp.id, unit), unit,
|
namegen.next_unit_name(side, cp.id, unit),
|
||||||
|
unit,
|
||||||
position=at,
|
position=at,
|
||||||
group_size=count,
|
group_size=count,
|
||||||
heading=heading,
|
heading=heading,
|
||||||
move_formation=move_formation)
|
move_formation=move_formation,
|
||||||
|
)
|
||||||
|
|
||||||
self.unit_map.add_front_line_units(group, cp)
|
self.unit_map.add_front_line_units(group, cp)
|
||||||
|
|
||||||
|
|||||||
@ -96,7 +96,8 @@ class Package:
|
|||||||
if tot is None:
|
if tot is None:
|
||||||
logging.error(
|
logging.error(
|
||||||
f"{flight} requested escort at {waypoint} but that "
|
f"{flight} requested escort at {waypoint} but that "
|
||||||
"waypoint has no TOT. It may not be escorted.")
|
"waypoint has no TOT. It may not be escorted."
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
times.append(tot)
|
times.append(tot)
|
||||||
if times:
|
if times:
|
||||||
@ -117,7 +118,8 @@ class Package:
|
|||||||
logging.error(
|
logging.error(
|
||||||
f"{flight} dismissed escort at {waypoint} but that "
|
f"{flight} dismissed escort at {waypoint} but that "
|
||||||
"waypoint has no TOT or departure time. It may not be "
|
"waypoint has no TOT or departure time. It may not be "
|
||||||
"escorted.")
|
"escorted."
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
times.append(tot)
|
times.append(tot)
|
||||||
if times:
|
if times:
|
||||||
|
|||||||
@ -23,9 +23,11 @@ from .runways import RunwayData
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CommInfo:
|
class CommInfo:
|
||||||
"""Communications information for the kneeboard."""
|
"""Communications information for the kneeboard."""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
freq: RadioFrequency
|
freq: RadioFrequency
|
||||||
|
|
||||||
@ -37,10 +39,13 @@ class FrontLineInfo:
|
|||||||
self.enemy_base: ControlPoint = front_line.control_point_b
|
self.enemy_base: ControlPoint = front_line.control_point_b
|
||||||
self.player_zero: bool = self.player_base.base.total_armor == 0
|
self.player_zero: bool = self.player_base.base.total_armor == 0
|
||||||
self.enemy_zero: bool = self.enemy_base.base.total_armor == 0
|
self.enemy_zero: bool = self.enemy_base.base.total_armor == 0
|
||||||
self.advantage: bool = self.player_base.base.total_armor > self.enemy_base.base.total_armor
|
self.advantage: bool = (
|
||||||
|
self.player_base.base.total_armor > self.enemy_base.base.total_armor
|
||||||
|
)
|
||||||
self.stance: CombatStance = self.player_base.stances[self.enemy_base.id]
|
self.stance: CombatStance = self.player_base.stances[self.enemy_base.id]
|
||||||
self.combat_stances = CombatStance
|
self.combat_stances = CombatStance
|
||||||
|
|
||||||
|
|
||||||
class MissionInfoGenerator:
|
class MissionInfoGenerator:
|
||||||
"""Base type for generators of mission information for the player.
|
"""Base type for generators of mission information for the player.
|
||||||
|
|
||||||
@ -131,7 +136,6 @@ def format_waypoint_time(waypoint: FlightWaypoint, depart_prefix: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
class BriefingGenerator(MissionInfoGenerator):
|
class BriefingGenerator(MissionInfoGenerator):
|
||||||
|
|
||||||
def __init__(self, mission: Mission, game: Game):
|
def __init__(self, mission: Mission, game: Game):
|
||||||
super().__init__(mission, game)
|
super().__init__(mission, game)
|
||||||
self.allied_flights_by_departure: Dict[str, List[FlightData]] = {}
|
self.allied_flights_by_departure: Dict[str, List[FlightData]] = {}
|
||||||
@ -149,28 +153,28 @@ class BriefingGenerator(MissionInfoGenerator):
|
|||||||
self.template = env.get_template("briefingtemplate_EN.j2")
|
self.template = env.get_template("briefingtemplate_EN.j2")
|
||||||
|
|
||||||
def generate(self) -> None:
|
def generate(self) -> None:
|
||||||
"""Generate the mission briefing
|
"""Generate the mission briefing"""
|
||||||
"""
|
|
||||||
self._generate_frontline_info()
|
self._generate_frontline_info()
|
||||||
self.generate_allied_flights_by_departure()
|
self.generate_allied_flights_by_departure()
|
||||||
self.mission.set_description_text(self.template.render(vars(self)))
|
self.mission.set_description_text(self.template.render(vars(self)))
|
||||||
self.mission.add_picture_blue(os.path.abspath(
|
self.mission.add_picture_blue(
|
||||||
"./resources/ui/splash_screen.png"))
|
os.path.abspath("./resources/ui/splash_screen.png")
|
||||||
|
)
|
||||||
|
|
||||||
def _generate_frontline_info(self) -> None:
|
def _generate_frontline_info(self) -> None:
|
||||||
"""Build FrontLineInfo objects from FrontLine type and append to briefing.
|
"""Build FrontLineInfo objects from FrontLine type and append to briefing."""
|
||||||
"""
|
|
||||||
for front_line in self.game.theater.conflicts(from_player=True):
|
for front_line in self.game.theater.conflicts(from_player=True):
|
||||||
self.add_frontline(FrontLineInfo(front_line))
|
self.add_frontline(FrontLineInfo(front_line))
|
||||||
|
|
||||||
# TODO: This should determine if runway is friendly through a method more robust than the existing string match
|
# TODO: This should determine if runway is friendly through a method more robust than the existing string match
|
||||||
def generate_allied_flights_by_departure(self) -> None:
|
def generate_allied_flights_by_departure(self) -> None:
|
||||||
"""Create iterable to display allied flights grouped by departure airfield.
|
"""Create iterable to display allied flights grouped by departure airfield."""
|
||||||
"""
|
|
||||||
for flight in self.flights:
|
for flight in self.flights:
|
||||||
if not flight.client_units and flight.friendly:
|
if not flight.client_units and flight.friendly:
|
||||||
name = flight.departure.airfield_name
|
name = flight.departure.airfield_name
|
||||||
if name in self.allied_flights_by_departure: # where else can we get this?
|
if (
|
||||||
|
name in self.allied_flights_by_departure
|
||||||
|
): # where else can we get this?
|
||||||
self.allied_flights_by_departure[name].append(flight)
|
self.allied_flights_by_departure[name].append(flight)
|
||||||
else:
|
else:
|
||||||
self.allied_flights_by_departure[name] = [flight]
|
self.allied_flights_by_departure[name] = [flight]
|
||||||
|
|||||||
@ -23,5 +23,9 @@ def generate_coastal_group(game, ground_object, faction_name: str):
|
|||||||
generator.generate()
|
generator.generate()
|
||||||
return generator.get_generated_group()
|
return generator.get_generated_group()
|
||||||
else:
|
else:
|
||||||
logging.info("Unable to generate missile group, generator : " + str(gen) + "does not exists")
|
logging.info(
|
||||||
|
"Unable to generate missile group, generator : "
|
||||||
|
+ str(gen)
|
||||||
|
+ "does not exists"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
@ -4,7 +4,6 @@ from gen.sam.group_generator import GroupGenerator
|
|||||||
|
|
||||||
|
|
||||||
class SilkwormGenerator(GroupGenerator):
|
class SilkwormGenerator(GroupGenerator):
|
||||||
|
|
||||||
def __init__(self, game, ground_object, faction):
|
def __init__(self, game, ground_object, faction):
|
||||||
super(SilkwormGenerator, self).__init__(game, ground_object)
|
super(SilkwormGenerator, self).__init__(game, ground_object)
|
||||||
self.faction = faction
|
self.faction = faction
|
||||||
@ -13,20 +12,47 @@ class SilkwormGenerator(GroupGenerator):
|
|||||||
|
|
||||||
positions = self.get_circular_position(5, launcher_distance=120, coverage=180)
|
positions = self.get_circular_position(5, launcher_distance=120, coverage=180)
|
||||||
|
|
||||||
self.add_unit(MissilesSS.Silkworm_Radar, "SR#0", self.position.x, self.position.y, self.heading)
|
self.add_unit(
|
||||||
|
MissilesSS.Silkworm_Radar,
|
||||||
|
"SR#0",
|
||||||
|
self.position.x,
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
# Launchers
|
# Launchers
|
||||||
for i, p in enumerate(positions):
|
for i, p in enumerate(positions):
|
||||||
self.add_unit(MissilesSS.SS_N_2_Silkworm, "Missile#" + str(i), p[0], p[1], self.heading)
|
self.add_unit(
|
||||||
|
MissilesSS.SS_N_2_Silkworm,
|
||||||
|
"Missile#" + str(i),
|
||||||
|
p[0],
|
||||||
|
p[1],
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
# Commander
|
# Commander
|
||||||
self.add_unit(Unarmed.Transport_KAMAZ_43101, "KAMAZ#0", self.position.x - 35, self.position.y - 20,
|
self.add_unit(
|
||||||
self.heading)
|
Unarmed.Transport_KAMAZ_43101,
|
||||||
|
"KAMAZ#0",
|
||||||
|
self.position.x - 35,
|
||||||
|
self.position.y - 20,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
# Shorad
|
# Shorad
|
||||||
self.add_unit(AirDefence.SPAAA_ZSU_23_4_Shilka, "SHILKA#0", self.position.x - 55, self.position.y - 38,
|
self.add_unit(
|
||||||
self.heading)
|
AirDefence.SPAAA_ZSU_23_4_Shilka,
|
||||||
|
"SHILKA#0",
|
||||||
|
self.position.x - 55,
|
||||||
|
self.position.y - 38,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
# Shorad 2
|
# Shorad 2
|
||||||
self.add_unit(AirDefence.SAM_SA_9_Strela_1_9P31, "STRELA#0",
|
self.add_unit(
|
||||||
self.position.x + 200, self.position.y + 15, 90)
|
AirDefence.SAM_SA_9_Strela_1_9P31,
|
||||||
|
"STRELA#0",
|
||||||
|
self.position.x + 200,
|
||||||
|
self.position.y + 15,
|
||||||
|
90,
|
||||||
|
)
|
||||||
|
|||||||
@ -12,8 +12,10 @@ from game.utils import heading_sum, opposite_heading
|
|||||||
|
|
||||||
FRONTLINE_LENGTH = 80000
|
FRONTLINE_LENGTH = 80000
|
||||||
|
|
||||||
|
|
||||||
class Conflict:
|
class Conflict:
|
||||||
def __init__(self,
|
def __init__(
|
||||||
|
self,
|
||||||
theater: ConflictTheater,
|
theater: ConflictTheater,
|
||||||
from_cp: ControlPoint,
|
from_cp: ControlPoint,
|
||||||
to_cp: ControlPoint,
|
to_cp: ControlPoint,
|
||||||
@ -23,7 +25,7 @@ class Conflict:
|
|||||||
defenders_country: Country,
|
defenders_country: Country,
|
||||||
position: Point,
|
position: Point,
|
||||||
heading: Optional[int] = None,
|
heading: Optional[int] = None,
|
||||||
size: Optional[int] = None
|
size: Optional[int] = None,
|
||||||
):
|
):
|
||||||
|
|
||||||
self.attackers_side = attackers_side
|
self.attackers_side = attackers_side
|
||||||
@ -43,27 +45,49 @@ class Conflict:
|
|||||||
return from_cp.has_frontline and to_cp.has_frontline
|
return from_cp.has_frontline and to_cp.has_frontline
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def frontline_position(cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater) -> Tuple[Point, int]:
|
def frontline_position(
|
||||||
|
cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater
|
||||||
|
) -> Tuple[Point, int]:
|
||||||
frontline = FrontLine(from_cp, to_cp, theater)
|
frontline = FrontLine(from_cp, to_cp, theater)
|
||||||
attack_heading = frontline.attack_heading
|
attack_heading = frontline.attack_heading
|
||||||
position = cls.find_ground_position(frontline.position, FRONTLINE_LENGTH, heading_sum(attack_heading, 90), theater)
|
position = cls.find_ground_position(
|
||||||
|
frontline.position,
|
||||||
|
FRONTLINE_LENGTH,
|
||||||
|
heading_sum(attack_heading, 90),
|
||||||
|
theater,
|
||||||
|
)
|
||||||
return position, opposite_heading(attack_heading)
|
return position, opposite_heading(attack_heading)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def frontline_vector(cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater) -> Tuple[Point, int, int]:
|
def frontline_vector(
|
||||||
|
cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater
|
||||||
|
) -> Tuple[Point, int, int]:
|
||||||
"""
|
"""
|
||||||
Returns a vector for a valid frontline location avoiding exclusion zones.
|
Returns a vector for a valid frontline location avoiding exclusion zones.
|
||||||
"""
|
"""
|
||||||
center_position, heading = cls.frontline_position(from_cp, to_cp, theater)
|
center_position, heading = cls.frontline_position(from_cp, to_cp, theater)
|
||||||
left_heading = heading_sum(heading, -90)
|
left_heading = heading_sum(heading, -90)
|
||||||
right_heading = heading_sum(heading, 90)
|
right_heading = heading_sum(heading, 90)
|
||||||
left_position = cls.extend_ground_position(center_position, int(FRONTLINE_LENGTH / 2), left_heading, theater)
|
left_position = cls.extend_ground_position(
|
||||||
right_position = cls.extend_ground_position(center_position, int(FRONTLINE_LENGTH / 2), right_heading, theater)
|
center_position, int(FRONTLINE_LENGTH / 2), left_heading, theater
|
||||||
|
)
|
||||||
|
right_position = cls.extend_ground_position(
|
||||||
|
center_position, int(FRONTLINE_LENGTH / 2), right_heading, theater
|
||||||
|
)
|
||||||
distance = int(left_position.distance_to_point(right_position))
|
distance = int(left_position.distance_to_point(right_position))
|
||||||
return left_position, right_heading, distance
|
return left_position, right_heading, distance
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def frontline_cas_conflict(cls, attacker_name: str, defender_name: str, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
|
def frontline_cas_conflict(
|
||||||
|
cls,
|
||||||
|
attacker_name: str,
|
||||||
|
defender_name: str,
|
||||||
|
attacker: Country,
|
||||||
|
defender: Country,
|
||||||
|
from_cp: ControlPoint,
|
||||||
|
to_cp: ControlPoint,
|
||||||
|
theater: ConflictTheater,
|
||||||
|
):
|
||||||
assert cls.has_frontline_between(from_cp, to_cp)
|
assert cls.has_frontline_between(from_cp, to_cp)
|
||||||
position, heading, distance = cls.frontline_vector(from_cp, to_cp, theater)
|
position, heading, distance = cls.frontline_vector(from_cp, to_cp, theater)
|
||||||
conflict = cls(
|
conflict = cls(
|
||||||
@ -76,12 +100,14 @@ class Conflict:
|
|||||||
defenders_side=defender_name,
|
defenders_side=defender_name,
|
||||||
attackers_country=attacker,
|
attackers_country=attacker,
|
||||||
defenders_country=defender,
|
defenders_country=defender,
|
||||||
size=distance
|
size=distance,
|
||||||
)
|
)
|
||||||
return conflict
|
return conflict
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def extend_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point:
|
def extend_ground_position(
|
||||||
|
cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater
|
||||||
|
) -> Point:
|
||||||
"""Finds the first intersection with an exclusion zone in one heading from an initial point up to max_distance"""
|
"""Finds the first intersection with an exclusion zone in one heading from an initial point up to max_distance"""
|
||||||
extended = initial.point_from_heading(heading, max_distance)
|
extended = initial.point_from_heading(heading, max_distance)
|
||||||
if theater.landmap is None:
|
if theater.landmap is None:
|
||||||
@ -92,8 +118,7 @@ class Conflict:
|
|||||||
p1 = ShapelyPoint(extended.x, extended.y)
|
p1 = ShapelyPoint(extended.x, extended.y)
|
||||||
line = LineString([p0, p1])
|
line = LineString([p0, p1])
|
||||||
|
|
||||||
intersection = line.intersection(
|
intersection = line.intersection(theater.landmap.inclusion_zone_only.boundary)
|
||||||
theater.landmap.inclusion_zone_only.boundary)
|
|
||||||
if intersection.is_empty:
|
if intersection.is_empty:
|
||||||
# Max extent does not intersect with the boundary of the inclusion
|
# Max extent does not intersect with the boundary of the inclusion
|
||||||
# zone, so the full front line is usable. This does assume that the
|
# zone, so the full front line is usable. This does assume that the
|
||||||
@ -104,7 +129,14 @@ class Conflict:
|
|||||||
return initial.point_from_heading(heading, p0.distance(intersection))
|
return initial.point_from_heading(heading, p0.distance(intersection))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def find_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater, coerce=True) -> Optional[Point]:
|
def find_ground_position(
|
||||||
|
cls,
|
||||||
|
initial: Point,
|
||||||
|
max_distance: int,
|
||||||
|
heading: int,
|
||||||
|
theater: ConflictTheater,
|
||||||
|
coerce=True,
|
||||||
|
) -> Optional[Point]:
|
||||||
"""
|
"""
|
||||||
Finds the nearest valid ground position along a provided heading and it's inverse up to max_distance.
|
Finds the nearest valid ground position along a provided heading and it's inverse up to max_distance.
|
||||||
`coerce=True` will return the closest land position to `initial` regardless of heading or distance
|
`coerce=True` will return the closest land position to `initial` regardless of heading or distance
|
||||||
@ -123,4 +155,3 @@ class Conflict:
|
|||||||
return pos
|
return pos
|
||||||
logging.error("Didn't find ground position ({})!".format(initial))
|
logging.error("Didn't find ground position ({})!".format(initial))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@ -3,15 +3,20 @@ import random
|
|||||||
from dcs.vehicles import Armor
|
from dcs.vehicles import Armor
|
||||||
|
|
||||||
from game import db
|
from game import db
|
||||||
from gen.defenses.armored_group_generator import ArmoredGroupGenerator, FixedSizeArmorGroupGenerator
|
from gen.defenses.armored_group_generator import (
|
||||||
|
ArmoredGroupGenerator,
|
||||||
|
FixedSizeArmorGroupGenerator,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def generate_armor_group(faction:str, game, ground_object):
|
def generate_armor_group(faction: str, game, ground_object):
|
||||||
"""
|
"""
|
||||||
This generate a group of ground units
|
This generate a group of ground units
|
||||||
:return: Generated group
|
:return: Generated group
|
||||||
"""
|
"""
|
||||||
possible_unit = [u for u in db.FACTIONS[faction].frontline_units if u in Armor.__dict__.values()]
|
possible_unit = [
|
||||||
|
u for u in db.FACTIONS[faction].frontline_units if u in Armor.__dict__.values()
|
||||||
|
]
|
||||||
if len(possible_unit) > 0:
|
if len(possible_unit) > 0:
|
||||||
unit_type = random.choice(possible_unit)
|
unit_type = random.choice(possible_unit)
|
||||||
return generate_armor_group_of_type(game, ground_object, unit_type)
|
return generate_armor_group_of_type(game, ground_object, unit_type)
|
||||||
@ -36,4 +41,3 @@ def generate_armor_group_of_type_and_size(game, ground_object, unit_type, size:
|
|||||||
generator = FixedSizeArmorGroupGenerator(game, ground_object, unit_type, size)
|
generator = FixedSizeArmorGroupGenerator(game, ground_object, unit_type, size)
|
||||||
generator.generate()
|
generator.generate()
|
||||||
return generator.get_generated_group()
|
return generator.get_generated_group()
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,6 @@ from gen.sam.group_generator import GroupGenerator
|
|||||||
|
|
||||||
|
|
||||||
class ArmoredGroupGenerator(GroupGenerator):
|
class ArmoredGroupGenerator(GroupGenerator):
|
||||||
|
|
||||||
def __init__(self, game, ground_object, unit_type):
|
def __init__(self, game, ground_object, unit_type):
|
||||||
super(ArmoredGroupGenerator, self).__init__(game, ground_object)
|
super(ArmoredGroupGenerator, self).__init__(game, ground_object)
|
||||||
self.unit_type = unit_type
|
self.unit_type = unit_type
|
||||||
@ -20,13 +19,16 @@ class ArmoredGroupGenerator(GroupGenerator):
|
|||||||
for i in range(grid_x):
|
for i in range(grid_x):
|
||||||
for j in range(grid_y):
|
for j in range(grid_y):
|
||||||
index = index + 1
|
index = index + 1
|
||||||
self.add_unit(self.unit_type, "Armor#" + str(index),
|
self.add_unit(
|
||||||
|
self.unit_type,
|
||||||
|
"Armor#" + str(index),
|
||||||
self.position.x + spacing * i,
|
self.position.x + spacing * i,
|
||||||
self.position.y + spacing * j, self.heading)
|
self.position.y + spacing * j,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FixedSizeArmorGroupGenerator(GroupGenerator):
|
class FixedSizeArmorGroupGenerator(GroupGenerator):
|
||||||
|
|
||||||
def __init__(self, game, ground_object, unit_type, size):
|
def __init__(self, game, ground_object, unit_type, size):
|
||||||
super(FixedSizeArmorGroupGenerator, self).__init__(game, ground_object)
|
super(FixedSizeArmorGroupGenerator, self).__init__(game, ground_object)
|
||||||
self.unit_type = unit_type
|
self.unit_type = unit_type
|
||||||
@ -38,7 +40,10 @@ class FixedSizeArmorGroupGenerator(GroupGenerator):
|
|||||||
index = 0
|
index = 0
|
||||||
for i in range(self.size):
|
for i in range(self.size):
|
||||||
index = index + 1
|
index = index + 1
|
||||||
self.add_unit(self.unit_type, "Armor#" + str(index),
|
self.add_unit(
|
||||||
|
self.unit_type,
|
||||||
|
"Armor#" + str(index),
|
||||||
self.position.x + spacing * i,
|
self.position.x + spacing * i,
|
||||||
self.position.y, self.heading)
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|||||||
@ -2,36 +2,74 @@ import random
|
|||||||
|
|
||||||
from gen.sam.group_generator import ShipGroupGenerator
|
from gen.sam.group_generator import ShipGroupGenerator
|
||||||
|
|
||||||
from dcs.ships import (
|
from dcs.ships import USS_Arleigh_Burke_IIa, Ticonderoga_class
|
||||||
USS_Arleigh_Burke_IIa,
|
|
||||||
Ticonderoga_class
|
|
||||||
)
|
|
||||||
|
|
||||||
class CarrierGroupGenerator(ShipGroupGenerator):
|
class CarrierGroupGenerator(ShipGroupGenerator):
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
|
|
||||||
#Carrier Strike Group 8
|
# Carrier Strike Group 8
|
||||||
if self.faction.carrier_names[0] == "Carrier Strike Group 8":
|
if self.faction.carrier_names[0] == "Carrier Strike Group 8":
|
||||||
carrier_type = random.choice(self.faction.aircraft_carrier)
|
carrier_type = random.choice(self.faction.aircraft_carrier)
|
||||||
|
|
||||||
self.add_unit(carrier_type, "CVN-75 Harry S. Truman", self.position.x, self.position.y, self.heading)
|
self.add_unit(
|
||||||
|
carrier_type,
|
||||||
|
"CVN-75 Harry S. Truman",
|
||||||
|
self.position.x,
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
# Add Arleigh Burke escort
|
# Add Arleigh Burke escort
|
||||||
self.add_unit(USS_Arleigh_Burke_IIa, "USS Ramage", self.position.x + 6482, self.position.y + 6667, self.heading)
|
self.add_unit(
|
||||||
|
USS_Arleigh_Burke_IIa,
|
||||||
|
"USS Ramage",
|
||||||
|
self.position.x + 6482,
|
||||||
|
self.position.y + 6667,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
self.add_unit(USS_Arleigh_Burke_IIa, "USS Mitscher", self.position.x - 7963, self.position.y + 7037, self.heading)
|
self.add_unit(
|
||||||
|
USS_Arleigh_Burke_IIa,
|
||||||
|
"USS Mitscher",
|
||||||
|
self.position.x - 7963,
|
||||||
|
self.position.y + 7037,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
self.add_unit(USS_Arleigh_Burke_IIa, "USS Forrest Sherman", self.position.x - 7408, self.position.y - 7408, self.heading)
|
self.add_unit(
|
||||||
|
USS_Arleigh_Burke_IIa,
|
||||||
|
"USS Forrest Sherman",
|
||||||
|
self.position.x - 7408,
|
||||||
|
self.position.y - 7408,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
self.add_unit(USS_Arleigh_Burke_IIa, "USS Lassen", self.position.x + 8704, self.position.y - 6296, self.heading)
|
self.add_unit(
|
||||||
|
USS_Arleigh_Burke_IIa,
|
||||||
|
"USS Lassen",
|
||||||
|
self.position.x + 8704,
|
||||||
|
self.position.y - 6296,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
# Add Ticonderoga escort
|
# Add Ticonderoga escort
|
||||||
if self.heading >= 180:
|
if self.heading >= 180:
|
||||||
self.add_unit(Ticonderoga_class, "USS Hué City", self.position.x + 2222, self.position.y - 3333, self.heading)
|
self.add_unit(
|
||||||
|
Ticonderoga_class,
|
||||||
|
"USS Hué City",
|
||||||
|
self.position.x + 2222,
|
||||||
|
self.position.y - 3333,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.add_unit(Ticonderoga_class, "USS Hué City", self.position.x - 3333, self.position.y + 2222, self.heading)
|
self.add_unit(
|
||||||
|
Ticonderoga_class,
|
||||||
|
"USS Hué City",
|
||||||
|
self.position.x - 3333,
|
||||||
|
self.position.y + 2222,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
self.get_generated_group().points[0].speed = 20
|
self.get_generated_group().points[0].speed = 20
|
||||||
##################################################################################################
|
##################################################################################################
|
||||||
@ -39,17 +77,47 @@ class CarrierGroupGenerator(ShipGroupGenerator):
|
|||||||
else:
|
else:
|
||||||
if len(self.faction.aircraft_carrier) > 0:
|
if len(self.faction.aircraft_carrier) > 0:
|
||||||
carrier_type = random.choice(self.faction.aircraft_carrier)
|
carrier_type = random.choice(self.faction.aircraft_carrier)
|
||||||
self.add_unit(carrier_type, "Carrier", self.position.x, self.position.y, self.heading)
|
self.add_unit(
|
||||||
|
carrier_type,
|
||||||
|
"Carrier",
|
||||||
|
self.position.x,
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Add destroyers escort
|
# Add destroyers escort
|
||||||
if len(self.faction.destroyers) > 0:
|
if len(self.faction.destroyers) > 0:
|
||||||
dd_type = random.choice(self.faction.destroyers)
|
dd_type = random.choice(self.faction.destroyers)
|
||||||
self.add_unit(dd_type, "DD1", self.position.x + 2500, self.position.y + 4500, self.heading)
|
self.add_unit(
|
||||||
self.add_unit(dd_type, "DD2", self.position.x + 2500, self.position.y - 4500, self.heading)
|
dd_type,
|
||||||
|
"DD1",
|
||||||
|
self.position.x + 2500,
|
||||||
|
self.position.y + 4500,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
dd_type,
|
||||||
|
"DD2",
|
||||||
|
self.position.x + 2500,
|
||||||
|
self.position.y - 4500,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
self.add_unit(dd_type, "DD3", self.position.x + 4500, self.position.y + 8500, self.heading)
|
self.add_unit(
|
||||||
self.add_unit(dd_type, "DD4", self.position.x + 4500, self.position.y - 8500, self.heading)
|
dd_type,
|
||||||
|
"DD3",
|
||||||
|
self.position.x + 4500,
|
||||||
|
self.position.y + 8500,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
dd_type,
|
||||||
|
"DD4",
|
||||||
|
self.position.x + 4500,
|
||||||
|
self.position.y - 8500,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
self.get_generated_group().points[0].speed = 20
|
self.get_generated_group().points[0].speed = 20
|
||||||
@ -20,7 +20,6 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class ChineseNavyGroupGenerator(ShipGroupGenerator):
|
class ChineseNavyGroupGenerator(ShipGroupGenerator):
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
|
|
||||||
include_frigate = random.choice([True, True, False])
|
include_frigate = random.choice([True, True, False])
|
||||||
@ -30,17 +29,45 @@ class ChineseNavyGroupGenerator(ShipGroupGenerator):
|
|||||||
include_frigate = True
|
include_frigate = True
|
||||||
|
|
||||||
if include_frigate:
|
if include_frigate:
|
||||||
self.add_unit(Type_054A_Frigate, "FF1", self.position.x + 1200, self.position.y + 900, self.heading)
|
self.add_unit(
|
||||||
self.add_unit(Type_054A_Frigate, "FF2", self.position.x + 1200, self.position.y - 900, self.heading)
|
Type_054A_Frigate,
|
||||||
|
"FF1",
|
||||||
|
self.position.x + 1200,
|
||||||
|
self.position.y + 900,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
Type_054A_Frigate,
|
||||||
|
"FF2",
|
||||||
|
self.position.x + 1200,
|
||||||
|
self.position.y - 900,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
if include_dd:
|
if include_dd:
|
||||||
dd_type = random.choice([Type_052C_Destroyer, Type_052B_Destroyer])
|
dd_type = random.choice([Type_052C_Destroyer, Type_052B_Destroyer])
|
||||||
self.add_unit(dd_type, "DD1", self.position.x + 2400, self.position.y + 900, self.heading)
|
self.add_unit(
|
||||||
self.add_unit(dd_type, "DD2", self.position.x + 2400, self.position.y - 900, self.heading)
|
dd_type,
|
||||||
|
"DD1",
|
||||||
|
self.position.x + 2400,
|
||||||
|
self.position.y + 900,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
dd_type,
|
||||||
|
"DD2",
|
||||||
|
self.position.x + 2400,
|
||||||
|
self.position.y - 900,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
self.get_generated_group().points[0].speed = 20
|
self.get_generated_group().points[0].speed = 20
|
||||||
|
|
||||||
|
|
||||||
class Type54GroupGenerator(DDGroupGenerator):
|
class Type54GroupGenerator(DDGroupGenerator):
|
||||||
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
|
def __init__(
|
||||||
super(Type54GroupGenerator, self).__init__(game, ground_object, faction, Type_054A_Frigate)
|
self, game: Game, ground_object: TheaterGroundObject, faction: Faction
|
||||||
|
):
|
||||||
|
super(Type54GroupGenerator, self).__init__(
|
||||||
|
game, ground_object, faction, Type_054A_Frigate
|
||||||
|
)
|
||||||
|
|||||||
@ -13,22 +13,47 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class DDGroupGenerator(ShipGroupGenerator):
|
class DDGroupGenerator(ShipGroupGenerator):
|
||||||
|
def __init__(
|
||||||
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction, ddtype: ShipType):
|
self,
|
||||||
|
game: Game,
|
||||||
|
ground_object: TheaterGroundObject,
|
||||||
|
faction: Faction,
|
||||||
|
ddtype: ShipType,
|
||||||
|
):
|
||||||
super(DDGroupGenerator, self).__init__(game, ground_object, faction)
|
super(DDGroupGenerator, self).__init__(game, ground_object, faction)
|
||||||
self.ddtype = ddtype
|
self.ddtype = ddtype
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
self.add_unit(self.ddtype, "DD1", self.position.x + 500, self.position.y + 900, self.heading)
|
self.add_unit(
|
||||||
self.add_unit(self.ddtype, "DD2", self.position.x + 500, self.position.y - 900, self.heading)
|
self.ddtype,
|
||||||
|
"DD1",
|
||||||
|
self.position.x + 500,
|
||||||
|
self.position.y + 900,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
self.ddtype,
|
||||||
|
"DD2",
|
||||||
|
self.position.x + 500,
|
||||||
|
self.position.y - 900,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
self.get_generated_group().points[0].speed = 20
|
self.get_generated_group().points[0].speed = 20
|
||||||
|
|
||||||
|
|
||||||
class OliverHazardPerryGroupGenerator(DDGroupGenerator):
|
class OliverHazardPerryGroupGenerator(DDGroupGenerator):
|
||||||
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
|
def __init__(
|
||||||
super(OliverHazardPerryGroupGenerator, self).__init__(game, ground_object, faction, Oliver_Hazzard_Perry_class)
|
self, game: Game, ground_object: TheaterGroundObject, faction: Faction
|
||||||
|
):
|
||||||
|
super(OliverHazardPerryGroupGenerator, self).__init__(
|
||||||
|
game, ground_object, faction, Oliver_Hazzard_Perry_class
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ArleighBurkeGroupGenerator(DDGroupGenerator):
|
class ArleighBurkeGroupGenerator(DDGroupGenerator):
|
||||||
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
|
def __init__(
|
||||||
super(ArleighBurkeGroupGenerator, self).__init__(game, ground_object, faction, USS_Arleigh_Burke_IIa)
|
self, game: Game, ground_object: TheaterGroundObject, faction: Faction
|
||||||
|
):
|
||||||
|
super(ArleighBurkeGroupGenerator, self).__init__(
|
||||||
|
game, ground_object, faction, USS_Arleigh_Burke_IIa
|
||||||
|
)
|
||||||
|
|||||||
@ -4,18 +4,31 @@ from gen.sam.group_generator import ShipGroupGenerator
|
|||||||
|
|
||||||
|
|
||||||
class LHAGroupGenerator(ShipGroupGenerator):
|
class LHAGroupGenerator(ShipGroupGenerator):
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
|
|
||||||
# Add carrier
|
# Add carrier
|
||||||
if len(self.faction.helicopter_carrier) > 0:
|
if len(self.faction.helicopter_carrier) > 0:
|
||||||
carrier_type = random.choice(self.faction.helicopter_carrier)
|
carrier_type = random.choice(self.faction.helicopter_carrier)
|
||||||
self.add_unit(carrier_type, "LHA", self.position.x, self.position.y, self.heading)
|
self.add_unit(
|
||||||
|
carrier_type, "LHA", self.position.x, self.position.y, self.heading
|
||||||
|
)
|
||||||
|
|
||||||
# Add destroyers escort
|
# Add destroyers escort
|
||||||
if len(self.faction.destroyers) > 0:
|
if len(self.faction.destroyers) > 0:
|
||||||
dd_type = random.choice(self.faction.destroyers)
|
dd_type = random.choice(self.faction.destroyers)
|
||||||
self.add_unit(dd_type, "DD1", self.position.x + 1250, self.position.y + 1450, self.heading)
|
self.add_unit(
|
||||||
self.add_unit(dd_type, "DD2", self.position.x + 1250, self.position.y - 1450, self.heading)
|
dd_type,
|
||||||
|
"DD1",
|
||||||
|
self.position.x + 1250,
|
||||||
|
self.position.y + 1450,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
dd_type,
|
||||||
|
"DD2",
|
||||||
|
self.position.x + 1250,
|
||||||
|
self.position.y - 1450,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
self.get_generated_group().points[0].speed = 20
|
self.get_generated_group().points[0].speed = 20
|
||||||
|
|||||||
@ -9,7 +9,7 @@ from dcs.ships import (
|
|||||||
FF_1135M_Rezky,
|
FF_1135M_Rezky,
|
||||||
CG_1164_Moskva,
|
CG_1164_Moskva,
|
||||||
SSK_877,
|
SSK_877,
|
||||||
SSK_641B
|
SSK_641B,
|
||||||
)
|
)
|
||||||
|
|
||||||
from gen.fleet.dd_group import DDGroupGenerator
|
from gen.fleet.dd_group import DDGroupGenerator
|
||||||
@ -23,7 +23,6 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class RussianNavyGroupGenerator(ShipGroupGenerator):
|
class RussianNavyGroupGenerator(ShipGroupGenerator):
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
|
|
||||||
include_frigate = random.choice([True, True, False])
|
include_frigate = random.choice([True, True, False])
|
||||||
@ -39,37 +38,79 @@ class RussianNavyGroupGenerator(ShipGroupGenerator):
|
|||||||
|
|
||||||
if include_frigate:
|
if include_frigate:
|
||||||
frigate_type = random.choice([FFL_1124_4_Grisha, FSG_1241_1MP_Molniya])
|
frigate_type = random.choice([FFL_1124_4_Grisha, FSG_1241_1MP_Molniya])
|
||||||
self.add_unit(frigate_type, "FF1", self.position.x + 1200, self.position.y + 900, self.heading)
|
self.add_unit(
|
||||||
self.add_unit(frigate_type, "FF2", self.position.x + 1200, self.position.y - 900, self.heading)
|
frigate_type,
|
||||||
|
"FF1",
|
||||||
|
self.position.x + 1200,
|
||||||
|
self.position.y + 900,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
frigate_type,
|
||||||
|
"FF2",
|
||||||
|
self.position.x + 1200,
|
||||||
|
self.position.y - 900,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
if include_dd:
|
if include_dd:
|
||||||
dd_type = random.choice([FFG_11540_Neustrashimy, FF_1135M_Rezky])
|
dd_type = random.choice([FFG_11540_Neustrashimy, FF_1135M_Rezky])
|
||||||
self.add_unit(dd_type, "DD1", self.position.x + 2400, self.position.y + 900, self.heading)
|
self.add_unit(
|
||||||
self.add_unit(dd_type, "DD2", self.position.x + 2400, self.position.y - 900, self.heading)
|
dd_type,
|
||||||
|
"DD1",
|
||||||
|
self.position.x + 2400,
|
||||||
|
self.position.y + 900,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
dd_type,
|
||||||
|
"DD2",
|
||||||
|
self.position.x + 2400,
|
||||||
|
self.position.y - 900,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
if include_cc:
|
if include_cc:
|
||||||
# Only include the Moskva for now, the Pyotry Velikiy is an unkillable monster.
|
# Only include the Moskva for now, the Pyotry Velikiy is an unkillable monster.
|
||||||
# See https://github.com/Khopa/dcs_liberation/issues/567
|
# See https://github.com/Khopa/dcs_liberation/issues/567
|
||||||
self.add_unit(CG_1164_Moskva, "CC1", self.position.x, self.position.y, self.heading)
|
self.add_unit(
|
||||||
|
CG_1164_Moskva, "CC1", self.position.x, self.position.y, self.heading
|
||||||
|
)
|
||||||
|
|
||||||
self.get_generated_group().points[0].speed = 20
|
self.get_generated_group().points[0].speed = 20
|
||||||
|
|
||||||
|
|
||||||
class GrishaGroupGenerator(DDGroupGenerator):
|
class GrishaGroupGenerator(DDGroupGenerator):
|
||||||
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
|
def __init__(
|
||||||
super(GrishaGroupGenerator, self).__init__(game, ground_object, faction, FFL_1124_4_Grisha)
|
self, game: Game, ground_object: TheaterGroundObject, faction: Faction
|
||||||
|
):
|
||||||
|
super(GrishaGroupGenerator, self).__init__(
|
||||||
|
game, ground_object, faction, FFL_1124_4_Grisha
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MolniyaGroupGenerator(DDGroupGenerator):
|
class MolniyaGroupGenerator(DDGroupGenerator):
|
||||||
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
|
def __init__(
|
||||||
super(MolniyaGroupGenerator, self).__init__(game, ground_object, faction, FSG_1241_1MP_Molniya)
|
self, game: Game, ground_object: TheaterGroundObject, faction: Faction
|
||||||
|
):
|
||||||
|
super(MolniyaGroupGenerator, self).__init__(
|
||||||
|
game, ground_object, faction, FSG_1241_1MP_Molniya
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class KiloSubGroupGenerator(DDGroupGenerator):
|
class KiloSubGroupGenerator(DDGroupGenerator):
|
||||||
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
|
def __init__(
|
||||||
super(KiloSubGroupGenerator, self).__init__(game, ground_object, faction, SSK_877)
|
self, game: Game, ground_object: TheaterGroundObject, faction: Faction
|
||||||
|
):
|
||||||
|
super(KiloSubGroupGenerator, self).__init__(
|
||||||
|
game, ground_object, faction, SSK_877
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TangoSubGroupGenerator(DDGroupGenerator):
|
class TangoSubGroupGenerator(DDGroupGenerator):
|
||||||
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
|
def __init__(
|
||||||
super(TangoSubGroupGenerator, self).__init__(game, ground_object, faction, SSK_641B)
|
self, game: Game, ground_object: TheaterGroundObject, faction: Faction
|
||||||
|
):
|
||||||
|
super(TangoSubGroupGenerator, self).__init__(
|
||||||
|
game, ground_object, faction, SSK_641B
|
||||||
|
)
|
||||||
|
|||||||
@ -6,10 +6,15 @@ from gen.sam.group_generator import ShipGroupGenerator
|
|||||||
|
|
||||||
|
|
||||||
class SchnellbootGroupGenerator(ShipGroupGenerator):
|
class SchnellbootGroupGenerator(ShipGroupGenerator):
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
|
|
||||||
for i in range(random.randint(2, 4)):
|
for i in range(random.randint(2, 4)):
|
||||||
self.add_unit(Schnellboot_type_S130, "Schnellboot" + str(i), self.position.x + i * random.randint(100, 250), self.position.y + (random.randint(100, 200)-100), self.heading)
|
self.add_unit(
|
||||||
|
Schnellboot_type_S130,
|
||||||
|
"Schnellboot" + str(i),
|
||||||
|
self.position.x + i * random.randint(100, 250),
|
||||||
|
self.position.y + (random.randint(100, 200) - 100),
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
self.get_generated_group().points[0].speed = 20
|
self.get_generated_group().points[0].speed = 20
|
||||||
|
|||||||
@ -4,10 +4,18 @@ import random
|
|||||||
from game import db
|
from game import db
|
||||||
from gen.fleet.carrier_group import CarrierGroupGenerator
|
from gen.fleet.carrier_group import CarrierGroupGenerator
|
||||||
from gen.fleet.cn_dd_group import ChineseNavyGroupGenerator, Type54GroupGenerator
|
from gen.fleet.cn_dd_group import ChineseNavyGroupGenerator, Type54GroupGenerator
|
||||||
from gen.fleet.dd_group import ArleighBurkeGroupGenerator, OliverHazardPerryGroupGenerator
|
from gen.fleet.dd_group import (
|
||||||
|
ArleighBurkeGroupGenerator,
|
||||||
|
OliverHazardPerryGroupGenerator,
|
||||||
|
)
|
||||||
from gen.fleet.lha_group import LHAGroupGenerator
|
from gen.fleet.lha_group import LHAGroupGenerator
|
||||||
from gen.fleet.ru_dd_group import RussianNavyGroupGenerator, GrishaGroupGenerator, MolniyaGroupGenerator, \
|
from gen.fleet.ru_dd_group import (
|
||||||
KiloSubGroupGenerator, TangoSubGroupGenerator
|
RussianNavyGroupGenerator,
|
||||||
|
GrishaGroupGenerator,
|
||||||
|
MolniyaGroupGenerator,
|
||||||
|
KiloSubGroupGenerator,
|
||||||
|
TangoSubGroupGenerator,
|
||||||
|
)
|
||||||
from gen.fleet.schnellboot import SchnellbootGroupGenerator
|
from gen.fleet.schnellboot import SchnellbootGroupGenerator
|
||||||
from gen.fleet.uboat import UBoatGroupGenerator
|
from gen.fleet.uboat import UBoatGroupGenerator
|
||||||
from gen.fleet.ww2lst import WW2LSTGroupGenerator
|
from gen.fleet.ww2lst import WW2LSTGroupGenerator
|
||||||
@ -25,7 +33,7 @@ SHIP_MAP = {
|
|||||||
"MolniyaGroupGenerator": MolniyaGroupGenerator,
|
"MolniyaGroupGenerator": MolniyaGroupGenerator,
|
||||||
"KiloSubGroupGenerator": KiloSubGroupGenerator,
|
"KiloSubGroupGenerator": KiloSubGroupGenerator,
|
||||||
"TangoSubGroupGenerator": TangoSubGroupGenerator,
|
"TangoSubGroupGenerator": TangoSubGroupGenerator,
|
||||||
"Type54GroupGenerator": Type54GroupGenerator
|
"Type54GroupGenerator": Type54GroupGenerator,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -43,7 +51,11 @@ def generate_ship_group(game, ground_object, faction_name: str):
|
|||||||
generator.generate()
|
generator.generate()
|
||||||
return generator.get_generated_group()
|
return generator.get_generated_group()
|
||||||
else:
|
else:
|
||||||
logging.info("Unable to generate ship group, generator : " + str(gen) + "does not exists")
|
logging.info(
|
||||||
|
"Unable to generate ship group, generator : "
|
||||||
|
+ str(gen)
|
||||||
|
+ "does not exists"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -6,10 +6,15 @@ from gen.sam.group_generator import ShipGroupGenerator
|
|||||||
|
|
||||||
|
|
||||||
class UBoatGroupGenerator(ShipGroupGenerator):
|
class UBoatGroupGenerator(ShipGroupGenerator):
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
|
|
||||||
for i in range(random.randint(1, 4)):
|
for i in range(random.randint(1, 4)):
|
||||||
self.add_unit(Uboat_VIIC_U_flak, "Uboat" + str(i), self.position.x + i * random.randint(100, 250), self.position.y + (random.randint(100, 200)-100), self.heading)
|
self.add_unit(
|
||||||
|
Uboat_VIIC_U_flak,
|
||||||
|
"Uboat" + str(i),
|
||||||
|
self.position.x + i * random.randint(100, 250),
|
||||||
|
self.position.y + (random.randint(100, 200) - 100),
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
self.get_generated_group().points[0].speed = 20
|
self.get_generated_group().points[0].speed = 20
|
||||||
@ -6,13 +6,24 @@ from gen.sam.group_generator import ShipGroupGenerator
|
|||||||
|
|
||||||
|
|
||||||
class WW2LSTGroupGenerator(ShipGroupGenerator):
|
class WW2LSTGroupGenerator(ShipGroupGenerator):
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
|
|
||||||
# Add LS Samuel Chase
|
# Add LS Samuel Chase
|
||||||
self.add_unit(LS_Samuel_Chase, "SamuelChase", self.position.x, self.position.y, self.heading)
|
self.add_unit(
|
||||||
|
LS_Samuel_Chase,
|
||||||
|
"SamuelChase",
|
||||||
|
self.position.x,
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
for i in range(1, random.randint(3, 4)):
|
for i in range(1, random.randint(3, 4)):
|
||||||
self.add_unit(LST_Mk_II, "LST" + str(i), self.position.x + i * random.randint(800, 1200), self.position.y, self.heading)
|
self.add_unit(
|
||||||
|
LST_Mk_II,
|
||||||
|
"LST" + str(i),
|
||||||
|
self.position.x + i * random.randint(800, 1200),
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
self.get_generated_group().points[0].speed = 20
|
self.get_generated_group().points[0].speed = 20
|
||||||
@ -109,16 +109,19 @@ class ProposedMission:
|
|||||||
flights: List[ProposedFlight]
|
flights: List[ProposedFlight]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
flights = ', '.join([str(f) for f in self.flights])
|
flights = ", ".join([str(f) for f in self.flights])
|
||||||
return f"{self.location.name}: {flights}"
|
return f"{self.location.name}: {flights}"
|
||||||
|
|
||||||
|
|
||||||
class AircraftAllocator:
|
class AircraftAllocator:
|
||||||
"""Finds suitable aircraft for proposed missions."""
|
"""Finds suitable aircraft for proposed missions."""
|
||||||
|
|
||||||
def __init__(self, closest_airfields: ClosestAirfields,
|
def __init__(
|
||||||
|
self,
|
||||||
|
closest_airfields: ClosestAirfields,
|
||||||
global_inventory: GlobalAircraftInventory,
|
global_inventory: GlobalAircraftInventory,
|
||||||
is_player: bool) -> None:
|
is_player: bool,
|
||||||
|
) -> None:
|
||||||
self.closest_airfields = closest_airfields
|
self.closest_airfields = closest_airfields
|
||||||
self.global_inventory = global_inventory
|
self.global_inventory = global_inventory
|
||||||
self.is_player = is_player
|
self.is_player = is_player
|
||||||
@ -144,12 +147,12 @@ class AircraftAllocator:
|
|||||||
on subsequent calls. If the found aircraft are not used, the caller is
|
on subsequent calls. If the found aircraft are not used, the caller is
|
||||||
responsible for returning them to the inventory.
|
responsible for returning them to the inventory.
|
||||||
"""
|
"""
|
||||||
return self.find_aircraft_of_type(
|
return self.find_aircraft_of_type(flight, aircraft_for_task(flight.task))
|
||||||
flight, aircraft_for_task(flight.task)
|
|
||||||
)
|
|
||||||
|
|
||||||
def find_aircraft_of_type(
|
def find_aircraft_of_type(
|
||||||
self, flight: ProposedFlight, types: List[Type[FlyingType]],
|
self,
|
||||||
|
flight: ProposedFlight,
|
||||||
|
types: List[Type[FlyingType]],
|
||||||
) -> Optional[Tuple[ControlPoint, Type[FlyingType]]]:
|
) -> Optional[Tuple[ControlPoint, Type[FlyingType]]]:
|
||||||
airfields_in_range = self.closest_airfields.airfields_within(
|
airfields_in_range = self.closest_airfields.airfields_within(
|
||||||
flight.max_distance
|
flight.max_distance
|
||||||
@ -171,18 +174,22 @@ class AircraftAllocator:
|
|||||||
class PackageBuilder:
|
class PackageBuilder:
|
||||||
"""Builds a Package for the flights it receives."""
|
"""Builds a Package for the flights it receives."""
|
||||||
|
|
||||||
def __init__(self, location: MissionTarget,
|
def __init__(
|
||||||
|
self,
|
||||||
|
location: MissionTarget,
|
||||||
closest_airfields: ClosestAirfields,
|
closest_airfields: ClosestAirfields,
|
||||||
global_inventory: GlobalAircraftInventory,
|
global_inventory: GlobalAircraftInventory,
|
||||||
is_player: bool,
|
is_player: bool,
|
||||||
package_country: str,
|
package_country: str,
|
||||||
start_type: str) -> None:
|
start_type: str,
|
||||||
|
) -> None:
|
||||||
self.closest_airfields = closest_airfields
|
self.closest_airfields = closest_airfields
|
||||||
self.is_player = is_player
|
self.is_player = is_player
|
||||||
self.package_country = package_country
|
self.package_country = package_country
|
||||||
self.package = Package(location)
|
self.package = Package(location)
|
||||||
self.allocator = AircraftAllocator(closest_airfields, global_inventory,
|
self.allocator = AircraftAllocator(
|
||||||
is_player)
|
closest_airfields, global_inventory, is_player
|
||||||
|
)
|
||||||
self.global_inventory = global_inventory
|
self.global_inventory = global_inventory
|
||||||
self.start_type = start_type
|
self.start_type = start_type
|
||||||
|
|
||||||
@ -203,14 +210,23 @@ class PackageBuilder:
|
|||||||
else:
|
else:
|
||||||
start_type = self.start_type
|
start_type = self.start_type
|
||||||
|
|
||||||
flight = Flight(self.package, self.package_country, aircraft, plan.num_aircraft, plan.task,
|
flight = Flight(
|
||||||
start_type, departure=airfield, arrival=airfield,
|
self.package,
|
||||||
divert=self.find_divert_field(aircraft, airfield))
|
self.package_country,
|
||||||
|
aircraft,
|
||||||
|
plan.num_aircraft,
|
||||||
|
plan.task,
|
||||||
|
start_type,
|
||||||
|
departure=airfield,
|
||||||
|
arrival=airfield,
|
||||||
|
divert=self.find_divert_field(aircraft, airfield),
|
||||||
|
)
|
||||||
self.package.add_flight(flight)
|
self.package.add_flight(flight)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def find_divert_field(self, aircraft: Type[FlyingType],
|
def find_divert_field(
|
||||||
arrival: ControlPoint) -> Optional[ControlPoint]:
|
self, aircraft: Type[FlyingType], arrival: ControlPoint
|
||||||
|
) -> Optional[ControlPoint]:
|
||||||
divert_limit = nautical_miles(150)
|
divert_limit = nautical_miles(150)
|
||||||
for airfield in self.closest_airfields.airfields_within(divert_limit):
|
for airfield in self.closest_airfields.airfields_within(divert_limit):
|
||||||
if airfield.captured != self.is_player:
|
if airfield.captured != self.is_player:
|
||||||
@ -323,8 +339,8 @@ class ObjectiveFinder:
|
|||||||
return self._targets_by_range(self.enemy_ships())
|
return self._targets_by_range(self.enemy_ships())
|
||||||
|
|
||||||
def _targets_by_range(
|
def _targets_by_range(
|
||||||
self,
|
self, targets: Iterable[MissionTarget]
|
||||||
targets: Iterable[MissionTarget]) -> Iterator[MissionTarget]:
|
) -> Iterator[MissionTarget]:
|
||||||
target_ranges: List[Tuple[MissionTarget, int]] = []
|
target_ranges: List[Tuple[MissionTarget, int]] = []
|
||||||
for target in targets:
|
for target in targets:
|
||||||
ranges: List[int] = []
|
ranges: List[int] = []
|
||||||
@ -430,8 +446,9 @@ class ObjectiveFinder:
|
|||||||
|
|
||||||
def friendly_control_points(self) -> Iterator[ControlPoint]:
|
def friendly_control_points(self) -> Iterator[ControlPoint]:
|
||||||
"""Iterates over all friendly control points."""
|
"""Iterates over all friendly control points."""
|
||||||
return (c for c in self.game.theater.controlpoints if
|
return (
|
||||||
c.is_friendly(self.is_player))
|
c for c in self.game.theater.controlpoints if c.is_friendly(self.is_player)
|
||||||
|
)
|
||||||
|
|
||||||
def farthest_friendly_control_point(self) -> ControlPoint:
|
def farthest_friendly_control_point(self) -> ControlPoint:
|
||||||
"""
|
"""
|
||||||
@ -451,8 +468,11 @@ class ObjectiveFinder:
|
|||||||
|
|
||||||
def enemy_control_points(self) -> Iterator[ControlPoint]:
|
def enemy_control_points(self) -> Iterator[ControlPoint]:
|
||||||
"""Iterates over all enemy control points."""
|
"""Iterates over all enemy control points."""
|
||||||
return (c for c in self.game.theater.controlpoints if
|
return (
|
||||||
not c.is_friendly(self.is_player))
|
c
|
||||||
|
for c in self.game.theater.controlpoints
|
||||||
|
if not c.is_friendly(self.is_player)
|
||||||
|
)
|
||||||
|
|
||||||
def all_possible_targets(self) -> Iterator[MissionTarget]:
|
def all_possible_targets(self) -> Iterator[MissionTarget]:
|
||||||
"""Iterates over all possible mission targets in the theater.
|
"""Iterates over all possible mission targets in the theater.
|
||||||
@ -524,33 +544,46 @@ class CoalitionMissionPlanner:
|
|||||||
eliminated this turn.
|
eliminated this turn.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#Find farthest, friendly CP for AEWC
|
# Find farthest, friendly CP for AEWC
|
||||||
cp = self.objective_finder.farthest_friendly_control_point()
|
cp = self.objective_finder.farthest_friendly_control_point()
|
||||||
yield ProposedMission(cp, [
|
yield ProposedMission(
|
||||||
ProposedFlight(FlightType.AEWC, 1, self.MAX_AWEC_RANGE)
|
cp, [ProposedFlight(FlightType.AEWC, 1, self.MAX_AWEC_RANGE)]
|
||||||
])
|
)
|
||||||
|
|
||||||
# Find friendly CPs within 100 nmi from an enemy airfield, plan CAP.
|
# Find friendly CPs within 100 nmi from an enemy airfield, plan CAP.
|
||||||
for cp in self.objective_finder.vulnerable_control_points():
|
for cp in self.objective_finder.vulnerable_control_points():
|
||||||
# Plan three rounds of CAP to give ~90 minutes coverage. Spacing
|
# Plan three rounds of CAP to give ~90 minutes coverage. Spacing
|
||||||
# these out appropriately is done in stagger_missions.
|
# these out appropriately is done in stagger_missions.
|
||||||
yield ProposedMission(cp, [
|
yield ProposedMission(
|
||||||
|
cp,
|
||||||
|
[
|
||||||
ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE),
|
ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE),
|
||||||
])
|
],
|
||||||
yield ProposedMission(cp, [
|
)
|
||||||
|
yield ProposedMission(
|
||||||
|
cp,
|
||||||
|
[
|
||||||
ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE),
|
ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE),
|
||||||
])
|
],
|
||||||
yield ProposedMission(cp, [
|
)
|
||||||
|
yield ProposedMission(
|
||||||
|
cp,
|
||||||
|
[
|
||||||
ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE),
|
ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE),
|
||||||
])
|
],
|
||||||
|
)
|
||||||
|
|
||||||
# Find front lines, plan CAS.
|
# Find front lines, plan CAS.
|
||||||
for front_line in self.objective_finder.front_lines():
|
for front_line in self.objective_finder.front_lines():
|
||||||
yield ProposedMission(front_line, [
|
yield ProposedMission(
|
||||||
|
front_line,
|
||||||
|
[
|
||||||
ProposedFlight(FlightType.CAS, 2, self.MAX_CAS_RANGE),
|
ProposedFlight(FlightType.CAS, 2, self.MAX_CAS_RANGE),
|
||||||
ProposedFlight(FlightType.TARCAP, 2, self.MAX_CAP_RANGE,
|
ProposedFlight(
|
||||||
EscortType.AirToAir),
|
FlightType.TARCAP, 2, self.MAX_CAP_RANGE, EscortType.AirToAir
|
||||||
])
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def propose_missions(self) -> Iterator[ProposedMission]:
|
def propose_missions(self) -> Iterator[ProposedMission]:
|
||||||
"""Identifies and iterates over potential mission in priority order."""
|
"""Identifies and iterates over potential mission in priority order."""
|
||||||
@ -561,30 +594,46 @@ class CoalitionMissionPlanner:
|
|||||||
# Find enemy SAM sites with ranges that extend to within 50 nmi of
|
# Find enemy SAM sites with ranges that extend to within 50 nmi of
|
||||||
# friendly CPs, front, lines, or objects, plan DEAD.
|
# friendly CPs, front, lines, or objects, plan DEAD.
|
||||||
for sam in self.objective_finder.threatening_sams():
|
for sam in self.objective_finder.threatening_sams():
|
||||||
yield ProposedMission(sam, [
|
yield ProposedMission(
|
||||||
|
sam,
|
||||||
|
[
|
||||||
ProposedFlight(FlightType.DEAD, 2, self.MAX_SEAD_RANGE),
|
ProposedFlight(FlightType.DEAD, 2, self.MAX_SEAD_RANGE),
|
||||||
# TODO: Max escort range.
|
# TODO: Max escort range.
|
||||||
ProposedFlight(FlightType.ESCORT, 2, self.MAX_SEAD_RANGE,
|
ProposedFlight(
|
||||||
EscortType.AirToAir),
|
FlightType.ESCORT, 2, self.MAX_SEAD_RANGE, EscortType.AirToAir
|
||||||
])
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
for group in self.objective_finder.threatening_ships():
|
for group in self.objective_finder.threatening_ships():
|
||||||
yield ProposedMission(group, [
|
yield ProposedMission(
|
||||||
|
group,
|
||||||
|
[
|
||||||
ProposedFlight(FlightType.ANTISHIP, 2, self.MAX_ANTISHIP_RANGE),
|
ProposedFlight(FlightType.ANTISHIP, 2, self.MAX_ANTISHIP_RANGE),
|
||||||
# TODO: Max escort range.
|
# TODO: Max escort range.
|
||||||
ProposedFlight(FlightType.ESCORT, 2, self.MAX_ANTISHIP_RANGE,
|
ProposedFlight(
|
||||||
EscortType.AirToAir),
|
FlightType.ESCORT,
|
||||||
])
|
2,
|
||||||
|
self.MAX_ANTISHIP_RANGE,
|
||||||
|
EscortType.AirToAir,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
for group in self.objective_finder.threatening_vehicle_groups():
|
for group in self.objective_finder.threatening_vehicle_groups():
|
||||||
yield ProposedMission(group, [
|
yield ProposedMission(
|
||||||
|
group,
|
||||||
|
[
|
||||||
ProposedFlight(FlightType.BAI, 2, self.MAX_BAI_RANGE),
|
ProposedFlight(FlightType.BAI, 2, self.MAX_BAI_RANGE),
|
||||||
# TODO: Max escort range.
|
# TODO: Max escort range.
|
||||||
ProposedFlight(FlightType.ESCORT, 2, self.MAX_BAI_RANGE,
|
ProposedFlight(
|
||||||
EscortType.AirToAir),
|
FlightType.ESCORT, 2, self.MAX_BAI_RANGE, EscortType.AirToAir
|
||||||
ProposedFlight(FlightType.SEAD, 2, self.MAX_OCA_RANGE,
|
),
|
||||||
EscortType.Sead),
|
ProposedFlight(
|
||||||
])
|
FlightType.SEAD, 2, self.MAX_OCA_RANGE, EscortType.Sead
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
for target in self.objective_finder.oca_targets(min_aircraft=20):
|
for target in self.objective_finder.oca_targets(min_aircraft=20):
|
||||||
flights = [
|
flights = [
|
||||||
@ -593,27 +642,37 @@ class CoalitionMissionPlanner:
|
|||||||
if self.game.settings.default_start_type == "Cold":
|
if self.game.settings.default_start_type == "Cold":
|
||||||
# Only schedule if the default start type is Cold. If the player
|
# Only schedule if the default start type is Cold. If the player
|
||||||
# has set anything else there are no targets to hit.
|
# has set anything else there are no targets to hit.
|
||||||
flights.append(ProposedFlight(FlightType.OCA_AIRCRAFT, 2,
|
flights.append(
|
||||||
self.MAX_OCA_RANGE))
|
ProposedFlight(FlightType.OCA_AIRCRAFT, 2, self.MAX_OCA_RANGE)
|
||||||
flights.extend([
|
)
|
||||||
|
flights.extend(
|
||||||
|
[
|
||||||
# TODO: Max escort range.
|
# TODO: Max escort range.
|
||||||
ProposedFlight(FlightType.ESCORT, 2, self.MAX_OCA_RANGE,
|
ProposedFlight(
|
||||||
EscortType.AirToAir),
|
FlightType.ESCORT, 2, self.MAX_OCA_RANGE, EscortType.AirToAir
|
||||||
ProposedFlight(FlightType.SEAD, 2, self.MAX_OCA_RANGE,
|
),
|
||||||
EscortType.Sead),
|
ProposedFlight(
|
||||||
])
|
FlightType.SEAD, 2, self.MAX_OCA_RANGE, EscortType.Sead
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
yield ProposedMission(target, flights)
|
yield ProposedMission(target, flights)
|
||||||
|
|
||||||
# Plan strike missions.
|
# Plan strike missions.
|
||||||
for target in self.objective_finder.strike_targets():
|
for target in self.objective_finder.strike_targets():
|
||||||
yield ProposedMission(target, [
|
yield ProposedMission(
|
||||||
|
target,
|
||||||
|
[
|
||||||
ProposedFlight(FlightType.STRIKE, 2, self.MAX_STRIKE_RANGE),
|
ProposedFlight(FlightType.STRIKE, 2, self.MAX_STRIKE_RANGE),
|
||||||
# TODO: Max escort range.
|
# TODO: Max escort range.
|
||||||
ProposedFlight(FlightType.ESCORT, 2, self.MAX_STRIKE_RANGE,
|
ProposedFlight(
|
||||||
EscortType.AirToAir),
|
FlightType.ESCORT, 2, self.MAX_STRIKE_RANGE, EscortType.AirToAir
|
||||||
ProposedFlight(FlightType.SEAD, 2, self.MAX_STRIKE_RANGE,
|
),
|
||||||
EscortType.Sead),
|
ProposedFlight(
|
||||||
])
|
FlightType.SEAD, 2, self.MAX_STRIKE_RANGE, EscortType.Sead
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def plan_missions(self) -> None:
|
def plan_missions(self) -> None:
|
||||||
"""Identifies and plans mission for the turn."""
|
"""Identifies and plans mission for the turn."""
|
||||||
@ -628,19 +687,23 @@ class CoalitionMissionPlanner:
|
|||||||
for cp in self.objective_finder.friendly_control_points():
|
for cp in self.objective_finder.friendly_control_points():
|
||||||
inventory = self.game.aircraft_inventory.for_control_point(cp)
|
inventory = self.game.aircraft_inventory.for_control_point(cp)
|
||||||
for aircraft, available in inventory.all_aircraft:
|
for aircraft, available in inventory.all_aircraft:
|
||||||
self.message("Unused aircraft",
|
self.message("Unused aircraft", f"{available} {aircraft.id} from {cp}")
|
||||||
f"{available} {aircraft.id} from {cp}")
|
|
||||||
|
|
||||||
def plan_flight(self, mission: ProposedMission, flight: ProposedFlight,
|
def plan_flight(
|
||||||
builder: PackageBuilder, missing_types: Set[FlightType],
|
self,
|
||||||
for_reserves: bool) -> None:
|
mission: ProposedMission,
|
||||||
|
flight: ProposedFlight,
|
||||||
|
builder: PackageBuilder,
|
||||||
|
missing_types: Set[FlightType],
|
||||||
|
for_reserves: bool,
|
||||||
|
) -> None:
|
||||||
if not builder.plan_flight(flight):
|
if not builder.plan_flight(flight):
|
||||||
missing_types.add(flight.task)
|
missing_types.add(flight.task)
|
||||||
purchase_order = AircraftProcurementRequest(
|
purchase_order = AircraftProcurementRequest(
|
||||||
near=mission.location,
|
near=mission.location,
|
||||||
range=flight.max_distance,
|
range=flight.max_distance,
|
||||||
task_capability=flight.task,
|
task_capability=flight.task,
|
||||||
number=flight.num_aircraft
|
number=flight.num_aircraft,
|
||||||
)
|
)
|
||||||
if for_reserves:
|
if for_reserves:
|
||||||
# Reserves are planned for critical missions, so prioritize
|
# Reserves are planned for critical missions, so prioritize
|
||||||
@ -650,26 +713,28 @@ class CoalitionMissionPlanner:
|
|||||||
self.procurement_requests.append(purchase_order)
|
self.procurement_requests.append(purchase_order)
|
||||||
|
|
||||||
def scrub_mission_missing_aircraft(
|
def scrub_mission_missing_aircraft(
|
||||||
self, mission: ProposedMission, builder: PackageBuilder,
|
self,
|
||||||
|
mission: ProposedMission,
|
||||||
|
builder: PackageBuilder,
|
||||||
missing_types: Set[FlightType],
|
missing_types: Set[FlightType],
|
||||||
not_attempted: Iterable[ProposedFlight],
|
not_attempted: Iterable[ProposedFlight],
|
||||||
reserves: bool) -> None:
|
reserves: bool,
|
||||||
|
) -> None:
|
||||||
# Try to plan the rest of the mission just so we can count the missing
|
# Try to plan the rest of the mission just so we can count the missing
|
||||||
# types to buy.
|
# types to buy.
|
||||||
for flight in not_attempted:
|
for flight in not_attempted:
|
||||||
self.plan_flight(mission, flight, builder, missing_types, reserves)
|
self.plan_flight(mission, flight, builder, missing_types, reserves)
|
||||||
|
|
||||||
missing_types_str = ", ".join(
|
missing_types_str = ", ".join(sorted([t.name for t in missing_types]))
|
||||||
sorted([t.name for t in missing_types]))
|
|
||||||
builder.release_planned_aircraft()
|
builder.release_planned_aircraft()
|
||||||
desc = "reserve aircraft" if reserves else "aircraft"
|
desc = "reserve aircraft" if reserves else "aircraft"
|
||||||
self.message(
|
self.message(
|
||||||
"Insufficient aircraft",
|
"Insufficient aircraft",
|
||||||
f"Not enough {desc} in range for {mission.location.name} "
|
f"Not enough {desc} in range for {mission.location.name} "
|
||||||
f"capable of: {missing_types_str}")
|
f"capable of: {missing_types_str}",
|
||||||
|
)
|
||||||
|
|
||||||
def check_needed_escorts(
|
def check_needed_escorts(self, builder: PackageBuilder) -> Dict[EscortType, bool]:
|
||||||
self, builder: PackageBuilder) -> Dict[EscortType, bool]:
|
|
||||||
threats = defaultdict(bool)
|
threats = defaultdict(bool)
|
||||||
for flight in builder.package.flights:
|
for flight in builder.package.flights:
|
||||||
if self.threat_zones.threatened_by_aircraft(flight):
|
if self.threat_zones.threatened_by_aircraft(flight):
|
||||||
@ -678,8 +743,7 @@ class CoalitionMissionPlanner:
|
|||||||
threats[EscortType.Sead] = True
|
threats[EscortType.Sead] = True
|
||||||
return threats
|
return threats
|
||||||
|
|
||||||
def plan_mission(self, mission: ProposedMission,
|
def plan_mission(self, mission: ProposedMission, reserves: bool = False) -> None:
|
||||||
reserves: bool = False) -> None:
|
|
||||||
"""Allocates aircraft for a proposed mission and adds it to the ATO."""
|
"""Allocates aircraft for a proposed mission and adds it to the ATO."""
|
||||||
|
|
||||||
if self.is_player:
|
if self.is_player:
|
||||||
@ -693,7 +757,7 @@ class CoalitionMissionPlanner:
|
|||||||
self.game.aircraft_inventory,
|
self.game.aircraft_inventory,
|
||||||
self.is_player,
|
self.is_player,
|
||||||
package_country,
|
package_country,
|
||||||
self.game.settings.default_start_type
|
self.game.settings.default_start_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Attempt to plan all the main elements of the mission first. Escorts
|
# Attempt to plan all the main elements of the mission first. Escorts
|
||||||
@ -707,12 +771,12 @@ class CoalitionMissionPlanner:
|
|||||||
# If the package does not need escorts they may be pruned.
|
# If the package does not need escorts they may be pruned.
|
||||||
escorts.append(proposed_flight)
|
escorts.append(proposed_flight)
|
||||||
continue
|
continue
|
||||||
self.plan_flight(mission, proposed_flight, builder, missing_types,
|
self.plan_flight(mission, proposed_flight, builder, missing_types, reserves)
|
||||||
reserves)
|
|
||||||
|
|
||||||
if missing_types:
|
if missing_types:
|
||||||
self.scrub_mission_missing_aircraft(mission, builder, missing_types,
|
self.scrub_mission_missing_aircraft(
|
||||||
escorts, reserves)
|
mission, builder, missing_types, escorts, reserves
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Create flight plans for the main flights of the package so we can
|
# Create flight plans for the main flights of the package so we can
|
||||||
@ -721,8 +785,9 @@ class CoalitionMissionPlanner:
|
|||||||
# flights that will rendezvous with their package will be affected by
|
# flights that will rendezvous with their package will be affected by
|
||||||
# the other flights in the package. Escorts will not be able to
|
# the other flights in the package. Escorts will not be able to
|
||||||
# contribute to this.
|
# contribute to this.
|
||||||
flight_plan_builder = FlightPlanBuilder(self.game, builder.package,
|
flight_plan_builder = FlightPlanBuilder(
|
||||||
self.is_player)
|
self.game, builder.package, self.is_player
|
||||||
|
)
|
||||||
for flight in builder.package.flights:
|
for flight in builder.package.flights:
|
||||||
flight_plan_builder.populate_flight_plan(flight)
|
flight_plan_builder.populate_flight_plan(flight)
|
||||||
|
|
||||||
@ -732,14 +797,14 @@ class CoalitionMissionPlanner:
|
|||||||
# impossible.
|
# impossible.
|
||||||
assert escort.escort_type is not None
|
assert escort.escort_type is not None
|
||||||
if needed_escorts[escort.escort_type]:
|
if needed_escorts[escort.escort_type]:
|
||||||
self.plan_flight(mission, escort, builder, missing_types,
|
self.plan_flight(mission, escort, builder, missing_types, reserves)
|
||||||
reserves)
|
|
||||||
|
|
||||||
# Check again for unavailable aircraft. If the escort was required and
|
# Check again for unavailable aircraft. If the escort was required and
|
||||||
# none were found, scrub the mission.
|
# none were found, scrub the mission.
|
||||||
if missing_types:
|
if missing_types:
|
||||||
self.scrub_mission_missing_aircraft(mission, builder, missing_types,
|
self.scrub_mission_missing_aircraft(
|
||||||
escorts, reserves)
|
mission, builder, missing_types, escorts, reserves
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if reserves:
|
if reserves:
|
||||||
@ -756,8 +821,9 @@ class CoalitionMissionPlanner:
|
|||||||
self.ato.add_package(package)
|
self.ato.add_package(package)
|
||||||
|
|
||||||
def stagger_missions(self) -> None:
|
def stagger_missions(self) -> None:
|
||||||
def start_time_generator(count: int, earliest: int, latest: int,
|
def start_time_generator(
|
||||||
margin: int) -> Iterator[timedelta]:
|
count: int, earliest: int, latest: int, margin: int
|
||||||
|
) -> Iterator[timedelta]:
|
||||||
interval = (latest - earliest) // count
|
interval = (latest - earliest) // count
|
||||||
for time in range(earliest, latest, interval):
|
for time in range(earliest, latest, interval):
|
||||||
error = random.randint(-margin, margin)
|
error = random.randint(-margin, margin)
|
||||||
@ -768,17 +834,13 @@ class CoalitionMissionPlanner:
|
|||||||
FlightType.TARCAP,
|
FlightType.TARCAP,
|
||||||
}
|
}
|
||||||
|
|
||||||
previous_cap_end_time: Dict[MissionTarget, timedelta] = defaultdict(
|
previous_cap_end_time: Dict[MissionTarget, timedelta] = defaultdict(timedelta)
|
||||||
timedelta
|
non_dca_packages = [
|
||||||
)
|
p for p in self.ato.packages if p.primary_task not in dca_types
|
||||||
non_dca_packages = [p for p in self.ato.packages if
|
]
|
||||||
p.primary_task not in dca_types]
|
|
||||||
|
|
||||||
start_time = start_time_generator(
|
start_time = start_time_generator(
|
||||||
count=len(non_dca_packages),
|
count=len(non_dca_packages), earliest=5, latest=90, margin=5
|
||||||
earliest=5,
|
|
||||||
latest=90,
|
|
||||||
margin=5
|
|
||||||
)
|
)
|
||||||
for package in self.ato.packages:
|
for package in self.ato.packages:
|
||||||
tot = TotEstimator(package).earliest_tot()
|
tot = TotEstimator(package).earliest_tot()
|
||||||
@ -795,8 +857,7 @@ class CoalitionMissionPlanner:
|
|||||||
departure_time = package.mission_departure_time
|
departure_time = package.mission_departure_time
|
||||||
# Should be impossible for CAPs
|
# Should be impossible for CAPs
|
||||||
if departure_time is None:
|
if departure_time is None:
|
||||||
logging.error(
|
logging.error(f"Could not determine mission end time for {package}")
|
||||||
f"Could not determine mission end time for {package}")
|
|
||||||
continue
|
continue
|
||||||
previous_cap_end_time[package.target] = departure_time
|
previous_cap_end_time[package.target] = departure_time
|
||||||
else:
|
else:
|
||||||
@ -815,8 +876,6 @@ class CoalitionMissionPlanner:
|
|||||||
message to the info panel.
|
message to the info panel.
|
||||||
"""
|
"""
|
||||||
if self.is_player:
|
if self.is_player:
|
||||||
self.game.informations.append(
|
self.game.informations.append(Information(title, text, self.game.turn))
|
||||||
Information(title, text, self.game.turn)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
logging.info(f"{title}: {text}")
|
logging.info(f"{title}: {text}")
|
||||||
|
|||||||
@ -370,11 +370,7 @@ TRANSPORT_CAPABLE = [
|
|||||||
UH_1H,
|
UH_1H,
|
||||||
]
|
]
|
||||||
|
|
||||||
DRONES = [
|
DRONES = [MQ_9_Reaper, RQ_1A_Predator, WingLoong_I]
|
||||||
MQ_9_Reaper,
|
|
||||||
RQ_1A_Predator,
|
|
||||||
WingLoong_I
|
|
||||||
]
|
|
||||||
|
|
||||||
AEWC_CAPABLE = [
|
AEWC_CAPABLE = [
|
||||||
E_3A,
|
E_3A,
|
||||||
|
|||||||
@ -12,8 +12,9 @@ if TYPE_CHECKING:
|
|||||||
class ClosestAirfields:
|
class ClosestAirfields:
|
||||||
"""Precalculates which control points are closes to the given target."""
|
"""Precalculates which control points are closes to the given target."""
|
||||||
|
|
||||||
def __init__(self, target: MissionTarget,
|
def __init__(
|
||||||
all_control_points: List[ControlPoint]) -> None:
|
self, target: MissionTarget, all_control_points: List[ControlPoint]
|
||||||
|
) -> None:
|
||||||
self.target = target
|
self.target = target
|
||||||
# This cache is configured once on load, so it's important that it is
|
# This cache is configured once on load, so it's important that it is
|
||||||
# complete and deterministic to avoid different behaviors across loads.
|
# complete and deterministic to avoid different behaviors across loads.
|
||||||
@ -52,9 +53,7 @@ class ObjectiveDistanceCache:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def get_closest_airfields(cls, location: MissionTarget) -> ClosestAirfields:
|
def get_closest_airfields(cls, location: MissionTarget) -> ClosestAirfields:
|
||||||
if cls.theater is None:
|
if cls.theater is None:
|
||||||
raise RuntimeError(
|
raise RuntimeError("Call ObjectiveDistanceCache.set_theater before using")
|
||||||
"Call ObjectiveDistanceCache.set_theater before using"
|
|
||||||
)
|
|
||||||
|
|
||||||
if location.name not in cls.closest_airfields:
|
if location.name not in cls.closest_airfields:
|
||||||
cls.closest_airfields[location.name] = ClosestAirfields(
|
cls.closest_airfields[location.name] = ClosestAirfields(
|
||||||
|
|||||||
@ -28,6 +28,7 @@ class FlightType(Enum):
|
|||||||
each flight and thus a part of the ATO, so changing these values will break
|
each flight and thus a part of the ATO, so changing these values will break
|
||||||
save compat.
|
save compat.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
TARCAP = "TARCAP"
|
TARCAP = "TARCAP"
|
||||||
BARCAP = "BARCAP"
|
BARCAP = "BARCAP"
|
||||||
CAS = "CAS"
|
CAS = "CAS"
|
||||||
@ -77,9 +78,13 @@ class FlightWaypointType(Enum):
|
|||||||
|
|
||||||
|
|
||||||
class FlightWaypoint:
|
class FlightWaypoint:
|
||||||
|
def __init__(
|
||||||
def __init__(self, waypoint_type: FlightWaypointType, x: float, y: float,
|
self,
|
||||||
alt: Distance = meters(0)) -> None:
|
waypoint_type: FlightWaypointType,
|
||||||
|
x: float,
|
||||||
|
y: float,
|
||||||
|
alt: Distance = meters(0),
|
||||||
|
) -> None:
|
||||||
"""Creates a flight waypoint.
|
"""Creates a flight waypoint.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -117,10 +122,13 @@ class FlightWaypoint:
|
|||||||
return Point(self.x, self.y)
|
return Point(self.x, self.y)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_pydcs(cls, point: MovingPoint,
|
def from_pydcs(cls, point: MovingPoint, from_cp: ControlPoint) -> "FlightWaypoint":
|
||||||
from_cp: ControlPoint) -> "FlightWaypoint":
|
waypoint = FlightWaypoint(
|
||||||
waypoint = FlightWaypoint(FlightWaypointType.NAV, point.position.x,
|
FlightWaypointType.NAV,
|
||||||
point.position.y, meters(point.alt))
|
point.position.x,
|
||||||
|
point.position.y,
|
||||||
|
meters(point.alt),
|
||||||
|
)
|
||||||
waypoint.alt_type = point.alt_type
|
waypoint.alt_type = point.alt_type
|
||||||
# Other actions exist... but none of them *should* be the first
|
# Other actions exist... but none of them *should* be the first
|
||||||
# waypoint for a flight.
|
# waypoint for a flight.
|
||||||
@ -144,12 +152,19 @@ class FlightWaypoint:
|
|||||||
|
|
||||||
|
|
||||||
class Flight:
|
class Flight:
|
||||||
|
def __init__(
|
||||||
def __init__(self, package: Package, country: str, unit_type: Type[FlyingType],
|
self,
|
||||||
count: int, flight_type: FlightType, start_type: str,
|
package: Package,
|
||||||
departure: ControlPoint, arrival: ControlPoint,
|
country: str,
|
||||||
|
unit_type: Type[FlyingType],
|
||||||
|
count: int,
|
||||||
|
flight_type: FlightType,
|
||||||
|
start_type: str,
|
||||||
|
departure: ControlPoint,
|
||||||
|
arrival: ControlPoint,
|
||||||
divert: Optional[ControlPoint],
|
divert: Optional[ControlPoint],
|
||||||
custom_name: Optional[str] = None) -> None:
|
custom_name: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
self.package = package
|
self.package = package
|
||||||
self.country = country
|
self.country = country
|
||||||
self.unit_type = unit_type
|
self.unit_type = unit_type
|
||||||
@ -170,10 +185,9 @@ class Flight:
|
|||||||
# FlightPlanBuilder, but an empty flight plan the flight begins with an
|
# FlightPlanBuilder, but an empty flight plan the flight begins with an
|
||||||
# empty flight plan.
|
# empty flight plan.
|
||||||
from gen.flights.flightplan import CustomFlightPlan
|
from gen.flights.flightplan import CustomFlightPlan
|
||||||
|
|
||||||
self.flight_plan: FlightPlan = CustomFlightPlan(
|
self.flight_plan: FlightPlan = CustomFlightPlan(
|
||||||
package=package,
|
package=package, flight=self, custom_waypoints=[]
|
||||||
flight=self,
|
|
||||||
custom_waypoints=[]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -191,7 +205,7 @@ class Flight:
|
|||||||
return f"[{self.flight_type}] {self.count} x {name}"
|
return f"[{self.flight_type}] {self.count} x {name}"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
name = db.unit_get_expanded_info(self.country, self.unit_type, 'name')
|
name = db.unit_get_expanded_info(self.country, self.unit_type, "name")
|
||||||
if self.custom_name:
|
if self.custom_name:
|
||||||
return f"{self.custom_name} {self.count} x {name}"
|
return f"{self.custom_name} {self.count} x {name}"
|
||||||
return f"[{self.flight_type}] {self.count} x {name}"
|
return f"[{self.flight_type}] {self.count} x {name}"
|
||||||
|
|||||||
@ -15,12 +15,7 @@ from datetime import timedelta
|
|||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from typing import Iterator, List, Optional, Set, TYPE_CHECKING, Tuple
|
from typing import Iterator, List, Optional, Set, TYPE_CHECKING, Tuple
|
||||||
|
|
||||||
from dcs.planes import (
|
from dcs.planes import E_3A, E_2C, A_50, KJ_2000
|
||||||
E_3A,
|
|
||||||
E_2C,
|
|
||||||
A_50,
|
|
||||||
KJ_2000
|
|
||||||
)
|
|
||||||
|
|
||||||
from dcs.mapping import Point
|
from dcs.mapping import Point
|
||||||
from dcs.unit import Unit
|
from dcs.unit import Unit
|
||||||
@ -93,8 +88,9 @@ class FlightPlan:
|
|||||||
|
|
||||||
return zip(self.waypoints[:last_index], self.waypoints[1:last_index])
|
return zip(self.waypoints[:last_index], self.waypoints[1:last_index])
|
||||||
|
|
||||||
def best_speed_between_waypoints(self, a: FlightWaypoint,
|
def best_speed_between_waypoints(
|
||||||
b: FlightWaypoint) -> Speed:
|
self, a: FlightWaypoint, b: FlightWaypoint
|
||||||
|
) -> Speed:
|
||||||
"""Desired ground speed between points a and b."""
|
"""Desired ground speed between points a and b."""
|
||||||
factor = 1.0
|
factor = 1.0
|
||||||
if b.waypoint_type == FlightWaypointType.ASCEND_POINT:
|
if b.waypoint_type == FlightWaypointType.ASCEND_POINT:
|
||||||
@ -115,8 +111,7 @@ class FlightPlan:
|
|||||||
# near 2000 ft MSL.
|
# near 2000 ft MSL.
|
||||||
return GroundSpeed.for_flight(self.flight, min(a.alt, b.alt)) * factor
|
return GroundSpeed.for_flight(self.flight, min(a.alt, b.alt)) * factor
|
||||||
|
|
||||||
def speed_between_waypoints(self, a: FlightWaypoint,
|
def speed_between_waypoints(self, a: FlightWaypoint, b: FlightWaypoint) -> Speed:
|
||||||
b: FlightWaypoint) -> Speed:
|
|
||||||
return self.best_speed_between_waypoints(a, b)
|
return self.best_speed_between_waypoints(a, b)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -131,8 +126,7 @@ class FlightPlan:
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def bingo_fuel(self) -> int:
|
def bingo_fuel(self) -> int:
|
||||||
"""Bingo fuel value for the FlightPlan
|
"""Bingo fuel value for the FlightPlan"""
|
||||||
"""
|
|
||||||
distance_to_arrival = self.max_distance_from(self.flight.arrival)
|
distance_to_arrival = self.max_distance_from(self.flight.arrival)
|
||||||
|
|
||||||
bingo = 1000.0 # Minimum Emergency Fuel
|
bingo = 1000.0 # Minimum Emergency Fuel
|
||||||
@ -149,8 +143,7 @@ class FlightPlan:
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def joker_fuel(self) -> int:
|
def joker_fuel(self) -> int:
|
||||||
"""Joker fuel value for the FlightPlan
|
"""Joker fuel value for the FlightPlan"""
|
||||||
"""
|
|
||||||
return self.bingo_fuel + 1000
|
return self.bingo_fuel + 1000
|
||||||
|
|
||||||
def max_distance_from(self, cp: ControlPoint) -> Distance:
|
def max_distance_from(self, cp: ControlPoint) -> Distance:
|
||||||
@ -159,8 +152,9 @@ class FlightPlan:
|
|||||||
"""
|
"""
|
||||||
if not self.waypoints:
|
if not self.waypoints:
|
||||||
return meters(0)
|
return meters(0)
|
||||||
return max([meters(cp.position.distance_to_point(w.position)) for w in
|
return max(
|
||||||
self.waypoints])
|
[meters(cp.position.distance_to_point(w.position)) for w in self.waypoints]
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tot_offset(self) -> timedelta:
|
def tot_offset(self) -> timedelta:
|
||||||
@ -171,30 +165,30 @@ class FlightPlan:
|
|||||||
"""
|
"""
|
||||||
return timedelta()
|
return timedelta()
|
||||||
|
|
||||||
def _travel_time_to_waypoint(
|
def _travel_time_to_waypoint(self, destination: FlightWaypoint) -> timedelta:
|
||||||
self, destination: FlightWaypoint) -> timedelta:
|
|
||||||
total = timedelta()
|
total = timedelta()
|
||||||
|
|
||||||
if destination not in self.waypoints:
|
if destination not in self.waypoints:
|
||||||
raise PlanningError(
|
raise PlanningError(
|
||||||
f"Did not find destination waypoint {destination} in "
|
f"Did not find destination waypoint {destination} in "
|
||||||
f"waypoints for {self.flight}")
|
f"waypoints for {self.flight}"
|
||||||
|
)
|
||||||
|
|
||||||
for previous_waypoint, waypoint in self.edges(until=destination):
|
for previous_waypoint, waypoint in self.edges(until=destination):
|
||||||
total += self.travel_time_between_waypoints(previous_waypoint,
|
total += self.travel_time_between_waypoints(previous_waypoint, waypoint)
|
||||||
waypoint)
|
|
||||||
return total
|
return total
|
||||||
|
|
||||||
def travel_time_between_waypoints(self, a: FlightWaypoint,
|
def travel_time_between_waypoints(
|
||||||
b: FlightWaypoint) -> timedelta:
|
self, a: FlightWaypoint, b: FlightWaypoint
|
||||||
return TravelTime.between_points(a.position, b.position,
|
) -> timedelta:
|
||||||
self.speed_between_waypoints(a, b))
|
return TravelTime.between_points(
|
||||||
|
a.position, b.position, self.speed_between_waypoints(a, b)
|
||||||
|
)
|
||||||
|
|
||||||
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def depart_time_for_waypoint(
|
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||||
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def request_escort_at(self) -> Optional[FlightWaypoint]:
|
def request_escort_at(self) -> Optional[FlightWaypoint]:
|
||||||
@ -219,8 +213,7 @@ class FlightPlan:
|
|||||||
if takeoff_time is None:
|
if takeoff_time is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
start_time = (takeoff_time - self.estimate_startup() -
|
start_time = takeoff_time - self.estimate_startup() - self.estimate_ground_ops()
|
||||||
self.estimate_ground_ops())
|
|
||||||
|
|
||||||
# In case FP math has given us some barely below zero time, round to
|
# In case FP math has given us some barely below zero time, round to
|
||||||
# zero.
|
# zero.
|
||||||
@ -276,14 +269,14 @@ class LoiterFlightPlan(FlightPlan):
|
|||||||
def push_time(self) -> timedelta:
|
def push_time(self) -> timedelta:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def depart_time_for_waypoint(
|
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||||
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
|
||||||
if waypoint == self.hold:
|
if waypoint == self.hold:
|
||||||
return self.push_time
|
return self.push_time
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def travel_time_between_waypoints(self, a: FlightWaypoint,
|
def travel_time_between_waypoints(
|
||||||
b: FlightWaypoint) -> timedelta:
|
self, a: FlightWaypoint, b: FlightWaypoint
|
||||||
|
) -> timedelta:
|
||||||
travel_time = super().travel_time_between_waypoints(a, b)
|
travel_time = super().travel_time_between_waypoints(a, b)
|
||||||
if a != self.hold:
|
if a != self.hold:
|
||||||
return travel_time
|
return travel_time
|
||||||
@ -328,12 +321,12 @@ class FormationFlightPlan(LoiterFlightPlan):
|
|||||||
speeds = []
|
speeds = []
|
||||||
for previous_waypoint, waypoint in self.edges():
|
for previous_waypoint, waypoint in self.edges():
|
||||||
if waypoint in self.package_speed_waypoints:
|
if waypoint in self.package_speed_waypoints:
|
||||||
speeds.append(self.best_speed_between_waypoints(
|
speeds.append(
|
||||||
previous_waypoint, waypoint))
|
self.best_speed_between_waypoints(previous_waypoint, waypoint)
|
||||||
|
)
|
||||||
return min(speeds)
|
return min(speeds)
|
||||||
|
|
||||||
def speed_between_waypoints(self, a: FlightWaypoint,
|
def speed_between_waypoints(self, a: FlightWaypoint, b: FlightWaypoint) -> Speed:
|
||||||
b: FlightWaypoint) -> Speed:
|
|
||||||
if b in self.package_speed_waypoints:
|
if b in self.package_speed_waypoints:
|
||||||
# Should be impossible, as any package with at least one
|
# Should be impossible, as any package with at least one
|
||||||
# FormationFlightPlan flight needs a formation speed.
|
# FormationFlightPlan flight needs a formation speed.
|
||||||
@ -366,7 +359,7 @@ class FormationFlightPlan(LoiterFlightPlan):
|
|||||||
return self.join_time - TravelTime.between_points(
|
return self.join_time - TravelTime.between_points(
|
||||||
self.hold.position,
|
self.hold.position,
|
||||||
self.join.position,
|
self.join.position,
|
||||||
GroundSpeed.for_flight(self.flight, self.hold.alt)
|
GroundSpeed.for_flight(self.flight, self.hold.alt),
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -406,8 +399,7 @@ class PatrollingFlightPlan(FlightPlan):
|
|||||||
return self.patrol_start_time
|
return self.patrol_start_time
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def depart_time_for_waypoint(
|
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||||
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
|
||||||
if waypoint == self.patrol_end:
|
if waypoint == self.patrol_end:
|
||||||
return self.patrol_end_time
|
return self.patrol_end_time
|
||||||
return None
|
return None
|
||||||
@ -497,8 +489,7 @@ class TarCapFlightPlan(PatrollingFlightPlan):
|
|||||||
def tot_offset(self) -> timedelta:
|
def tot_offset(self) -> timedelta:
|
||||||
return -self.lead_time
|
return -self.lead_time
|
||||||
|
|
||||||
def depart_time_for_waypoint(
|
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||||
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
|
||||||
if waypoint == self.patrol_end:
|
if waypoint == self.patrol_end:
|
||||||
return self.patrol_end_time
|
return self.patrol_end_time
|
||||||
return super().depart_time_for_waypoint(waypoint)
|
return super().depart_time_for_waypoint(waypoint)
|
||||||
@ -554,8 +545,7 @@ class StrikeFlightPlan(FormationFlightPlan):
|
|||||||
self.split,
|
self.split,
|
||||||
} | set(self.targets)
|
} | set(self.targets)
|
||||||
|
|
||||||
def speed_between_waypoints(self, a: FlightWaypoint,
|
def speed_between_waypoints(self, a: FlightWaypoint, b: FlightWaypoint) -> Speed:
|
||||||
b: FlightWaypoint) -> Speed:
|
|
||||||
# FlightWaypoint is only comparable by identity, so adding
|
# FlightWaypoint is only comparable by identity, so adding
|
||||||
# target_area_waypoint to package_speed_waypoints is useless.
|
# target_area_waypoint to package_speed_waypoints is useless.
|
||||||
if b.waypoint_type == FlightWaypointType.TARGET_GROUP_LOC:
|
if b.waypoint_type == FlightWaypointType.TARGET_GROUP_LOC:
|
||||||
@ -571,10 +561,12 @@ class StrikeFlightPlan(FormationFlightPlan):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def target_area_waypoint(self) -> FlightWaypoint:
|
def target_area_waypoint(self) -> FlightWaypoint:
|
||||||
return FlightWaypoint(FlightWaypointType.TARGET_GROUP_LOC,
|
return FlightWaypoint(
|
||||||
|
FlightWaypointType.TARGET_GROUP_LOC,
|
||||||
self.package.target.position.x,
|
self.package.target.position.x,
|
||||||
self.package.target.position.y,
|
self.package.target.position.y,
|
||||||
meters(0))
|
meters(0),
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def travel_time_to_target(self) -> timedelta:
|
def travel_time_to_target(self) -> timedelta:
|
||||||
@ -588,14 +580,15 @@ class StrikeFlightPlan(FormationFlightPlan):
|
|||||||
# package we need to use the travel time to the same position as
|
# package we need to use the travel time to the same position as
|
||||||
# the others.
|
# the others.
|
||||||
total += self.travel_time_between_waypoints(
|
total += self.travel_time_between_waypoints(
|
||||||
previous_waypoint, self.target_area_waypoint)
|
previous_waypoint, self.target_area_waypoint
|
||||||
|
)
|
||||||
break
|
break
|
||||||
total += self.travel_time_between_waypoints(previous_waypoint,
|
total += self.travel_time_between_waypoints(previous_waypoint, waypoint)
|
||||||
waypoint)
|
|
||||||
else:
|
else:
|
||||||
raise PlanningError(
|
raise PlanningError(
|
||||||
f"Did not find destination waypoint {destination} in "
|
f"Did not find destination waypoint {destination} in "
|
||||||
f"waypoints for {self.flight}")
|
f"waypoints for {self.flight}"
|
||||||
|
)
|
||||||
return total
|
return total
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -604,28 +597,28 @@ class StrikeFlightPlan(FormationFlightPlan):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def join_time(self) -> timedelta:
|
def join_time(self) -> timedelta:
|
||||||
travel_time = self.travel_time_between_waypoints(
|
travel_time = self.travel_time_between_waypoints(self.join, self.ingress)
|
||||||
self.join, self.ingress)
|
|
||||||
return self.ingress_time - travel_time
|
return self.ingress_time - travel_time
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def split_time(self) -> timedelta:
|
def split_time(self) -> timedelta:
|
||||||
travel_time = self.travel_time_between_waypoints(
|
travel_time = self.travel_time_between_waypoints(self.egress, self.split)
|
||||||
self.egress, self.split)
|
|
||||||
return self.egress_time + travel_time
|
return self.egress_time + travel_time
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ingress_time(self) -> timedelta:
|
def ingress_time(self) -> timedelta:
|
||||||
tot = self.package.time_over_target
|
tot = self.package.time_over_target
|
||||||
travel_time = self.travel_time_between_waypoints(
|
travel_time = self.travel_time_between_waypoints(
|
||||||
self.ingress, self.target_area_waypoint)
|
self.ingress, self.target_area_waypoint
|
||||||
|
)
|
||||||
return tot - travel_time
|
return tot - travel_time
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def egress_time(self) -> timedelta:
|
def egress_time(self) -> timedelta:
|
||||||
tot = self.package.time_over_target
|
tot = self.package.time_over_target
|
||||||
travel_time = self.travel_time_between_waypoints(
|
travel_time = self.travel_time_between_waypoints(
|
||||||
self.target_area_waypoint, self.egress)
|
self.target_area_waypoint, self.egress
|
||||||
|
)
|
||||||
return tot + travel_time
|
return tot + travel_time
|
||||||
|
|
||||||
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||||
@ -671,7 +664,8 @@ class SweepFlightPlan(LoiterFlightPlan):
|
|||||||
@property
|
@property
|
||||||
def sweep_start_time(self) -> timedelta:
|
def sweep_start_time(self) -> timedelta:
|
||||||
travel_time = self.travel_time_between_waypoints(
|
travel_time = self.travel_time_between_waypoints(
|
||||||
self.sweep_start, self.sweep_end)
|
self.sweep_start, self.sweep_end
|
||||||
|
)
|
||||||
return self.sweep_end_time - travel_time
|
return self.sweep_end_time - travel_time
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -685,8 +679,7 @@ class SweepFlightPlan(LoiterFlightPlan):
|
|||||||
return self.sweep_end_time
|
return self.sweep_end_time
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def depart_time_for_waypoint(
|
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||||
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
|
||||||
if waypoint == self.hold:
|
if waypoint == self.hold:
|
||||||
return self.push_time
|
return self.push_time
|
||||||
return None
|
return None
|
||||||
@ -696,7 +689,7 @@ class SweepFlightPlan(LoiterFlightPlan):
|
|||||||
return self.sweep_end_time - TravelTime.between_points(
|
return self.sweep_end_time - TravelTime.between_points(
|
||||||
self.hold.position,
|
self.hold.position,
|
||||||
self.sweep_end.position,
|
self.sweep_end.position,
|
||||||
GroundSpeed.for_flight(self.flight, self.hold.alt)
|
GroundSpeed.for_flight(self.flight, self.hold.alt),
|
||||||
)
|
)
|
||||||
|
|
||||||
def mission_departure_time(self) -> timedelta:
|
def mission_departure_time(self) -> timedelta:
|
||||||
@ -763,8 +756,7 @@ class CustomFlightPlan(FlightPlan):
|
|||||||
return self.package.time_over_target
|
return self.package.time_over_target
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def depart_time_for_waypoint(
|
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||||
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -794,9 +786,11 @@ class FlightPlanBuilder:
|
|||||||
self.threat_zones = self.game.threat_zone_for(not self.is_player)
|
self.threat_zones = self.game.threat_zone_for(not self.is_player)
|
||||||
|
|
||||||
def populate_flight_plan(
|
def populate_flight_plan(
|
||||||
self, flight: Flight,
|
self,
|
||||||
|
flight: Flight,
|
||||||
# TODO: Custom targets should be an attribute of the flight.
|
# TODO: Custom targets should be an attribute of the flight.
|
||||||
custom_targets: Optional[List[Unit]] = None) -> None:
|
custom_targets: Optional[List[Unit]] = None,
|
||||||
|
) -> None:
|
||||||
"""Creates a default flight plan for the given mission."""
|
"""Creates a default flight plan for the given mission."""
|
||||||
if flight not in self.package.flights:
|
if flight not in self.package.flights:
|
||||||
raise RuntimeError("Flight must be a part of the package")
|
raise RuntimeError("Flight must be a part of the package")
|
||||||
@ -805,8 +799,8 @@ class FlightPlanBuilder:
|
|||||||
flight.flight_plan = self.generate_flight_plan(flight, custom_targets)
|
flight.flight_plan = self.generate_flight_plan(flight, custom_targets)
|
||||||
|
|
||||||
def generate_flight_plan(
|
def generate_flight_plan(
|
||||||
self, flight: Flight,
|
self, flight: Flight, custom_targets: Optional[List[Unit]]
|
||||||
custom_targets: Optional[List[Unit]]) -> FlightPlan:
|
) -> FlightPlan:
|
||||||
# TODO: Flesh out mission types.
|
# TODO: Flesh out mission types.
|
||||||
task = flight.flight_type
|
task = flight.flight_type
|
||||||
if task == FlightType.ANTISHIP:
|
if task == FlightType.ANTISHIP:
|
||||||
@ -835,8 +829,7 @@ class FlightPlanBuilder:
|
|||||||
return self.generate_tarcap(flight)
|
return self.generate_tarcap(flight)
|
||||||
elif task == FlightType.AEWC:
|
elif task == FlightType.AEWC:
|
||||||
return self.generate_aewc(flight)
|
return self.generate_aewc(flight)
|
||||||
raise PlanningError(
|
raise PlanningError(f"{task} flight plan generation not implemented")
|
||||||
f"{task} flight plan generation not implemented")
|
|
||||||
|
|
||||||
def regenerate_package_waypoints(self) -> None:
|
def regenerate_package_waypoints(self) -> None:
|
||||||
# The simple case is where the target is greater than the ingress
|
# The simple case is where the target is greater than the ingress
|
||||||
@ -873,6 +866,7 @@ class FlightPlanBuilder:
|
|||||||
# | | | |
|
# | | | |
|
||||||
# +--------------+ +---------------+
|
# +--------------+ +---------------+
|
||||||
from gen.ato import PackageWaypoints
|
from gen.ato import PackageWaypoints
|
||||||
|
|
||||||
target = self.package.target.position
|
target = self.package.target.position
|
||||||
|
|
||||||
join_point = self.preferred_join_point()
|
join_point = self.preferred_join_point()
|
||||||
@ -906,10 +900,9 @@ class FlightPlanBuilder:
|
|||||||
|
|
||||||
def legacy_package_waypoints_impl(self) -> None:
|
def legacy_package_waypoints_impl(self) -> None:
|
||||||
from gen.ato import PackageWaypoints
|
from gen.ato import PackageWaypoints
|
||||||
ingress_point = self._ingress_point(
|
|
||||||
self._target_heading_to_package_airfield())
|
ingress_point = self._ingress_point(self._target_heading_to_package_airfield())
|
||||||
egress_point = self._egress_point(
|
egress_point = self._egress_point(self._target_heading_to_package_airfield())
|
||||||
self._target_heading_to_package_airfield())
|
|
||||||
join_point = self._rendezvous_point(ingress_point)
|
join_point = self._rendezvous_point(ingress_point)
|
||||||
split_point = self._rendezvous_point(egress_point)
|
split_point = self._rendezvous_point(egress_point)
|
||||||
self.package.waypoints = PackageWaypoints(
|
self.package.waypoints = PackageWaypoints(
|
||||||
@ -921,7 +914,8 @@ class FlightPlanBuilder:
|
|||||||
|
|
||||||
def preferred_join_point(self) -> Optional[Point]:
|
def preferred_join_point(self) -> Optional[Point]:
|
||||||
path = self.game.navmesh_for(self.is_player).shortest_path(
|
path = self.game.navmesh_for(self.is_player).shortest_path(
|
||||||
self.package_airfield().position, self.package.target.position)
|
self.package_airfield().position, self.package.target.position
|
||||||
|
)
|
||||||
for point in reversed(path):
|
for point in reversed(path):
|
||||||
if not self.threat_zones.threatened(point):
|
if not self.threat_zones.threatened(point):
|
||||||
return point
|
return point
|
||||||
@ -961,9 +955,9 @@ class FlightPlanBuilder:
|
|||||||
|
|
||||||
targets.append(StrikeTarget(building.category, building))
|
targets.append(StrikeTarget(building.category, building))
|
||||||
|
|
||||||
return self.strike_flightplan(flight, location,
|
return self.strike_flightplan(
|
||||||
FlightWaypointType.INGRESS_STRIKE,
|
flight, location, FlightWaypointType.INGRESS_STRIKE, targets
|
||||||
targets)
|
)
|
||||||
|
|
||||||
def generate_aewc(self, flight: Flight) -> AwacsFlightPlan:
|
def generate_aewc(self, flight: Flight) -> AwacsFlightPlan:
|
||||||
"""Generate a AWACS flight at a given location.
|
"""Generate a AWACS flight at a given location.
|
||||||
@ -994,10 +988,12 @@ class FlightPlanBuilder:
|
|||||||
package=self.package,
|
package=self.package,
|
||||||
flight=flight,
|
flight=flight,
|
||||||
takeoff=builder.takeoff(flight.departure),
|
takeoff=builder.takeoff(flight.departure),
|
||||||
nav_to=builder.nav_path(flight.departure.position, start.position,
|
nav_to=builder.nav_path(
|
||||||
patrol_alt),
|
flight.departure.position, start.position, patrol_alt
|
||||||
nav_from=builder.nav_path(start.position, flight.arrival.position,
|
),
|
||||||
patrol_alt),
|
nav_from=builder.nav_path(
|
||||||
|
start.position, flight.arrival.position, patrol_alt
|
||||||
|
),
|
||||||
land=builder.land(flight.arrival),
|
land=builder.land(flight.arrival),
|
||||||
divert=builder.divert(flight.divert),
|
divert=builder.divert(flight.divert),
|
||||||
hold=start,
|
hold=start,
|
||||||
@ -1017,11 +1013,11 @@ class FlightPlanBuilder:
|
|||||||
|
|
||||||
targets: List[StrikeTarget] = []
|
targets: List[StrikeTarget] = []
|
||||||
for group in location.groups:
|
for group in location.groups:
|
||||||
targets.append(
|
targets.append(StrikeTarget(f"{group.name} at {location.name}", group))
|
||||||
StrikeTarget(f"{group.name} at {location.name}", group))
|
|
||||||
|
|
||||||
return self.strike_flightplan(flight, location,
|
return self.strike_flightplan(
|
||||||
FlightWaypointType.INGRESS_BAI, targets)
|
flight, location, FlightWaypointType.INGRESS_BAI, targets
|
||||||
|
)
|
||||||
|
|
||||||
def generate_anti_ship(self, flight: Flight) -> StrikeFlightPlan:
|
def generate_anti_ship(self, flight: Flight) -> StrikeFlightPlan:
|
||||||
"""Generates an anti-ship flight plan.
|
"""Generates an anti-ship flight plan.
|
||||||
@ -1043,11 +1039,11 @@ class FlightPlanBuilder:
|
|||||||
|
|
||||||
targets: List[StrikeTarget] = []
|
targets: List[StrikeTarget] = []
|
||||||
for group in location.groups:
|
for group in location.groups:
|
||||||
targets.append(
|
targets.append(StrikeTarget(f"{group.name} at {location.name}", group))
|
||||||
StrikeTarget(f"{group.name} at {location.name}", group))
|
|
||||||
|
|
||||||
return self.strike_flightplan(flight, location,
|
return self.strike_flightplan(
|
||||||
FlightWaypointType.INGRESS_BAI, targets)
|
flight, location, FlightWaypointType.INGRESS_BAI, targets
|
||||||
|
)
|
||||||
|
|
||||||
def generate_barcap(self, flight: Flight) -> BarCapFlightPlan:
|
def generate_barcap(self, flight: Flight) -> BarCapFlightPlan:
|
||||||
"""Generate a BARCAP flight at a given location.
|
"""Generate a BARCAP flight at a given location.
|
||||||
@ -1061,10 +1057,12 @@ class FlightPlanBuilder:
|
|||||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||||
|
|
||||||
start, end = self.racetrack_for_objective(location, barcap=True)
|
start, end = self.racetrack_for_objective(location, barcap=True)
|
||||||
patrol_alt = meters(random.randint(
|
patrol_alt = meters(
|
||||||
|
random.randint(
|
||||||
int(self.doctrine.min_patrol_altitude.meters),
|
int(self.doctrine.min_patrol_altitude.meters),
|
||||||
int(self.doctrine.max_patrol_altitude.meters)
|
int(self.doctrine.max_patrol_altitude.meters),
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
builder = WaypointBuilder(flight, self.game, self.is_player)
|
builder = WaypointBuilder(flight, self.game, self.is_player)
|
||||||
start, end = builder.race_track(start, end, patrol_alt)
|
start, end = builder.race_track(start, end, patrol_alt)
|
||||||
@ -1075,14 +1073,16 @@ class FlightPlanBuilder:
|
|||||||
patrol_duration=self.doctrine.cap_duration,
|
patrol_duration=self.doctrine.cap_duration,
|
||||||
engagement_distance=self.doctrine.cap_engagement_range,
|
engagement_distance=self.doctrine.cap_engagement_range,
|
||||||
takeoff=builder.takeoff(flight.departure),
|
takeoff=builder.takeoff(flight.departure),
|
||||||
nav_to=builder.nav_path(flight.departure.position, start.position,
|
nav_to=builder.nav_path(
|
||||||
patrol_alt),
|
flight.departure.position, start.position, patrol_alt
|
||||||
nav_from=builder.nav_path(end.position, flight.arrival.position,
|
),
|
||||||
patrol_alt),
|
nav_from=builder.nav_path(
|
||||||
|
end.position, flight.arrival.position, patrol_alt
|
||||||
|
),
|
||||||
patrol_start=start,
|
patrol_start=start,
|
||||||
patrol_end=end,
|
patrol_end=end,
|
||||||
land=builder.land(flight.arrival),
|
land=builder.land(flight.arrival),
|
||||||
divert=builder.divert(flight.divert)
|
divert=builder.divert(flight.divert),
|
||||||
)
|
)
|
||||||
|
|
||||||
def generate_sweep(self, flight: Flight) -> SweepFlightPlan:
|
def generate_sweep(self, flight: Flight) -> SweepFlightPlan:
|
||||||
@ -1095,12 +1095,10 @@ class FlightPlanBuilder:
|
|||||||
target = self.package.target.position
|
target = self.package.target.position
|
||||||
|
|
||||||
heading = self.package.waypoints.join.heading_between_point(target)
|
heading = self.package.waypoints.join.heading_between_point(target)
|
||||||
start = target.point_from_heading(heading,
|
start = target.point_from_heading(heading, -self.doctrine.sweep_distance.meters)
|
||||||
-self.doctrine.sweep_distance.meters)
|
|
||||||
|
|
||||||
builder = WaypointBuilder(flight, self.game, self.is_player)
|
builder = WaypointBuilder(flight, self.game, self.is_player)
|
||||||
start, end = builder.sweep(start, target,
|
start, end = builder.sweep(start, target, self.doctrine.ingress_altitude)
|
||||||
self.doctrine.ingress_altitude)
|
|
||||||
|
|
||||||
hold = builder.hold(self._hold_point(flight))
|
hold = builder.hold(self._hold_point(flight))
|
||||||
|
|
||||||
@ -1111,18 +1109,21 @@ class FlightPlanBuilder:
|
|||||||
takeoff=builder.takeoff(flight.departure),
|
takeoff=builder.takeoff(flight.departure),
|
||||||
hold=hold,
|
hold=hold,
|
||||||
hold_duration=timedelta(minutes=5),
|
hold_duration=timedelta(minutes=5),
|
||||||
nav_to=builder.nav_path(hold.position, start.position,
|
nav_to=builder.nav_path(
|
||||||
self.doctrine.ingress_altitude),
|
hold.position, start.position, self.doctrine.ingress_altitude
|
||||||
nav_from=builder.nav_path(end.position, flight.arrival.position,
|
),
|
||||||
self.doctrine.ingress_altitude),
|
nav_from=builder.nav_path(
|
||||||
|
end.position, flight.arrival.position, self.doctrine.ingress_altitude
|
||||||
|
),
|
||||||
sweep_start=start,
|
sweep_start=start,
|
||||||
sweep_end=end,
|
sweep_end=end,
|
||||||
land=builder.land(flight.arrival),
|
land=builder.land(flight.arrival),
|
||||||
divert=builder.divert(flight.divert)
|
divert=builder.divert(flight.divert),
|
||||||
)
|
)
|
||||||
|
|
||||||
def racetrack_for_objective(self, location: MissionTarget,
|
def racetrack_for_objective(
|
||||||
barcap: bool) -> Tuple[Point, Point]:
|
self, location: MissionTarget, barcap: bool
|
||||||
|
) -> Tuple[Point, Point]:
|
||||||
closest_cache = ObjectiveDistanceCache.get_closest_airfields(location)
|
closest_cache = ObjectiveDistanceCache.get_closest_airfields(location)
|
||||||
for airfield in closest_cache.operational_airfields:
|
for airfield in closest_cache.operational_airfields:
|
||||||
# If the mission is a BARCAP of an enemy airfield, find the *next*
|
# If the mission is a BARCAP of an enemy airfield, find the *next*
|
||||||
@ -1135,20 +1136,21 @@ class FlightPlanBuilder:
|
|||||||
else:
|
else:
|
||||||
raise PlanningError("Could not find any enemy airfields")
|
raise PlanningError("Could not find any enemy airfields")
|
||||||
|
|
||||||
heading = location.position.heading_between_point(
|
heading = location.position.heading_between_point(closest_airfield.position)
|
||||||
closest_airfield.position
|
|
||||||
)
|
|
||||||
|
|
||||||
position = ShapelyPoint(self.package.target.position.x,
|
position = ShapelyPoint(
|
||||||
self.package.target.position.y)
|
self.package.target.position.x, self.package.target.position.y
|
||||||
|
)
|
||||||
|
|
||||||
if barcap:
|
if barcap:
|
||||||
# BARCAPs should remain far enough back from the enemy that their
|
# BARCAPs should remain far enough back from the enemy that their
|
||||||
# commit range does not enter the enemy's threat zone. Include a 5nm
|
# commit range does not enter the enemy's threat zone. Include a 5nm
|
||||||
# buffer.
|
# buffer.
|
||||||
distance_to_no_fly = meters(
|
distance_to_no_fly = (
|
||||||
position.distance(self.threat_zones.all)
|
meters(position.distance(self.threat_zones.all))
|
||||||
) - self.doctrine.cap_engagement_range - nautical_miles(5)
|
- self.doctrine.cap_engagement_range
|
||||||
|
- nautical_miles(5)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Other race tracks (TARCAPs, currently) just try to keep some
|
# Other race tracks (TARCAPs, currently) just try to keep some
|
||||||
# distance from the nearest enemy airbase, but since they are by
|
# distance from the nearest enemy airbase, but since they are by
|
||||||
@ -1158,22 +1160,24 @@ class FlightPlanBuilder:
|
|||||||
distance_to_airfield = meters(
|
distance_to_airfield = meters(
|
||||||
closest_airfield.position.distance_to_point(
|
closest_airfield.position.distance_to_point(
|
||||||
self.package.target.position
|
self.package.target.position
|
||||||
))
|
)
|
||||||
|
)
|
||||||
distance_to_no_fly = distance_to_airfield - min_distance_from_enemy
|
distance_to_no_fly = distance_to_airfield - min_distance_from_enemy
|
||||||
|
|
||||||
min_cap_distance = min(self.doctrine.cap_min_distance_from_cp,
|
min_cap_distance = min(
|
||||||
distance_to_no_fly)
|
self.doctrine.cap_min_distance_from_cp, distance_to_no_fly
|
||||||
max_cap_distance = min(self.doctrine.cap_max_distance_from_cp,
|
)
|
||||||
distance_to_no_fly)
|
max_cap_distance = min(
|
||||||
|
self.doctrine.cap_max_distance_from_cp, distance_to_no_fly
|
||||||
|
)
|
||||||
|
|
||||||
end = location.position.point_from_heading(
|
end = location.position.point_from_heading(
|
||||||
heading,
|
heading,
|
||||||
random.randint(int(min_cap_distance.meters),
|
random.randint(int(min_cap_distance.meters), int(max_cap_distance.meters)),
|
||||||
int(max_cap_distance.meters))
|
|
||||||
)
|
)
|
||||||
diameter = random.randint(
|
diameter = random.randint(
|
||||||
int(self.doctrine.cap_min_track_length.meters),
|
int(self.doctrine.cap_min_track_length.meters),
|
||||||
int(self.doctrine.cap_max_track_length.meters)
|
int(self.doctrine.cap_max_track_length.meters),
|
||||||
)
|
)
|
||||||
start = end.point_from_heading(heading - 180, diameter)
|
start = end.point_from_heading(heading - 180, diameter)
|
||||||
return start, end
|
return start, end
|
||||||
@ -1185,13 +1189,11 @@ class FlightPlanBuilder:
|
|||||||
# Place this either over the target or as close as possible outside the
|
# Place this either over the target or as close as possible outside the
|
||||||
# threat zone: https://github.com/Khopa/dcs_liberation/issues/842.
|
# threat zone: https://github.com/Khopa/dcs_liberation/issues/842.
|
||||||
heading = location.position.heading_between_point(closest_airfield.position)
|
heading = location.position.heading_between_point(closest_airfield.position)
|
||||||
return location.position.point_from_heading(
|
return location.position.point_from_heading(heading, 5000)
|
||||||
heading,
|
|
||||||
5000
|
|
||||||
)
|
|
||||||
|
|
||||||
def racetrack_for_frontline(self, origin: Point,
|
def racetrack_for_frontline(
|
||||||
front_line: FrontLine) -> Tuple[Point, Point]:
|
self, origin: Point, front_line: FrontLine
|
||||||
|
) -> Tuple[Point, Point]:
|
||||||
ally_cp, enemy_cp = front_line.control_points
|
ally_cp, enemy_cp = front_line.control_points
|
||||||
|
|
||||||
# Find targets waypoints
|
# Find targets waypoints
|
||||||
@ -1200,8 +1202,10 @@ class FlightPlanBuilder:
|
|||||||
)
|
)
|
||||||
center = ingress.point_from_heading(heading, distance / 2)
|
center = ingress.point_from_heading(heading, distance / 2)
|
||||||
orbit_center = center.point_from_heading(
|
orbit_center = center.point_from_heading(
|
||||||
heading - 90, random.randint(int(nautical_miles(6).meters),
|
heading - 90,
|
||||||
int(nautical_miles(15).meters))
|
random.randint(
|
||||||
|
int(nautical_miles(6).meters), int(nautical_miles(15).meters)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
combat_width = distance / 2
|
combat_width = distance / 2
|
||||||
@ -1227,18 +1231,21 @@ class FlightPlanBuilder:
|
|||||||
location = self.package.target
|
location = self.package.target
|
||||||
|
|
||||||
patrol_alt = meters(
|
patrol_alt = meters(
|
||||||
random.randint(int(self.doctrine.min_patrol_altitude.meters),
|
random.randint(
|
||||||
int(self.doctrine.max_patrol_altitude.meters)))
|
int(self.doctrine.min_patrol_altitude.meters),
|
||||||
|
int(self.doctrine.max_patrol_altitude.meters),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Create points
|
# Create points
|
||||||
builder = WaypointBuilder(flight, self.game, self.is_player)
|
builder = WaypointBuilder(flight, self.game, self.is_player)
|
||||||
|
|
||||||
if isinstance(location, FrontLine):
|
if isinstance(location, FrontLine):
|
||||||
orbit0p, orbit1p = self.racetrack_for_frontline(
|
orbit0p, orbit1p = self.racetrack_for_frontline(
|
||||||
flight.departure.position, location)
|
flight.departure.position, location
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
orbit0p, orbit1p = self.racetrack_for_objective(location,
|
orbit0p, orbit1p = self.racetrack_for_objective(location, barcap=False)
|
||||||
barcap=False)
|
|
||||||
|
|
||||||
start, end = builder.race_track(orbit0p, orbit1p, patrol_alt)
|
start, end = builder.race_track(orbit0p, orbit1p, patrol_alt)
|
||||||
return TarCapFlightPlan(
|
return TarCapFlightPlan(
|
||||||
@ -1252,18 +1259,17 @@ class FlightPlanBuilder:
|
|||||||
patrol_duration=self.doctrine.cap_duration,
|
patrol_duration=self.doctrine.cap_duration,
|
||||||
engagement_distance=self.doctrine.cap_engagement_range,
|
engagement_distance=self.doctrine.cap_engagement_range,
|
||||||
takeoff=builder.takeoff(flight.departure),
|
takeoff=builder.takeoff(flight.departure),
|
||||||
nav_to=builder.nav_path(flight.departure.position, orbit0p,
|
nav_to=builder.nav_path(flight.departure.position, orbit0p, patrol_alt),
|
||||||
patrol_alt),
|
nav_from=builder.nav_path(orbit1p, flight.arrival.position, patrol_alt),
|
||||||
nav_from=builder.nav_path(orbit1p, flight.arrival.position,
|
|
||||||
patrol_alt),
|
|
||||||
patrol_start=start,
|
patrol_start=start,
|
||||||
patrol_end=end,
|
patrol_end=end,
|
||||||
land=builder.land(flight.arrival),
|
land=builder.land(flight.arrival),
|
||||||
divert=builder.divert(flight.divert)
|
divert=builder.divert(flight.divert),
|
||||||
)
|
)
|
||||||
|
|
||||||
def generate_dead(self, flight: Flight,
|
def generate_dead(
|
||||||
custom_targets: Optional[List[Unit]]) -> StrikeFlightPlan:
|
self, flight: Flight, custom_targets: Optional[List[Unit]]
|
||||||
|
) -> StrikeFlightPlan:
|
||||||
"""Generate a DEAD flight at a given location.
|
"""Generate a DEAD flight at a given location.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -1276,7 +1282,8 @@ class FlightPlanBuilder:
|
|||||||
is_sam = isinstance(location, SamGroundObject)
|
is_sam = isinstance(location, SamGroundObject)
|
||||||
if not is_ewr and not is_sam:
|
if not is_ewr and not is_sam:
|
||||||
logging.exception(
|
logging.exception(
|
||||||
f"Invalid Objective Location for DEAD flight {flight=} at {location=}")
|
f"Invalid Objective Location for DEAD flight {flight=} at {location=}"
|
||||||
|
)
|
||||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||||
|
|
||||||
# TODO: Unify these.
|
# TODO: Unify these.
|
||||||
@ -1288,8 +1295,9 @@ class FlightPlanBuilder:
|
|||||||
for target in custom_targets:
|
for target in custom_targets:
|
||||||
targets.append(StrikeTarget(location.name, target))
|
targets.append(StrikeTarget(location.name, target))
|
||||||
|
|
||||||
return self.strike_flightplan(flight, location,
|
return self.strike_flightplan(
|
||||||
FlightWaypointType.INGRESS_DEAD, targets)
|
flight, location, FlightWaypointType.INGRESS_DEAD, targets
|
||||||
|
)
|
||||||
|
|
||||||
def generate_oca_strike(self, flight: Flight) -> StrikeFlightPlan:
|
def generate_oca_strike(self, flight: Flight) -> StrikeFlightPlan:
|
||||||
"""Generate an OCA Strike flight plan at a given location.
|
"""Generate an OCA Strike flight plan at a given location.
|
||||||
@ -1302,11 +1310,13 @@ class FlightPlanBuilder:
|
|||||||
if not isinstance(location, Airfield):
|
if not isinstance(location, Airfield):
|
||||||
logging.exception(
|
logging.exception(
|
||||||
f"Invalid Objective Location for OCA Strike flight "
|
f"Invalid Objective Location for OCA Strike flight "
|
||||||
f"{flight=} at {location=}.")
|
f"{flight=} at {location=}."
|
||||||
|
)
|
||||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||||
|
|
||||||
return self.strike_flightplan(flight, location,
|
return self.strike_flightplan(
|
||||||
FlightWaypointType.INGRESS_OCA_AIRCRAFT)
|
flight, location, FlightWaypointType.INGRESS_OCA_AIRCRAFT
|
||||||
|
)
|
||||||
|
|
||||||
def generate_runway_attack(self, flight: Flight) -> StrikeFlightPlan:
|
def generate_runway_attack(self, flight: Flight) -> StrikeFlightPlan:
|
||||||
"""Generate a runway attack flight plan at a given location.
|
"""Generate a runway attack flight plan at a given location.
|
||||||
@ -1319,14 +1329,17 @@ class FlightPlanBuilder:
|
|||||||
if not isinstance(location, Airfield):
|
if not isinstance(location, Airfield):
|
||||||
logging.exception(
|
logging.exception(
|
||||||
f"Invalid Objective Location for runway bombing flight "
|
f"Invalid Objective Location for runway bombing flight "
|
||||||
f"{flight=} at {location=}.")
|
f"{flight=} at {location=}."
|
||||||
|
)
|
||||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||||
|
|
||||||
return self.strike_flightplan(flight, location,
|
return self.strike_flightplan(
|
||||||
FlightWaypointType.INGRESS_OCA_RUNWAY)
|
flight, location, FlightWaypointType.INGRESS_OCA_RUNWAY
|
||||||
|
)
|
||||||
|
|
||||||
def generate_sead(self, flight: Flight,
|
def generate_sead(
|
||||||
custom_targets: Optional[List[Unit]]) -> StrikeFlightPlan:
|
self, flight: Flight, custom_targets: Optional[List[Unit]]
|
||||||
|
) -> StrikeFlightPlan:
|
||||||
"""Generate a SEAD flight at a given location.
|
"""Generate a SEAD flight at a given location.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -1344,16 +1357,19 @@ class FlightPlanBuilder:
|
|||||||
for target in custom_targets:
|
for target in custom_targets:
|
||||||
targets.append(StrikeTarget(location.name, target))
|
targets.append(StrikeTarget(location.name, target))
|
||||||
|
|
||||||
return self.strike_flightplan(flight, location,
|
return self.strike_flightplan(
|
||||||
FlightWaypointType.INGRESS_SEAD, targets)
|
flight, location, FlightWaypointType.INGRESS_SEAD, targets
|
||||||
|
)
|
||||||
|
|
||||||
def generate_escort(self, flight: Flight) -> StrikeFlightPlan:
|
def generate_escort(self, flight: Flight) -> StrikeFlightPlan:
|
||||||
assert self.package.waypoints is not None
|
assert self.package.waypoints is not None
|
||||||
|
|
||||||
builder = WaypointBuilder(flight, self.game, self.is_player)
|
builder = WaypointBuilder(flight, self.game, self.is_player)
|
||||||
ingress, target, egress = builder.escort(
|
ingress, target, egress = builder.escort(
|
||||||
self.package.waypoints.ingress, self.package.target,
|
self.package.waypoints.ingress,
|
||||||
self.package.waypoints.egress)
|
self.package.target,
|
||||||
|
self.package.waypoints.egress,
|
||||||
|
)
|
||||||
hold = builder.hold(self._hold_point(flight))
|
hold = builder.hold(self._hold_point(flight))
|
||||||
join = builder.join(self.package.waypoints.join)
|
join = builder.join(self.package.waypoints.join)
|
||||||
split = builder.split(self.package.waypoints.split)
|
split = builder.split(self.package.waypoints.split)
|
||||||
@ -1364,17 +1380,19 @@ class FlightPlanBuilder:
|
|||||||
takeoff=builder.takeoff(flight.departure),
|
takeoff=builder.takeoff(flight.departure),
|
||||||
hold=hold,
|
hold=hold,
|
||||||
hold_duration=timedelta(minutes=5),
|
hold_duration=timedelta(minutes=5),
|
||||||
nav_to=builder.nav_path(hold.position, join.position,
|
nav_to=builder.nav_path(
|
||||||
self.doctrine.ingress_altitude),
|
hold.position, join.position, self.doctrine.ingress_altitude
|
||||||
|
),
|
||||||
join=join,
|
join=join,
|
||||||
ingress=ingress,
|
ingress=ingress,
|
||||||
targets=[target],
|
targets=[target],
|
||||||
egress=egress,
|
egress=egress,
|
||||||
split=split,
|
split=split,
|
||||||
nav_from=builder.nav_path(split.position, flight.arrival.position,
|
nav_from=builder.nav_path(
|
||||||
self.doctrine.ingress_altitude),
|
split.position, flight.arrival.position, self.doctrine.ingress_altitude
|
||||||
|
),
|
||||||
land=builder.land(flight.arrival),
|
land=builder.land(flight.arrival),
|
||||||
divert=builder.divert(flight.divert)
|
divert=builder.divert(flight.divert),
|
||||||
)
|
)
|
||||||
|
|
||||||
def generate_cas(self, flight: Flight) -> CasFlightPlan:
|
def generate_cas(self, flight: Flight) -> CasFlightPlan:
|
||||||
@ -1389,8 +1407,7 @@ class FlightPlanBuilder:
|
|||||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||||
|
|
||||||
ingress, heading, distance = Conflict.frontline_vector(
|
ingress, heading, distance = Conflict.frontline_vector(
|
||||||
location.control_points[0], location.control_points[1],
|
location.control_points[0], location.control_points[1], self.game.theater
|
||||||
self.game.theater
|
|
||||||
)
|
)
|
||||||
center = ingress.point_from_heading(heading, distance / 2)
|
center = ingress.point_from_heading(heading, distance / 2)
|
||||||
egress = ingress.point_from_heading(heading, distance)
|
egress = ingress.point_from_heading(heading, distance)
|
||||||
@ -1407,22 +1424,26 @@ class FlightPlanBuilder:
|
|||||||
flight=flight,
|
flight=flight,
|
||||||
patrol_duration=self.doctrine.cas_duration,
|
patrol_duration=self.doctrine.cas_duration,
|
||||||
takeoff=builder.takeoff(flight.departure),
|
takeoff=builder.takeoff(flight.departure),
|
||||||
nav_to=builder.nav_path(flight.departure.position, ingress,
|
nav_to=builder.nav_path(
|
||||||
self.doctrine.ingress_altitude),
|
flight.departure.position, ingress, self.doctrine.ingress_altitude
|
||||||
nav_from=builder.nav_path(egress, flight.arrival.position,
|
),
|
||||||
self.doctrine.ingress_altitude),
|
nav_from=builder.nav_path(
|
||||||
patrol_start=builder.ingress(FlightWaypointType.INGRESS_CAS,
|
egress, flight.arrival.position, self.doctrine.ingress_altitude
|
||||||
ingress, location),
|
),
|
||||||
|
patrol_start=builder.ingress(
|
||||||
|
FlightWaypointType.INGRESS_CAS, ingress, location
|
||||||
|
),
|
||||||
engagement_distance=meters(FRONTLINE_LENGTH) / 2,
|
engagement_distance=meters(FRONTLINE_LENGTH) / 2,
|
||||||
target=builder.cas(center),
|
target=builder.cas(center),
|
||||||
patrol_end=builder.egress(egress, location),
|
patrol_end=builder.egress(egress, location),
|
||||||
land=builder.land(flight.arrival),
|
land=builder.land(flight.arrival),
|
||||||
divert=builder.divert(flight.divert)
|
divert=builder.divert(flight.divert),
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def target_waypoint(flight: Flight, builder: WaypointBuilder,
|
def target_waypoint(
|
||||||
target: StrikeTarget) -> FlightWaypoint:
|
flight: Flight, builder: WaypointBuilder, target: StrikeTarget
|
||||||
|
) -> FlightWaypoint:
|
||||||
if flight.flight_type in {FlightType.ANTISHIP, FlightType.BAI}:
|
if flight.flight_type in {FlightType.ANTISHIP, FlightType.BAI}:
|
||||||
return builder.bai_group(target)
|
return builder.bai_group(target)
|
||||||
elif flight.flight_type == FlightType.DEAD:
|
elif flight.flight_type == FlightType.DEAD:
|
||||||
@ -1433,8 +1454,9 @@ class FlightPlanBuilder:
|
|||||||
return builder.strike_point(target)
|
return builder.strike_point(target)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def target_area_waypoint(flight: Flight, location: MissionTarget,
|
def target_area_waypoint(
|
||||||
builder: WaypointBuilder) -> FlightWaypoint:
|
flight: Flight, location: MissionTarget, builder: WaypointBuilder
|
||||||
|
) -> FlightWaypoint:
|
||||||
if flight.flight_type == FlightType.DEAD:
|
if flight.flight_type == FlightType.DEAD:
|
||||||
return builder.dead_area(location)
|
return builder.dead_area(location)
|
||||||
elif flight.flight_type == FlightType.SEAD:
|
elif flight.flight_type == FlightType.SEAD:
|
||||||
@ -1455,12 +1477,14 @@ class FlightPlanBuilder:
|
|||||||
# If the origin airfield is closer to the target than the join
|
# If the origin airfield is closer to the target than the join
|
||||||
# point, plan the hold point such that it retreats from the origin
|
# point, plan the hold point such that it retreats from the origin
|
||||||
# airfield.
|
# airfield.
|
||||||
return join.point_from_heading(target.heading_between_point(origin),
|
return join.point_from_heading(
|
||||||
self.doctrine.push_distance.meters)
|
target.heading_between_point(origin), self.doctrine.push_distance.meters
|
||||||
|
)
|
||||||
|
|
||||||
heading_to_join = origin.heading_between_point(join)
|
heading_to_join = origin.heading_between_point(join)
|
||||||
hold_point = origin.point_from_heading(
|
hold_point = origin.point_from_heading(
|
||||||
heading_to_join, self.doctrine.push_distance.meters)
|
heading_to_join, self.doctrine.push_distance.meters
|
||||||
|
)
|
||||||
hold_distance = meters(hold_point.distance_to_point(join))
|
hold_distance = meters(hold_point.distance_to_point(join))
|
||||||
if hold_distance >= self.doctrine.push_distance:
|
if hold_distance >= self.doctrine.push_distance:
|
||||||
# Hold point is between the origin airfield and the join point and
|
# Hold point is between the origin airfield and the join point and
|
||||||
@ -1474,26 +1498,27 @@ class FlightPlanBuilder:
|
|||||||
# properly.
|
# properly.
|
||||||
origin_to_join = origin.distance_to_point(join)
|
origin_to_join = origin.distance_to_point(join)
|
||||||
cos_theta = (
|
cos_theta = (
|
||||||
(self.doctrine.hold_distance.meters ** 2 +
|
self.doctrine.hold_distance.meters ** 2
|
||||||
origin_to_join ** 2 -
|
+ origin_to_join ** 2
|
||||||
self.doctrine.join_distance.meters ** 2) /
|
- self.doctrine.join_distance.meters ** 2
|
||||||
(2 * self.doctrine.hold_distance.meters * origin_to_join)
|
) / (2 * self.doctrine.hold_distance.meters * origin_to_join)
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
theta = math.acos(cos_theta)
|
theta = math.acos(cos_theta)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# No solution that maintains hold and join distances. Extend the
|
# No solution that maintains hold and join distances. Extend the
|
||||||
# hold point away from the target.
|
# hold point away from the target.
|
||||||
return origin.point_from_heading(
|
return origin.point_from_heading(
|
||||||
target.heading_between_point(origin),
|
target.heading_between_point(origin), self.doctrine.hold_distance.meters
|
||||||
self.doctrine.hold_distance.meters)
|
)
|
||||||
|
|
||||||
return origin.point_from_heading(heading_to_join - theta,
|
return origin.point_from_heading(
|
||||||
self.doctrine.hold_distance.meters)
|
heading_to_join - theta, self.doctrine.hold_distance.meters
|
||||||
|
)
|
||||||
|
|
||||||
# TODO: Make a model for the waypoint builder and use that in the UI.
|
# TODO: Make a model for the waypoint builder and use that in the UI.
|
||||||
def generate_rtb_waypoint(self, flight: Flight,
|
def generate_rtb_waypoint(
|
||||||
arrival: ControlPoint) -> FlightWaypoint:
|
self, flight: Flight, arrival: ControlPoint
|
||||||
|
) -> FlightWaypoint:
|
||||||
"""Generate RTB landing point.
|
"""Generate RTB landing point.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -1504,20 +1529,23 @@ class FlightPlanBuilder:
|
|||||||
return builder.land(arrival)
|
return builder.land(arrival)
|
||||||
|
|
||||||
def strike_flightplan(
|
def strike_flightplan(
|
||||||
self, flight: Flight, location: MissionTarget,
|
self,
|
||||||
|
flight: Flight,
|
||||||
|
location: MissionTarget,
|
||||||
ingress_type: FlightWaypointType,
|
ingress_type: FlightWaypointType,
|
||||||
targets: Optional[List[StrikeTarget]] = None) -> StrikeFlightPlan:
|
targets: Optional[List[StrikeTarget]] = None,
|
||||||
|
) -> StrikeFlightPlan:
|
||||||
assert self.package.waypoints is not None
|
assert self.package.waypoints is not None
|
||||||
builder = WaypointBuilder(flight, self.game, self.is_player, targets)
|
builder = WaypointBuilder(flight, self.game, self.is_player, targets)
|
||||||
|
|
||||||
target_waypoints: List[FlightWaypoint] = []
|
target_waypoints: List[FlightWaypoint] = []
|
||||||
if targets is not None:
|
if targets is not None:
|
||||||
for target in targets:
|
for target in targets:
|
||||||
target_waypoints.append(
|
target_waypoints.append(self.target_waypoint(flight, builder, target))
|
||||||
self.target_waypoint(flight, builder, target))
|
|
||||||
else:
|
else:
|
||||||
target_waypoints.append(
|
target_waypoints.append(
|
||||||
self.target_area_waypoint(flight, location, builder))
|
self.target_area_waypoint(flight, location, builder)
|
||||||
|
)
|
||||||
|
|
||||||
hold = builder.hold(self._hold_point(flight))
|
hold = builder.hold(self._hold_point(flight))
|
||||||
join = builder.join(self.package.waypoints.join)
|
join = builder.join(self.package.waypoints.join)
|
||||||
@ -1529,32 +1557,38 @@ class FlightPlanBuilder:
|
|||||||
takeoff=builder.takeoff(flight.departure),
|
takeoff=builder.takeoff(flight.departure),
|
||||||
hold=hold,
|
hold=hold,
|
||||||
hold_duration=timedelta(minutes=5),
|
hold_duration=timedelta(minutes=5),
|
||||||
nav_to=builder.nav_path(hold.position, join.position,
|
nav_to=builder.nav_path(
|
||||||
self.doctrine.ingress_altitude),
|
hold.position, join.position, self.doctrine.ingress_altitude
|
||||||
|
),
|
||||||
join=join,
|
join=join,
|
||||||
ingress=builder.ingress(ingress_type,
|
ingress=builder.ingress(
|
||||||
self.package.waypoints.ingress, location),
|
ingress_type, self.package.waypoints.ingress, location
|
||||||
|
),
|
||||||
targets=target_waypoints,
|
targets=target_waypoints,
|
||||||
egress=builder.egress(self.package.waypoints.egress, location),
|
egress=builder.egress(self.package.waypoints.egress, location),
|
||||||
split=split,
|
split=split,
|
||||||
nav_from=builder.nav_path(split.position, flight.arrival.position,
|
nav_from=builder.nav_path(
|
||||||
self.doctrine.ingress_altitude),
|
split.position, flight.arrival.position, self.doctrine.ingress_altitude
|
||||||
|
),
|
||||||
land=builder.land(flight.arrival),
|
land=builder.land(flight.arrival),
|
||||||
divert=builder.divert(flight.divert)
|
divert=builder.divert(flight.divert),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _retreating_rendezvous_point(self, attack_transition: Point) -> Point:
|
def _retreating_rendezvous_point(self, attack_transition: Point) -> Point:
|
||||||
"""Creates a rendezvous point that retreats from the origin airfield."""
|
"""Creates a rendezvous point that retreats from the origin airfield."""
|
||||||
return attack_transition.point_from_heading(
|
return attack_transition.point_from_heading(
|
||||||
self.package.target.position.heading_between_point(
|
self.package.target.position.heading_between_point(
|
||||||
self.package_airfield().position),
|
self.package_airfield().position
|
||||||
self.doctrine.join_distance.meters)
|
),
|
||||||
|
self.doctrine.join_distance.meters,
|
||||||
|
)
|
||||||
|
|
||||||
def _advancing_rendezvous_point(self, attack_transition: Point) -> Point:
|
def _advancing_rendezvous_point(self, attack_transition: Point) -> Point:
|
||||||
"""Creates a rendezvous point that advances toward the target."""
|
"""Creates a rendezvous point that advances toward the target."""
|
||||||
heading = self._heading_to_package_airfield(attack_transition)
|
heading = self._heading_to_package_airfield(attack_transition)
|
||||||
return attack_transition.point_from_heading(
|
return attack_transition.point_from_heading(
|
||||||
heading, -self.doctrine.join_distance.meters)
|
heading, -self.doctrine.join_distance.meters
|
||||||
|
)
|
||||||
|
|
||||||
def _rendezvous_should_retreat(self, attack_transition: Point) -> bool:
|
def _rendezvous_should_retreat(self, attack_transition: Point) -> bool:
|
||||||
transition_target_distance = attack_transition.distance_to_point(
|
transition_target_distance = attack_transition.distance_to_point(
|
||||||
@ -1609,13 +1643,9 @@ class FlightPlanBuilder:
|
|||||||
# The package airfield is either the flight's airfield (when there is no
|
# The package airfield is either the flight's airfield (when there is no
|
||||||
# package) or the closest airfield to the objective that is the
|
# package) or the closest airfield to the objective that is the
|
||||||
# departure airfield for some flight in the package.
|
# departure airfield for some flight in the package.
|
||||||
cache = ObjectiveDistanceCache.get_closest_airfields(
|
cache = ObjectiveDistanceCache.get_closest_airfields(self.package.target)
|
||||||
self.package.target
|
|
||||||
)
|
|
||||||
for airfield in cache.operational_airfields:
|
for airfield in cache.operational_airfields:
|
||||||
for flight in self.package.flights:
|
for flight in self.package.flights:
|
||||||
if flight.departure == airfield:
|
if flight.departure == airfield:
|
||||||
return airfield
|
return airfield
|
||||||
raise RuntimeError(
|
raise RuntimeError("Could not find any airfield assigned to this package")
|
||||||
"Could not find any airfield assigned to this package"
|
|
||||||
)
|
|
||||||
|
|||||||
@ -23,7 +23,6 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class GroundSpeed:
|
class GroundSpeed:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def for_flight(cls, flight: Flight, altitude: Distance) -> Speed:
|
def for_flight(cls, flight: Flight, altitude: Distance) -> Speed:
|
||||||
if not issubclass(flight.unit_type, FlyingType):
|
if not issubclass(flight.unit_type, FlyingType):
|
||||||
@ -55,13 +54,11 @@ class TravelTime:
|
|||||||
def between_points(a: Point, b: Point, speed: Speed) -> timedelta:
|
def between_points(a: Point, b: Point, speed: Speed) -> timedelta:
|
||||||
error_factor = 1.1
|
error_factor = 1.1
|
||||||
distance = meters(a.distance_to_point(b))
|
distance = meters(a.distance_to_point(b))
|
||||||
return timedelta(
|
return timedelta(hours=distance.nautical_miles / speed.knots * error_factor)
|
||||||
hours=distance.nautical_miles / speed.knots * error_factor)
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: Most if not all of this should move into FlightPlan.
|
# TODO: Most if not all of this should move into FlightPlan.
|
||||||
class TotEstimator:
|
class TotEstimator:
|
||||||
|
|
||||||
def __init__(self, package: Package) -> None:
|
def __init__(self, package: Package) -> None:
|
||||||
self.package = package
|
self.package = package
|
||||||
|
|
||||||
@ -75,9 +72,9 @@ class TotEstimator:
|
|||||||
return startup_time
|
return startup_time
|
||||||
|
|
||||||
def earliest_tot(self) -> timedelta:
|
def earliest_tot(self) -> timedelta:
|
||||||
earliest_tot = max((
|
earliest_tot = max(
|
||||||
self.earliest_tot_for_flight(f) for f in self.package.flights
|
(self.earliest_tot_for_flight(f) for f in self.package.flights)
|
||||||
))
|
)
|
||||||
|
|
||||||
# Trim microseconds. DCS doesn't handle sub-second resolution for tasks,
|
# Trim microseconds. DCS doesn't handle sub-second resolution for tasks,
|
||||||
# and they're not interesting from a mission planning perspective so we
|
# and they're not interesting from a mission planning perspective so we
|
||||||
|
|||||||
@ -36,8 +36,13 @@ class StrikeTarget:
|
|||||||
|
|
||||||
|
|
||||||
class WaypointBuilder:
|
class WaypointBuilder:
|
||||||
def __init__(self, flight: Flight, game: Game, player: bool,
|
def __init__(
|
||||||
targets: Optional[List[StrikeTarget]] = None) -> None:
|
self,
|
||||||
|
flight: Flight,
|
||||||
|
game: Game,
|
||||||
|
player: bool,
|
||||||
|
targets: Optional[List[StrikeTarget]] = None,
|
||||||
|
) -> None:
|
||||||
self.flight = flight
|
self.flight = flight
|
||||||
self.conditions = game.conditions
|
self.conditions = game.conditions
|
||||||
self.doctrine = game.faction_for(player).doctrine
|
self.doctrine = game.faction_for(player).doctrine
|
||||||
@ -65,9 +70,7 @@ class WaypointBuilder:
|
|||||||
FlightWaypointType.NAV,
|
FlightWaypointType.NAV,
|
||||||
position.x,
|
position.x,
|
||||||
position.y,
|
position.y,
|
||||||
meters(
|
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
|
||||||
500
|
|
||||||
) if self.is_helo else self.doctrine.rendezvous_altitude
|
|
||||||
)
|
)
|
||||||
waypoint.name = "NAV"
|
waypoint.name = "NAV"
|
||||||
waypoint.alt_type = "BARO"
|
waypoint.alt_type = "BARO"
|
||||||
@ -75,10 +78,7 @@ class WaypointBuilder:
|
|||||||
waypoint.pretty_name = "Enter theater"
|
waypoint.pretty_name = "Enter theater"
|
||||||
else:
|
else:
|
||||||
waypoint = FlightWaypoint(
|
waypoint = FlightWaypoint(
|
||||||
FlightWaypointType.TAKEOFF,
|
FlightWaypointType.TAKEOFF, position.x, position.y, meters(0)
|
||||||
position.x,
|
|
||||||
position.y,
|
|
||||||
meters(0)
|
|
||||||
)
|
)
|
||||||
waypoint.name = "TAKEOFF"
|
waypoint.name = "TAKEOFF"
|
||||||
waypoint.alt_type = "RADIO"
|
waypoint.alt_type = "RADIO"
|
||||||
@ -98,9 +98,7 @@ class WaypointBuilder:
|
|||||||
FlightWaypointType.NAV,
|
FlightWaypointType.NAV,
|
||||||
position.x,
|
position.x,
|
||||||
position.y,
|
position.y,
|
||||||
meters(
|
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
|
||||||
500
|
|
||||||
) if self.is_helo else self.doctrine.rendezvous_altitude
|
|
||||||
)
|
)
|
||||||
waypoint.name = "NAV"
|
waypoint.name = "NAV"
|
||||||
waypoint.alt_type = "BARO"
|
waypoint.alt_type = "BARO"
|
||||||
@ -108,10 +106,7 @@ class WaypointBuilder:
|
|||||||
waypoint.pretty_name = "Exit theater"
|
waypoint.pretty_name = "Exit theater"
|
||||||
else:
|
else:
|
||||||
waypoint = FlightWaypoint(
|
waypoint = FlightWaypoint(
|
||||||
FlightWaypointType.LANDING_POINT,
|
FlightWaypointType.LANDING_POINT, position.x, position.y, meters(0)
|
||||||
position.x,
|
|
||||||
position.y,
|
|
||||||
meters(0)
|
|
||||||
)
|
)
|
||||||
waypoint.name = "LANDING"
|
waypoint.name = "LANDING"
|
||||||
waypoint.alt_type = "RADIO"
|
waypoint.alt_type = "RADIO"
|
||||||
@ -119,8 +114,7 @@ class WaypointBuilder:
|
|||||||
waypoint.pretty_name = "Land"
|
waypoint.pretty_name = "Land"
|
||||||
return waypoint
|
return waypoint
|
||||||
|
|
||||||
def divert(self,
|
def divert(self, divert: Optional[ControlPoint]) -> Optional[FlightWaypoint]:
|
||||||
divert: Optional[ControlPoint]) -> Optional[FlightWaypoint]:
|
|
||||||
"""Create divert waypoint for the given arrival airfield or carrier.
|
"""Create divert waypoint for the given arrival airfield or carrier.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -141,10 +135,7 @@ class WaypointBuilder:
|
|||||||
altitude_type = "RADIO"
|
altitude_type = "RADIO"
|
||||||
|
|
||||||
waypoint = FlightWaypoint(
|
waypoint = FlightWaypoint(
|
||||||
FlightWaypointType.DIVERT,
|
FlightWaypointType.DIVERT, position.x, position.y, altitude
|
||||||
position.x,
|
|
||||||
position.y,
|
|
||||||
altitude
|
|
||||||
)
|
)
|
||||||
waypoint.alt_type = altitude_type
|
waypoint.alt_type = altitude_type
|
||||||
waypoint.name = "DIVERT"
|
waypoint.name = "DIVERT"
|
||||||
@ -158,9 +149,7 @@ class WaypointBuilder:
|
|||||||
FlightWaypointType.LOITER,
|
FlightWaypointType.LOITER,
|
||||||
position.x,
|
position.x,
|
||||||
position.y,
|
position.y,
|
||||||
meters(
|
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
|
||||||
500
|
|
||||||
) if self.is_helo else self.doctrine.rendezvous_altitude
|
|
||||||
)
|
)
|
||||||
waypoint.pretty_name = "Hold"
|
waypoint.pretty_name = "Hold"
|
||||||
waypoint.description = "Wait until push time"
|
waypoint.description = "Wait until push time"
|
||||||
@ -172,9 +161,7 @@ class WaypointBuilder:
|
|||||||
FlightWaypointType.JOIN,
|
FlightWaypointType.JOIN,
|
||||||
position.x,
|
position.x,
|
||||||
position.y,
|
position.y,
|
||||||
meters(
|
meters(80) if self.is_helo else self.doctrine.ingress_altitude,
|
||||||
80
|
|
||||||
) if self.is_helo else self.doctrine.ingress_altitude
|
|
||||||
)
|
)
|
||||||
if self.is_helo:
|
if self.is_helo:
|
||||||
waypoint.alt_type = "RADIO"
|
waypoint.alt_type = "RADIO"
|
||||||
@ -188,9 +175,7 @@ class WaypointBuilder:
|
|||||||
FlightWaypointType.SPLIT,
|
FlightWaypointType.SPLIT,
|
||||||
position.x,
|
position.x,
|
||||||
position.y,
|
position.y,
|
||||||
meters(
|
meters(80) if self.is_helo else self.doctrine.ingress_altitude,
|
||||||
80
|
|
||||||
) if self.is_helo else self.doctrine.ingress_altitude
|
|
||||||
)
|
)
|
||||||
if self.is_helo:
|
if self.is_helo:
|
||||||
waypoint.alt_type = "RADIO"
|
waypoint.alt_type = "RADIO"
|
||||||
@ -199,15 +184,17 @@ class WaypointBuilder:
|
|||||||
waypoint.name = "SPLIT"
|
waypoint.name = "SPLIT"
|
||||||
return waypoint
|
return waypoint
|
||||||
|
|
||||||
def ingress(self, ingress_type: FlightWaypointType, position: Point,
|
def ingress(
|
||||||
objective: MissionTarget) -> FlightWaypoint:
|
self,
|
||||||
|
ingress_type: FlightWaypointType,
|
||||||
|
position: Point,
|
||||||
|
objective: MissionTarget,
|
||||||
|
) -> FlightWaypoint:
|
||||||
waypoint = FlightWaypoint(
|
waypoint = FlightWaypoint(
|
||||||
ingress_type,
|
ingress_type,
|
||||||
position.x,
|
position.x,
|
||||||
position.y,
|
position.y,
|
||||||
meters(
|
meters(50) if self.is_helo else self.doctrine.ingress_altitude,
|
||||||
50
|
|
||||||
) if self.is_helo else self.doctrine.ingress_altitude
|
|
||||||
)
|
)
|
||||||
if self.is_helo:
|
if self.is_helo:
|
||||||
waypoint.alt_type = "RADIO"
|
waypoint.alt_type = "RADIO"
|
||||||
@ -223,9 +210,7 @@ class WaypointBuilder:
|
|||||||
FlightWaypointType.EGRESS,
|
FlightWaypointType.EGRESS,
|
||||||
position.x,
|
position.x,
|
||||||
position.y,
|
position.y,
|
||||||
meters(
|
meters(50) if self.is_helo else self.doctrine.ingress_altitude,
|
||||||
50
|
|
||||||
) if self.is_helo else self.doctrine.ingress_altitude
|
|
||||||
)
|
)
|
||||||
if self.is_helo:
|
if self.is_helo:
|
||||||
waypoint.alt_type = "RADIO"
|
waypoint.alt_type = "RADIO"
|
||||||
@ -252,7 +237,7 @@ class WaypointBuilder:
|
|||||||
FlightWaypointType.TARGET_POINT,
|
FlightWaypointType.TARGET_POINT,
|
||||||
target.target.position.x,
|
target.target.position.x,
|
||||||
target.target.position.y,
|
target.target.position.y,
|
||||||
meters(0)
|
meters(0),
|
||||||
)
|
)
|
||||||
waypoint.description = description
|
waypoint.description = description
|
||||||
waypoint.pretty_name = description
|
waypoint.pretty_name = description
|
||||||
@ -277,13 +262,14 @@ class WaypointBuilder:
|
|||||||
return self._target_area(f"ATTACK {target.name}", target, flyover=True)
|
return self._target_area(f"ATTACK {target.name}", target, flyover=True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _target_area(name: str, location: MissionTarget,
|
def _target_area(
|
||||||
flyover: bool = False) -> FlightWaypoint:
|
name: str, location: MissionTarget, flyover: bool = False
|
||||||
|
) -> FlightWaypoint:
|
||||||
waypoint = FlightWaypoint(
|
waypoint = FlightWaypoint(
|
||||||
FlightWaypointType.TARGET_GROUP_LOC,
|
FlightWaypointType.TARGET_GROUP_LOC,
|
||||||
location.position.x,
|
location.position.x,
|
||||||
location.position.y,
|
location.position.y,
|
||||||
meters(0)
|
meters(0),
|
||||||
)
|
)
|
||||||
waypoint.description = name
|
waypoint.description = name
|
||||||
waypoint.pretty_name = name
|
waypoint.pretty_name = name
|
||||||
@ -308,7 +294,7 @@ class WaypointBuilder:
|
|||||||
FlightWaypointType.CAS,
|
FlightWaypointType.CAS,
|
||||||
position.x,
|
position.x,
|
||||||
position.y,
|
position.y,
|
||||||
meters(50) if self.is_helo else meters(1000)
|
meters(50) if self.is_helo else meters(1000),
|
||||||
)
|
)
|
||||||
waypoint.alt_type = "RADIO"
|
waypoint.alt_type = "RADIO"
|
||||||
waypoint.description = "Provide CAS"
|
waypoint.description = "Provide CAS"
|
||||||
@ -325,10 +311,7 @@ class WaypointBuilder:
|
|||||||
altitude: Altitude of the racetrack.
|
altitude: Altitude of the racetrack.
|
||||||
"""
|
"""
|
||||||
waypoint = FlightWaypoint(
|
waypoint = FlightWaypoint(
|
||||||
FlightWaypointType.PATROL_TRACK,
|
FlightWaypointType.PATROL_TRACK, position.x, position.y, altitude
|
||||||
position.x,
|
|
||||||
position.y,
|
|
||||||
altitude
|
|
||||||
)
|
)
|
||||||
waypoint.name = "RACETRACK START"
|
waypoint.name = "RACETRACK START"
|
||||||
waypoint.description = "Orbit between this point and the next point"
|
waypoint.description = "Orbit between this point and the next point"
|
||||||
@ -344,18 +327,16 @@ class WaypointBuilder:
|
|||||||
altitude: Altitude of the racetrack.
|
altitude: Altitude of the racetrack.
|
||||||
"""
|
"""
|
||||||
waypoint = FlightWaypoint(
|
waypoint = FlightWaypoint(
|
||||||
FlightWaypointType.PATROL,
|
FlightWaypointType.PATROL, position.x, position.y, altitude
|
||||||
position.x,
|
|
||||||
position.y,
|
|
||||||
altitude
|
|
||||||
)
|
)
|
||||||
waypoint.name = "RACETRACK END"
|
waypoint.name = "RACETRACK END"
|
||||||
waypoint.description = "Orbit between this point and the previous point"
|
waypoint.description = "Orbit between this point and the previous point"
|
||||||
waypoint.pretty_name = "Race-track end"
|
waypoint.pretty_name = "Race-track end"
|
||||||
return waypoint
|
return waypoint
|
||||||
|
|
||||||
def race_track(self, start: Point, end: Point,
|
def race_track(
|
||||||
altitude: Distance) -> Tuple[FlightWaypoint, FlightWaypoint]:
|
self, start: Point, end: Point, altitude: Distance
|
||||||
|
) -> Tuple[FlightWaypoint, FlightWaypoint]:
|
||||||
"""Creates two waypoint for a racetrack orbit.
|
"""Creates two waypoint for a racetrack orbit.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -363,8 +344,10 @@ class WaypointBuilder:
|
|||||||
end: The ending racetrack waypoint.
|
end: The ending racetrack waypoint.
|
||||||
altitude: The racetrack altitude.
|
altitude: The racetrack altitude.
|
||||||
"""
|
"""
|
||||||
return (self.race_track_start(start, altitude),
|
return (
|
||||||
self.race_track_end(end, altitude))
|
self.race_track_start(start, altitude),
|
||||||
|
self.race_track_end(end, altitude),
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def orbit(start: Point, altitude: Distance) -> FlightWaypoint:
|
def orbit(start: Point, altitude: Distance) -> FlightWaypoint:
|
||||||
@ -375,12 +358,7 @@ class WaypointBuilder:
|
|||||||
altitude: Altitude of the racetrack.
|
altitude: Altitude of the racetrack.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
waypoint = FlightWaypoint(
|
waypoint = FlightWaypoint(FlightWaypointType.LOITER, start.x, start.y, altitude)
|
||||||
FlightWaypointType.LOITER,
|
|
||||||
start.x,
|
|
||||||
start.y,
|
|
||||||
altitude
|
|
||||||
)
|
|
||||||
waypoint.name = "ORBIT"
|
waypoint.name = "ORBIT"
|
||||||
waypoint.description = "Anchor and hold at this point"
|
waypoint.description = "Anchor and hold at this point"
|
||||||
waypoint.pretty_name = "Orbit"
|
waypoint.pretty_name = "Orbit"
|
||||||
@ -395,10 +373,7 @@ class WaypointBuilder:
|
|||||||
altitude: Altitude of the sweep in meters.
|
altitude: Altitude of the sweep in meters.
|
||||||
"""
|
"""
|
||||||
waypoint = FlightWaypoint(
|
waypoint = FlightWaypoint(
|
||||||
FlightWaypointType.INGRESS_SWEEP,
|
FlightWaypointType.INGRESS_SWEEP, position.x, position.y, altitude
|
||||||
position.x,
|
|
||||||
position.y,
|
|
||||||
altitude
|
|
||||||
)
|
)
|
||||||
waypoint.name = "SWEEP START"
|
waypoint.name = "SWEEP START"
|
||||||
waypoint.description = "Proceed to the target and engage enemy aircraft"
|
waypoint.description = "Proceed to the target and engage enemy aircraft"
|
||||||
@ -414,18 +389,16 @@ class WaypointBuilder:
|
|||||||
altitude: Altitude of the sweep in meters.
|
altitude: Altitude of the sweep in meters.
|
||||||
"""
|
"""
|
||||||
waypoint = FlightWaypoint(
|
waypoint = FlightWaypoint(
|
||||||
FlightWaypointType.EGRESS,
|
FlightWaypointType.EGRESS, position.x, position.y, altitude
|
||||||
position.x,
|
|
||||||
position.y,
|
|
||||||
altitude
|
|
||||||
)
|
)
|
||||||
waypoint.name = "SWEEP END"
|
waypoint.name = "SWEEP END"
|
||||||
waypoint.description = "End of sweep"
|
waypoint.description = "End of sweep"
|
||||||
waypoint.pretty_name = "Sweep end"
|
waypoint.pretty_name = "Sweep end"
|
||||||
return waypoint
|
return waypoint
|
||||||
|
|
||||||
def sweep(self, start: Point, end: Point,
|
def sweep(
|
||||||
altitude: Distance) -> Tuple[FlightWaypoint, FlightWaypoint]:
|
self, start: Point, end: Point, altitude: Distance
|
||||||
|
) -> Tuple[FlightWaypoint, FlightWaypoint]:
|
||||||
"""Creates two waypoint for a racetrack orbit.
|
"""Creates two waypoint for a racetrack orbit.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -433,11 +406,11 @@ class WaypointBuilder:
|
|||||||
end: The end of the sweep.
|
end: The end of the sweep.
|
||||||
altitude: The sweep altitude.
|
altitude: The sweep altitude.
|
||||||
"""
|
"""
|
||||||
return (self.sweep_start(start, altitude),
|
return (self.sweep_start(start, altitude), self.sweep_end(end, altitude))
|
||||||
self.sweep_end(end, altitude))
|
|
||||||
|
|
||||||
def escort(self, ingress: Point, target: MissionTarget, egress: Point) -> \
|
def escort(
|
||||||
Tuple[FlightWaypoint, FlightWaypoint, FlightWaypoint]:
|
self, ingress: Point, target: MissionTarget, egress: Point
|
||||||
|
) -> Tuple[FlightWaypoint, FlightWaypoint, FlightWaypoint]:
|
||||||
"""Creates the waypoints needed to escort the package.
|
"""Creates the waypoints needed to escort the package.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -451,16 +424,13 @@ class WaypointBuilder:
|
|||||||
# description in gen.aircraft.JoinPointBuilder), so instead we give
|
# description in gen.aircraft.JoinPointBuilder), so instead we give
|
||||||
# the escort flights a flight plan including the ingress point, target
|
# the escort flights a flight plan including the ingress point, target
|
||||||
# area, and egress point.
|
# area, and egress point.
|
||||||
ingress = self.ingress(FlightWaypointType.INGRESS_ESCORT, ingress,
|
ingress = self.ingress(FlightWaypointType.INGRESS_ESCORT, ingress, target)
|
||||||
target)
|
|
||||||
|
|
||||||
waypoint = FlightWaypoint(
|
waypoint = FlightWaypoint(
|
||||||
FlightWaypointType.TARGET_GROUP_LOC,
|
FlightWaypointType.TARGET_GROUP_LOC,
|
||||||
target.position.x,
|
target.position.x,
|
||||||
target.position.y,
|
target.position.y,
|
||||||
meters(
|
meters(50) if self.is_helo else self.doctrine.ingress_altitude,
|
||||||
50
|
|
||||||
) if self.is_helo else self.doctrine.ingress_altitude
|
|
||||||
)
|
)
|
||||||
if self.is_helo:
|
if self.is_helo:
|
||||||
waypoint.alt_type = "RADIO"
|
waypoint.alt_type = "RADIO"
|
||||||
@ -480,18 +450,14 @@ class WaypointBuilder:
|
|||||||
altitude: Altitude of the waypoint.
|
altitude: Altitude of the waypoint.
|
||||||
"""
|
"""
|
||||||
waypoint = FlightWaypoint(
|
waypoint = FlightWaypoint(
|
||||||
FlightWaypointType.NAV,
|
FlightWaypointType.NAV, position.x, position.y, altitude
|
||||||
position.x,
|
|
||||||
position.y,
|
|
||||||
altitude
|
|
||||||
)
|
)
|
||||||
waypoint.name = "NAV"
|
waypoint.name = "NAV"
|
||||||
waypoint.description = "NAV"
|
waypoint.description = "NAV"
|
||||||
waypoint.pretty_name = "Nav"
|
waypoint.pretty_name = "Nav"
|
||||||
return waypoint
|
return waypoint
|
||||||
|
|
||||||
def nav_path(self, a: Point, b: Point,
|
def nav_path(self, a: Point, b: Point, altitude: Distance) -> List[FlightWaypoint]:
|
||||||
altitude: Distance) -> List[FlightWaypoint]:
|
|
||||||
path = self.clean_nav_points(self.navmesh.shortest_path(a, b))
|
path = self.clean_nav_points(self.navmesh.shortest_path(a, b))
|
||||||
return [self.nav(self.perturb(p), altitude) for p in path]
|
return [self.nav(self.perturb(p), altitude) for p in path]
|
||||||
|
|
||||||
@ -518,10 +484,8 @@ class WaypointBuilder:
|
|||||||
previous = current
|
previous = current
|
||||||
current = nxt
|
current = nxt
|
||||||
|
|
||||||
def nav_point_prunable(self, previous: Point, current: Point,
|
def nav_point_prunable(self, previous: Point, current: Point, nxt: Point) -> bool:
|
||||||
nxt: Point) -> bool:
|
previous_threatened = self.threat_zones.path_threatened(previous, current)
|
||||||
previous_threatened = self.threat_zones.path_threatened(previous,
|
|
||||||
current)
|
|
||||||
next_threatened = self.threat_zones.path_threatened(current, nxt)
|
next_threatened = self.threat_zones.path_threatened(current, nxt)
|
||||||
pruned_threatened = self.threat_zones.path_threatened(previous, nxt)
|
pruned_threatened = self.threat_zones.path_threatened(previous, nxt)
|
||||||
previous_distance = meters(previous.distance_to_point(current))
|
previous_distance = meters(previous.distance_to_point(current))
|
||||||
|
|||||||
@ -15,11 +15,15 @@ class ForcedOptionsGenerator:
|
|||||||
self.game = game
|
self.game = game
|
||||||
|
|
||||||
def _set_options_view(self) -> None:
|
def _set_options_view(self) -> None:
|
||||||
self.mission.forced_options.options_view = self.game.settings.map_coalition_visibility
|
self.mission.forced_options.options_view = (
|
||||||
|
self.game.settings.map_coalition_visibility
|
||||||
|
)
|
||||||
|
|
||||||
def _set_external_views(self) -> None:
|
def _set_external_views(self) -> None:
|
||||||
if not self.game.settings.external_views_allowed:
|
if not self.game.settings.external_views_allowed:
|
||||||
self.mission.forced_options.external_views = self.game.settings.external_views_allowed
|
self.mission.forced_options.external_views = (
|
||||||
|
self.game.settings.external_views_allowed
|
||||||
|
)
|
||||||
|
|
||||||
def _set_labels(self) -> None:
|
def _set_labels(self) -> None:
|
||||||
# TODO: Fix settings to use the real type.
|
# TODO: Fix settings to use the real type.
|
||||||
|
|||||||
@ -39,12 +39,11 @@ GROUP_SIZES_BY_COMBAT_STANCE = {
|
|||||||
CombatStance.RETREAT: [2, 4, 6, 8],
|
CombatStance.RETREAT: [2, 4, 6, 8],
|
||||||
CombatStance.BREAKTHROUGH: [4, 6, 6, 8],
|
CombatStance.BREAKTHROUGH: [4, 6, 6, 8],
|
||||||
CombatStance.ELIMINATION: [2, 4, 4, 4, 6],
|
CombatStance.ELIMINATION: [2, 4, 4, 4, 6],
|
||||||
CombatStance.AMBUSH: [1, 1, 2, 2, 2, 2, 4]
|
CombatStance.AMBUSH: [1, 1, 2, 2, 2, 2, 4],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class CombatGroup:
|
class CombatGroup:
|
||||||
|
|
||||||
def __init__(self, role: CombatGroupRole):
|
def __init__(self, role: CombatGroupRole):
|
||||||
self.units: List[VehicleType] = []
|
self.units: List[VehicleType] = []
|
||||||
self.role = role
|
self.role = role
|
||||||
@ -60,11 +59,12 @@ class CombatGroup:
|
|||||||
|
|
||||||
|
|
||||||
class GroundPlanner:
|
class GroundPlanner:
|
||||||
|
def __init__(self, cp: ControlPoint, game):
|
||||||
def __init__(self, cp:ControlPoint, game):
|
|
||||||
self.cp = cp
|
self.cp = cp
|
||||||
self.game = game
|
self.game = game
|
||||||
self.connected_enemy_cp = [cp for cp in self.cp.connected_points if cp.captured != self.cp.captured]
|
self.connected_enemy_cp = [
|
||||||
|
cp for cp in self.cp.connected_points if cp.captured != self.cp.captured
|
||||||
|
]
|
||||||
self.tank_groups: List[CombatGroup] = []
|
self.tank_groups: List[CombatGroup] = []
|
||||||
self.apc_group: List[CombatGroup] = []
|
self.apc_group: List[CombatGroup] = []
|
||||||
self.ifv_group: List[CombatGroup] = []
|
self.ifv_group: List[CombatGroup] = []
|
||||||
@ -80,7 +80,7 @@ class GroundPlanner:
|
|||||||
|
|
||||||
def plan_groundwar(self):
|
def plan_groundwar(self):
|
||||||
|
|
||||||
if hasattr(self.cp, 'stance'):
|
if hasattr(self.cp, "stance"):
|
||||||
group_size_choice = GROUP_SIZES_BY_COMBAT_STANCE[self.cp.stance]
|
group_size_choice = GROUP_SIZES_BY_COMBAT_STANCE[self.cp.stance]
|
||||||
else:
|
else:
|
||||||
self.cp.stance = CombatStance.DEFENSIVE
|
self.cp.stance = CombatStance.DEFENSIVE
|
||||||
@ -152,12 +152,3 @@ class GroundPlanner:
|
|||||||
print("For : #" + str(key))
|
print("For : #" + str(key))
|
||||||
for group in self.units_per_cp[key]:
|
for group in self.units_per_cp[key]:
|
||||||
print(str(group))
|
print(str(group))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,6 @@ TYPE_TANKS = [
|
|||||||
Armor.MBT_M60A3_Patton,
|
Armor.MBT_M60A3_Patton,
|
||||||
Armor.MBT_Merkava_Mk__4,
|
Armor.MBT_Merkava_Mk__4,
|
||||||
Armor.ZTZ_96B,
|
Armor.ZTZ_96B,
|
||||||
|
|
||||||
# WW2
|
# WW2
|
||||||
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G,
|
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G,
|
||||||
Armor.MT_Pz_Kpfw_IV_Ausf_H,
|
Armor.MT_Pz_Kpfw_IV_Ausf_H,
|
||||||
@ -29,36 +28,30 @@ TYPE_TANKS = [
|
|||||||
Armor.CT_Cromwell_IV,
|
Armor.CT_Cromwell_IV,
|
||||||
Armor.HIT_Churchill_VII,
|
Armor.HIT_Churchill_VII,
|
||||||
Armor.LT_Mk_VII_Tetrarch,
|
Armor.LT_Mk_VII_Tetrarch,
|
||||||
|
|
||||||
# Mods
|
# Mods
|
||||||
frenchpack.DIM__TOYOTA_BLUE,
|
frenchpack.DIM__TOYOTA_BLUE,
|
||||||
frenchpack.DIM__TOYOTA_GREEN,
|
frenchpack.DIM__TOYOTA_GREEN,
|
||||||
frenchpack.DIM__TOYOTA_DESERT,
|
frenchpack.DIM__TOYOTA_DESERT,
|
||||||
frenchpack.DIM__KAMIKAZE,
|
frenchpack.DIM__KAMIKAZE,
|
||||||
|
|
||||||
frenchpack.AMX_10RCR,
|
frenchpack.AMX_10RCR,
|
||||||
frenchpack.AMX_10RCR_SEPAR,
|
frenchpack.AMX_10RCR_SEPAR,
|
||||||
frenchpack.AMX_30B2,
|
frenchpack.AMX_30B2,
|
||||||
frenchpack.Leclerc_Serie_XXI,
|
frenchpack.Leclerc_Serie_XXI,
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
TYPE_ATGM = [
|
TYPE_ATGM = [
|
||||||
Armor.ATGM_M1045_HMMWV_TOW,
|
Armor.ATGM_M1045_HMMWV_TOW,
|
||||||
Armor.ATGM_M1134_Stryker,
|
Armor.ATGM_M1134_Stryker,
|
||||||
Armor.IFV_BMP_2,
|
Armor.IFV_BMP_2,
|
||||||
|
|
||||||
# WW2 (Tank Destroyers)
|
# WW2 (Tank Destroyers)
|
||||||
Armor.M30_Cargo_Carrier,
|
Armor.M30_Cargo_Carrier,
|
||||||
Armor.TD_Jagdpanzer_IV,
|
Armor.TD_Jagdpanzer_IV,
|
||||||
Armor.TD_Jagdpanther_G1,
|
Armor.TD_Jagdpanther_G1,
|
||||||
Armor.TD_M10_GMC,
|
Armor.TD_M10_GMC,
|
||||||
|
|
||||||
# Mods
|
# Mods
|
||||||
frenchpack.VBAE_CRAB_MMP,
|
frenchpack.VBAE_CRAB_MMP,
|
||||||
frenchpack.VAB_MEPHISTO,
|
frenchpack.VAB_MEPHISTO,
|
||||||
frenchpack.TRM_2000_PAMELA,
|
frenchpack.TRM_2000_PAMELA,
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
TYPE_IFV = [
|
TYPE_IFV = [
|
||||||
@ -73,17 +66,14 @@ TYPE_IFV = [
|
|||||||
Armor.IFV_M2A2_Bradley,
|
Armor.IFV_M2A2_Bradley,
|
||||||
Armor.IFV_BMD_1,
|
Armor.IFV_BMD_1,
|
||||||
Armor.ZBD_04A,
|
Armor.ZBD_04A,
|
||||||
|
|
||||||
# WW2
|
# WW2
|
||||||
Armor.AC_Sd_Kfz_234_2_Puma,
|
Armor.AC_Sd_Kfz_234_2_Puma,
|
||||||
Armor.LAC_M8_Greyhound,
|
Armor.LAC_M8_Greyhound,
|
||||||
Armor.Daimler_Armoured_Car,
|
Armor.Daimler_Armoured_Car,
|
||||||
|
|
||||||
# Mods
|
# Mods
|
||||||
frenchpack.ERC_90,
|
frenchpack.ERC_90,
|
||||||
frenchpack.VBAE_CRAB,
|
frenchpack.VBAE_CRAB,
|
||||||
frenchpack.VAB_T20_13
|
frenchpack.VAB_T20_13,
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
TYPE_APC = [
|
TYPE_APC = [
|
||||||
@ -101,16 +91,13 @@ TYPE_APC = [
|
|||||||
Armor.ARV_BRDM_2,
|
Armor.ARV_BRDM_2,
|
||||||
Armor.ARV_BTR_RD,
|
Armor.ARV_BTR_RD,
|
||||||
Armor.FDDM_Grad,
|
Armor.FDDM_Grad,
|
||||||
|
|
||||||
# WW2
|
# WW2
|
||||||
Armor.APC_M2A1,
|
Armor.APC_M2A1,
|
||||||
Armor.APC_Sd_Kfz_251,
|
Armor.APC_Sd_Kfz_251,
|
||||||
|
|
||||||
# Mods
|
# Mods
|
||||||
frenchpack.VAB__50,
|
frenchpack.VAB__50,
|
||||||
frenchpack.VBL__50,
|
frenchpack.VBL__50,
|
||||||
frenchpack.VBL_AANF1,
|
frenchpack.VBL_AANF1,
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
TYPE_ARTILLERY = [
|
TYPE_ARTILLERY = [
|
||||||
@ -125,10 +112,9 @@ TYPE_ARTILLERY = [
|
|||||||
Artillery.SpGH_Dana,
|
Artillery.SpGH_Dana,
|
||||||
Artillery.SPH_2S19_Msta,
|
Artillery.SPH_2S19_Msta,
|
||||||
Artillery.MLRS_FDDM,
|
Artillery.MLRS_FDDM,
|
||||||
|
|
||||||
# WW2
|
# WW2
|
||||||
Artillery.Sturmpanzer_IV_Brummbär,
|
Artillery.Sturmpanzer_IV_Brummbär,
|
||||||
Artillery.M12_GMC
|
Artillery.M12_GMC,
|
||||||
]
|
]
|
||||||
|
|
||||||
TYPE_LOGI = [
|
TYPE_LOGI = [
|
||||||
@ -147,11 +133,9 @@ TYPE_LOGI = [
|
|||||||
Unarmed.Willys_MB,
|
Unarmed.Willys_MB,
|
||||||
Unarmed.Land_Rover_109_S3,
|
Unarmed.Land_Rover_109_S3,
|
||||||
Unarmed.Land_Rover_101_FC,
|
Unarmed.Land_Rover_101_FC,
|
||||||
|
|
||||||
# Mods
|
# Mods
|
||||||
frenchpack.VBL,
|
frenchpack.VBL,
|
||||||
frenchpack.VAB,
|
frenchpack.VAB,
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
TYPE_INFANTRY = [
|
TYPE_INFANTRY = [
|
||||||
@ -179,7 +163,6 @@ TYPE_SHORAD = [
|
|||||||
AirDefence.SAM_SA_13_Strela_10M3_9A35M3,
|
AirDefence.SAM_SA_13_Strela_10M3_9A35M3,
|
||||||
AirDefence.SAM_SA_15_Tor_9A331,
|
AirDefence.SAM_SA_15_Tor_9A331,
|
||||||
AirDefence.SAM_SA_19_Tunguska_2S6,
|
AirDefence.SAM_SA_19_Tunguska_2S6,
|
||||||
|
|
||||||
AirDefence.SPAAA_Gepard,
|
AirDefence.SPAAA_Gepard,
|
||||||
AirDefence.AAA_Vulcan_M163,
|
AirDefence.AAA_Vulcan_M163,
|
||||||
AirDefence.SAM_Linebacker_M6,
|
AirDefence.SAM_Linebacker_M6,
|
||||||
@ -187,7 +170,6 @@ TYPE_SHORAD = [
|
|||||||
AirDefence.SAM_Avenger_M1097,
|
AirDefence.SAM_Avenger_M1097,
|
||||||
AirDefence.SAM_Roland_ADS,
|
AirDefence.SAM_Roland_ADS,
|
||||||
AirDefence.HQ_7_Self_Propelled_LN,
|
AirDefence.HQ_7_Self_Propelled_LN,
|
||||||
|
|
||||||
AirDefence.AAA_8_8cm_Flak_18,
|
AirDefence.AAA_8_8cm_Flak_18,
|
||||||
AirDefence.AAA_8_8cm_Flak_36,
|
AirDefence.AAA_8_8cm_Flak_36,
|
||||||
AirDefence.AAA_8_8cm_Flak_37,
|
AirDefence.AAA_8_8cm_Flak_37,
|
||||||
@ -195,5 +177,4 @@ TYPE_SHORAD = [
|
|||||||
AirDefence.AAA_Bofors_40mm,
|
AirDefence.AAA_Bofors_40mm,
|
||||||
AirDefence.AAA_M1_37mm,
|
AirDefence.AAA_M1_37mm,
|
||||||
AirDefence.AA_gun_QF_3_7,
|
AirDefence.AA_gun_QF_3_7,
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|||||||
@ -3,9 +3,10 @@ from enum import Enum
|
|||||||
|
|
||||||
class CombatStance(Enum):
|
class CombatStance(Enum):
|
||||||
DEFENSIVE = 0 # Unit will adopt defensive stance with medium group of units
|
DEFENSIVE = 0 # Unit will adopt defensive stance with medium group of units
|
||||||
AGGRESSIVE = 1 # Unit will attempt to make progress with medium sized group of units
|
AGGRESSIVE = (
|
||||||
|
1 # Unit will attempt to make progress with medium sized group of units
|
||||||
|
)
|
||||||
RETREAT = 2 # Unit will retreat
|
RETREAT = 2 # Unit will retreat
|
||||||
BREAKTHROUGH = 3 # Unit will attempt a breakthrough, rushing forward very aggresively with big group of armored units, and even less armored units will move aggresively
|
BREAKTHROUGH = 3 # Unit will attempt a breakthrough, rushing forward very aggresively with big group of armored units, and even less armored units will move aggresively
|
||||||
ELIMINATION = 4 # Unit will progress aggresively toward anemy units, attempting to eliminate the ennemy force
|
ELIMINATION = 4 # Unit will progress aggresively toward anemy units, attempting to eliminate the ennemy force
|
||||||
AMBUSH = 5 # Units will adopt a defensive stance a bit different from 'DEFENSIVE', ATGM & INFANTRY with RPG will be located on frontline with the armored units. (The groups of units will be smaller)
|
AMBUSH = 5 # Units will adopt a defensive stance a bit different from 'DEFENSIVE', ATGM & INFANTRY with RPG will be located on frontline with the armored units. (The groups of units will be smaller)
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,8 @@ from dcs.task import (
|
|||||||
ActivateBeaconCommand,
|
ActivateBeaconCommand,
|
||||||
ActivateICLSCommand,
|
ActivateICLSCommand,
|
||||||
EPLRS,
|
EPLRS,
|
||||||
OptAlarmState, FireAtPoint,
|
OptAlarmState,
|
||||||
|
FireAtPoint,
|
||||||
)
|
)
|
||||||
from dcs.unit import Ship, Unit, Vehicle
|
from dcs.unit import Ship, Unit, Vehicle
|
||||||
from dcs.unitgroup import Group, ShipGroup, StaticGroup, VehicleGroup
|
from dcs.unitgroup import Group, ShipGroup, StaticGroup, VehicleGroup
|
||||||
@ -30,9 +31,12 @@ from game.data.building_data import FORTIFICATION_UNITS, FORTIFICATION_UNITS_ID
|
|||||||
from game.db import unit_type_from_name
|
from game.db import unit_type_from_name
|
||||||
from game.theater import ControlPoint, TheaterGroundObject
|
from game.theater import ControlPoint, TheaterGroundObject
|
||||||
from game.theater.theatergroundobject import (
|
from game.theater.theatergroundobject import (
|
||||||
BuildingGroundObject, CarrierGroundObject,
|
BuildingGroundObject,
|
||||||
|
CarrierGroundObject,
|
||||||
GenericCarrierGroundObject,
|
GenericCarrierGroundObject,
|
||||||
LhaGroundObject, ShipGroundObject, MissileSiteGroundObject,
|
LhaGroundObject,
|
||||||
|
ShipGroundObject,
|
||||||
|
MissileSiteGroundObject,
|
||||||
)
|
)
|
||||||
from game.unitmap import UnitMap
|
from game.unitmap import UnitMap
|
||||||
from game.utils import knots, mps
|
from game.utils import knots, mps
|
||||||
@ -53,8 +57,15 @@ class GenericGroundObjectGenerator:
|
|||||||
|
|
||||||
Currently used only for SAM
|
Currently used only for SAM
|
||||||
"""
|
"""
|
||||||
def __init__(self, ground_object: TheaterGroundObject, country: Country,
|
|
||||||
game: Game, mission: Mission, unit_map: UnitMap) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
ground_object: TheaterGroundObject,
|
||||||
|
country: Country,
|
||||||
|
game: Game,
|
||||||
|
mission: Mission,
|
||||||
|
unit_map: UnitMap,
|
||||||
|
) -> None:
|
||||||
self.ground_object = ground_object
|
self.ground_object = ground_object
|
||||||
self.country = country
|
self.country = country
|
||||||
self.game = game
|
self.game = game
|
||||||
@ -72,18 +83,22 @@ class GenericGroundObjectGenerator:
|
|||||||
|
|
||||||
unit_type = unit_type_from_name(group.units[0].type)
|
unit_type = unit_type_from_name(group.units[0].type)
|
||||||
if unit_type is None:
|
if unit_type is None:
|
||||||
raise RuntimeError(
|
raise RuntimeError(f"Unrecognized unit type: {group.units[0].type}")
|
||||||
f"Unrecognized unit type: {group.units[0].type}")
|
|
||||||
|
|
||||||
vg = self.m.vehicle_group(self.country, group.name, unit_type,
|
vg = self.m.vehicle_group(
|
||||||
|
self.country,
|
||||||
|
group.name,
|
||||||
|
unit_type,
|
||||||
position=group.position,
|
position=group.position,
|
||||||
heading=group.units[0].heading)
|
heading=group.units[0].heading,
|
||||||
|
)
|
||||||
vg.units[0].name = self.m.string(group.units[0].name)
|
vg.units[0].name = self.m.string(group.units[0].name)
|
||||||
vg.units[0].player_can_drive = True
|
vg.units[0].player_can_drive = True
|
||||||
for i, u in enumerate(group.units):
|
for i, u in enumerate(group.units):
|
||||||
if i > 0:
|
if i > 0:
|
||||||
vehicle = Vehicle(self.m.next_unit_id(),
|
vehicle = Vehicle(
|
||||||
self.m.string(u.name), u.type)
|
self.m.next_unit_id(), self.m.string(u.name), u.type
|
||||||
|
)
|
||||||
vehicle.position.x = u.position.x
|
vehicle.position.x = u.position.x
|
||||||
vehicle.position.y = u.position.y
|
vehicle.position.y = u.position.y
|
||||||
vehicle.heading = u.heading
|
vehicle.heading = u.heading
|
||||||
@ -96,7 +111,7 @@ class GenericGroundObjectGenerator:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def enable_eplrs(group: Group, unit_type: Type[UnitType]) -> None:
|
def enable_eplrs(group: Group, unit_type: Type[UnitType]) -> None:
|
||||||
if hasattr(unit_type, 'eplrs'):
|
if hasattr(unit_type, "eplrs"):
|
||||||
if unit_type.eplrs:
|
if unit_type.eplrs:
|
||||||
group.points[0].tasks.append(EPLRS(group.id))
|
group.points[0].tasks.append(EPLRS(group.id))
|
||||||
|
|
||||||
@ -106,14 +121,13 @@ class GenericGroundObjectGenerator:
|
|||||||
else:
|
else:
|
||||||
group.points[0].tasks.append(OptAlarmState(1))
|
group.points[0].tasks.append(OptAlarmState(1))
|
||||||
|
|
||||||
def _register_unit_group(self, persistence_group: Group,
|
def _register_unit_group(self, persistence_group: Group, miz_group: Group) -> None:
|
||||||
miz_group: Group) -> None:
|
self.unit_map.add_ground_object_units(
|
||||||
self.unit_map.add_ground_object_units(self.ground_object,
|
self.ground_object, persistence_group, miz_group
|
||||||
persistence_group, miz_group)
|
)
|
||||||
|
|
||||||
|
|
||||||
class MissileSiteGenerator(GenericGroundObjectGenerator):
|
class MissileSiteGenerator(GenericGroundObjectGenerator):
|
||||||
|
|
||||||
def generate(self) -> None:
|
def generate(self) -> None:
|
||||||
super(MissileSiteGenerator, self).generate()
|
super(MissileSiteGenerator, self).generate()
|
||||||
# Note : Only the SCUD missiles group can fire (V1 site cannot fire in game right now)
|
# Note : Only the SCUD missiles group can fire (V1 site cannot fire in game right now)
|
||||||
@ -125,13 +139,19 @@ class MissileSiteGenerator(GenericGroundObjectGenerator):
|
|||||||
targets = self.possible_missile_targets(vg)
|
targets = self.possible_missile_targets(vg)
|
||||||
if targets:
|
if targets:
|
||||||
target = random.choice(targets)
|
target = random.choice(targets)
|
||||||
real_target = target.point_from_heading(random.randint(0, 360), random.randint(0, 2500))
|
real_target = target.point_from_heading(
|
||||||
|
random.randint(0, 360), random.randint(0, 2500)
|
||||||
|
)
|
||||||
vg.points[0].add_task(FireAtPoint(real_target))
|
vg.points[0].add_task(FireAtPoint(real_target))
|
||||||
logging.info("Set up fire task for missile group.")
|
logging.info("Set up fire task for missile group.")
|
||||||
else:
|
else:
|
||||||
logging.info("Couldn't setup missile site to fire, no valid target in range.")
|
logging.info(
|
||||||
|
"Couldn't setup missile site to fire, no valid target in range."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logging.info("Couldn't setup missile site to fire, group was not generated.")
|
logging.info(
|
||||||
|
"Couldn't setup missile site to fire, group was not generated."
|
||||||
|
)
|
||||||
|
|
||||||
def possible_missile_targets(self, vg: Group) -> List[Point]:
|
def possible_missile_targets(self, vg: Group) -> List[Point]:
|
||||||
"""
|
"""
|
||||||
@ -190,7 +210,8 @@ class BuildingSiteGenerator(GenericGroundObjectGenerator):
|
|||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
logging.error(
|
logging.error(
|
||||||
f"{self.ground_object.dcs_identifier} not found in static maps")
|
f"{self.ground_object.dcs_identifier} not found in static maps"
|
||||||
|
)
|
||||||
|
|
||||||
def generate_vehicle_group(self, unit_type: UnitType) -> None:
|
def generate_vehicle_group(self, unit_type: UnitType) -> None:
|
||||||
if not self.ground_object.is_dead:
|
if not self.ground_object.is_dead:
|
||||||
@ -228,11 +249,20 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator):
|
|||||||
|
|
||||||
Used by both CV(N) groups and LHA groups.
|
Used by both CV(N) groups and LHA groups.
|
||||||
"""
|
"""
|
||||||
def __init__(self, ground_object: GenericCarrierGroundObject,
|
|
||||||
control_point: ControlPoint, country: Country, game: Game,
|
def __init__(
|
||||||
mission: Mission, radio_registry: RadioRegistry,
|
self,
|
||||||
tacan_registry: TacanRegistry, icls_alloc: Iterator[int],
|
ground_object: GenericCarrierGroundObject,
|
||||||
runways: Dict[str, RunwayData], unit_map: UnitMap) -> None:
|
control_point: ControlPoint,
|
||||||
|
country: Country,
|
||||||
|
game: Game,
|
||||||
|
mission: Mission,
|
||||||
|
radio_registry: RadioRegistry,
|
||||||
|
tacan_registry: TacanRegistry,
|
||||||
|
icls_alloc: Iterator[int],
|
||||||
|
runways: Dict[str, RunwayData],
|
||||||
|
unit_map: UnitMap,
|
||||||
|
) -> None:
|
||||||
super().__init__(ground_object, country, game, mission, unit_map)
|
super().__init__(ground_object, country, game, mission, unit_map)
|
||||||
self.ground_object = ground_object
|
self.ground_object = ground_object
|
||||||
self.control_point = control_point
|
self.control_point = control_point
|
||||||
@ -245,8 +275,7 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator):
|
|||||||
# TODO: Require single group?
|
# TODO: Require single group?
|
||||||
for group in self.ground_object.groups:
|
for group in self.ground_object.groups:
|
||||||
if not group.units:
|
if not group.units:
|
||||||
logging.warning(
|
logging.warning(f"Found empty carrier group in {self.control_point}")
|
||||||
f"Found empty carrier group in {self.control_point}")
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
atc = self.radio_registry.alloc_uhf()
|
atc = self.radio_registry.alloc_uhf()
|
||||||
@ -270,25 +299,29 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator):
|
|||||||
def get_carrier_type(self, group: Group) -> Type[UnitType]:
|
def get_carrier_type(self, group: Group) -> Type[UnitType]:
|
||||||
unit_type = unit_type_from_name(group.units[0].type)
|
unit_type = unit_type_from_name(group.units[0].type)
|
||||||
if unit_type is None:
|
if unit_type is None:
|
||||||
raise RuntimeError(
|
raise RuntimeError(f"Unrecognized carrier name: {group.units[0].type}")
|
||||||
f"Unrecognized carrier name: {group.units[0].type}")
|
|
||||||
return unit_type
|
return unit_type
|
||||||
|
|
||||||
def configure_carrier(self, group: Group,
|
def configure_carrier(self, group: Group, atc_channel: RadioFrequency) -> ShipGroup:
|
||||||
atc_channel: RadioFrequency) -> ShipGroup:
|
|
||||||
unit_type = self.get_carrier_type(group)
|
unit_type = self.get_carrier_type(group)
|
||||||
|
|
||||||
ship_group = self.m.ship_group(self.country, group.name, unit_type,
|
ship_group = self.m.ship_group(
|
||||||
|
self.country,
|
||||||
|
group.name,
|
||||||
|
unit_type,
|
||||||
position=group.position,
|
position=group.position,
|
||||||
heading=group.units[0].heading)
|
heading=group.units[0].heading,
|
||||||
|
)
|
||||||
ship_group.set_frequency(atc_channel.hertz)
|
ship_group.set_frequency(atc_channel.hertz)
|
||||||
ship_group.units[0].name = self.m.string(group.units[0].name)
|
ship_group.units[0].name = self.m.string(group.units[0].name)
|
||||||
return ship_group
|
return ship_group
|
||||||
|
|
||||||
def create_ship(self, unit: Unit, atc_channel: RadioFrequency) -> Ship:
|
def create_ship(self, unit: Unit, atc_channel: RadioFrequency) -> Ship:
|
||||||
ship = Ship(self.m.next_unit_id(),
|
ship = Ship(
|
||||||
|
self.m.next_unit_id(),
|
||||||
self.m.string(unit.name),
|
self.m.string(unit.name),
|
||||||
unit_type_from_name(unit.type))
|
unit_type_from_name(unit.type),
|
||||||
|
)
|
||||||
ship.position.x = unit.position.x
|
ship.position.x = unit.position.x
|
||||||
ship.position.y = unit.position.y
|
ship.position.y = unit.position.y
|
||||||
ship.heading = unit.heading
|
ship.heading = unit.heading
|
||||||
@ -303,7 +336,8 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator):
|
|||||||
carrier_speed = knots(25) - mps(wind.speed)
|
carrier_speed = knots(25) - mps(wind.speed)
|
||||||
for attempt in range(5):
|
for attempt in range(5):
|
||||||
point = group.points[0].position.point_from_heading(
|
point = group.points[0].position.point_from_heading(
|
||||||
brc, 100000 - attempt * 20000)
|
brc, 100000 - attempt * 20000
|
||||||
|
)
|
||||||
if self.game.theater.is_in_sea(point):
|
if self.game.theater.is_in_sea(point):
|
||||||
group.points[0].speed = carrier_speed.meters_per_second
|
group.points[0].speed = carrier_speed.meters_per_second
|
||||||
group.add_waypoint(point, carrier_speed.kph)
|
group.add_waypoint(point, carrier_speed.kph)
|
||||||
@ -314,21 +348,30 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def activate_beacons(group: ShipGroup, tacan: TacanChannel,
|
def activate_beacons(
|
||||||
callsign: str, icls: int) -> None:
|
group: ShipGroup, tacan: TacanChannel, callsign: str, icls: int
|
||||||
group.points[0].tasks.append(ActivateBeaconCommand(
|
) -> None:
|
||||||
|
group.points[0].tasks.append(
|
||||||
|
ActivateBeaconCommand(
|
||||||
channel=tacan.number,
|
channel=tacan.number,
|
||||||
modechannel=tacan.band.value,
|
modechannel=tacan.band.value,
|
||||||
callsign=callsign,
|
callsign=callsign,
|
||||||
unit_id=group.units[0].id,
|
unit_id=group.units[0].id,
|
||||||
aa=False
|
aa=False,
|
||||||
))
|
)
|
||||||
group.points[0].tasks.append(ActivateICLSCommand(
|
)
|
||||||
icls, unit_id=group.units[0].id
|
group.points[0].tasks.append(
|
||||||
))
|
ActivateICLSCommand(icls, unit_id=group.units[0].id)
|
||||||
|
)
|
||||||
|
|
||||||
def add_runway_data(self, brc: int, atc: RadioFrequency,
|
def add_runway_data(
|
||||||
tacan: TacanChannel, callsign: str, icls: int) -> None:
|
self,
|
||||||
|
brc: int,
|
||||||
|
atc: RadioFrequency,
|
||||||
|
tacan: TacanChannel,
|
||||||
|
callsign: str,
|
||||||
|
icls: int,
|
||||||
|
) -> None:
|
||||||
# TODO: Make unit name usable.
|
# TODO: Make unit name usable.
|
||||||
# This relies on one control point mapping exactly
|
# This relies on one control point mapping exactly
|
||||||
# to one LHA, carrier, or other usable "runway".
|
# to one LHA, carrier, or other usable "runway".
|
||||||
@ -354,8 +397,7 @@ class CarrierGenerator(GenericCarrierGenerator):
|
|||||||
def get_carrier_type(self, group: Group) -> UnitType:
|
def get_carrier_type(self, group: Group) -> UnitType:
|
||||||
unit_type = super().get_carrier_type(group)
|
unit_type = super().get_carrier_type(group)
|
||||||
if self.game.settings.supercarrier:
|
if self.game.settings.supercarrier:
|
||||||
unit_type = db.upgrade_to_supercarrier(unit_type,
|
unit_type = db.upgrade_to_supercarrier(unit_type, self.control_point.name)
|
||||||
self.control_point.name)
|
|
||||||
return unit_type
|
return unit_type
|
||||||
|
|
||||||
def tacan_callsign(self) -> str:
|
def tacan_callsign(self) -> str:
|
||||||
@ -363,7 +405,8 @@ class CarrierGenerator(GenericCarrierGenerator):
|
|||||||
if self.control_point.name == "Carrier Strike Group 8":
|
if self.control_point.name == "Carrier Strike Group 8":
|
||||||
return "TRU"
|
return "TRU"
|
||||||
else:
|
else:
|
||||||
return random.choice([
|
return random.choice(
|
||||||
|
[
|
||||||
"STE",
|
"STE",
|
||||||
"CVN",
|
"CVN",
|
||||||
"CVH",
|
"CVH",
|
||||||
@ -374,7 +417,8 @@ class CarrierGenerator(GenericCarrierGenerator):
|
|||||||
"ABR",
|
"ABR",
|
||||||
"LIN",
|
"LIN",
|
||||||
"TRU",
|
"TRU",
|
||||||
])
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LhaGenerator(GenericCarrierGenerator):
|
class LhaGenerator(GenericCarrierGenerator):
|
||||||
@ -382,14 +426,16 @@ class LhaGenerator(GenericCarrierGenerator):
|
|||||||
|
|
||||||
def tacan_callsign(self) -> str:
|
def tacan_callsign(self) -> str:
|
||||||
# TODO: Assign these properly.
|
# TODO: Assign these properly.
|
||||||
return random.choice([
|
return random.choice(
|
||||||
|
[
|
||||||
"LHD",
|
"LHD",
|
||||||
"LHA",
|
"LHA",
|
||||||
"LHB",
|
"LHB",
|
||||||
"LHC",
|
"LHC",
|
||||||
"LHD",
|
"LHD",
|
||||||
"LDS",
|
"LDS",
|
||||||
])
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ShipObjectGenerator(GenericGroundObjectGenerator):
|
class ShipObjectGenerator(GenericGroundObjectGenerator):
|
||||||
@ -406,22 +452,23 @@ class ShipObjectGenerator(GenericGroundObjectGenerator):
|
|||||||
|
|
||||||
unit_type = unit_type_from_name(group.units[0].type)
|
unit_type = unit_type_from_name(group.units[0].type)
|
||||||
if unit_type is None:
|
if unit_type is None:
|
||||||
raise RuntimeError(
|
raise RuntimeError(f"Unrecognized unit type: {group.units[0].type}")
|
||||||
f"Unrecognized unit type: {group.units[0].type}")
|
|
||||||
|
|
||||||
self.generate_group(group, unit_type)
|
self.generate_group(group, unit_type)
|
||||||
|
|
||||||
def generate_group(self, group_def: Group,
|
def generate_group(self, group_def: Group, first_unit_type: Type[UnitType]) -> None:
|
||||||
first_unit_type: Type[UnitType]) -> None:
|
group = self.m.ship_group(
|
||||||
group = self.m.ship_group(self.country, group_def.name, first_unit_type,
|
self.country,
|
||||||
|
group_def.name,
|
||||||
|
first_unit_type,
|
||||||
position=group_def.position,
|
position=group_def.position,
|
||||||
heading=group_def.units[0].heading)
|
heading=group_def.units[0].heading,
|
||||||
|
)
|
||||||
group.units[0].name = self.m.string(group_def.units[0].name)
|
group.units[0].name = self.m.string(group_def.units[0].name)
|
||||||
# TODO: Skipping the first unit looks like copy pasta from the carrier.
|
# TODO: Skipping the first unit looks like copy pasta from the carrier.
|
||||||
for unit in group_def.units[1:]:
|
for unit in group_def.units[1:]:
|
||||||
unit_type = unit_type_from_name(unit.type)
|
unit_type = unit_type_from_name(unit.type)
|
||||||
ship = Ship(self.m.next_unit_id(),
|
ship = Ship(self.m.next_unit_id(), self.m.string(unit.name), unit_type)
|
||||||
self.m.string(unit.name), unit_type)
|
|
||||||
ship.position.x = unit.position.x
|
ship.position.x = unit.position.x
|
||||||
ship.position.y = unit.position.y
|
ship.position.y = unit.position.y
|
||||||
ship.heading = unit.heading
|
ship.heading = unit.heading
|
||||||
@ -439,9 +486,14 @@ class GroundObjectsGenerator:
|
|||||||
the appropriate generators.
|
the appropriate generators.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, mission: Mission, game: Game,
|
def __init__(
|
||||||
radio_registry: RadioRegistry, tacan_registry: TacanRegistry,
|
self,
|
||||||
unit_map: UnitMap) -> None:
|
mission: Mission,
|
||||||
|
game: Game,
|
||||||
|
radio_registry: RadioRegistry,
|
||||||
|
tacan_registry: TacanRegistry,
|
||||||
|
unit_map: UnitMap,
|
||||||
|
) -> None:
|
||||||
self.m = mission
|
self.m = mission
|
||||||
self.game = game
|
self.game = game
|
||||||
self.radio_registry = radio_registry
|
self.radio_registry = radio_registry
|
||||||
@ -461,28 +513,44 @@ class GroundObjectsGenerator:
|
|||||||
for ground_object in cp.ground_objects:
|
for ground_object in cp.ground_objects:
|
||||||
if isinstance(ground_object, BuildingGroundObject):
|
if isinstance(ground_object, BuildingGroundObject):
|
||||||
generator = BuildingSiteGenerator(
|
generator = BuildingSiteGenerator(
|
||||||
ground_object, country, self.game, self.m,
|
ground_object, country, self.game, self.m, self.unit_map
|
||||||
self.unit_map)
|
)
|
||||||
elif isinstance(ground_object, CarrierGroundObject):
|
elif isinstance(ground_object, CarrierGroundObject):
|
||||||
generator = CarrierGenerator(
|
generator = CarrierGenerator(
|
||||||
ground_object, cp, country, self.game, self.m,
|
ground_object,
|
||||||
self.radio_registry, self.tacan_registry,
|
cp,
|
||||||
self.icls_alloc, self.runways, self.unit_map)
|
country,
|
||||||
|
self.game,
|
||||||
|
self.m,
|
||||||
|
self.radio_registry,
|
||||||
|
self.tacan_registry,
|
||||||
|
self.icls_alloc,
|
||||||
|
self.runways,
|
||||||
|
self.unit_map,
|
||||||
|
)
|
||||||
elif isinstance(ground_object, LhaGroundObject):
|
elif isinstance(ground_object, LhaGroundObject):
|
||||||
generator = CarrierGenerator(
|
generator = CarrierGenerator(
|
||||||
ground_object, cp, country, self.game, self.m,
|
ground_object,
|
||||||
self.radio_registry, self.tacan_registry,
|
cp,
|
||||||
self.icls_alloc, self.runways, self.unit_map)
|
country,
|
||||||
|
self.game,
|
||||||
|
self.m,
|
||||||
|
self.radio_registry,
|
||||||
|
self.tacan_registry,
|
||||||
|
self.icls_alloc,
|
||||||
|
self.runways,
|
||||||
|
self.unit_map,
|
||||||
|
)
|
||||||
elif isinstance(ground_object, ShipGroundObject):
|
elif isinstance(ground_object, ShipGroundObject):
|
||||||
generator = ShipObjectGenerator(
|
generator = ShipObjectGenerator(
|
||||||
ground_object, country, self.game, self.m,
|
ground_object, country, self.game, self.m, self.unit_map
|
||||||
self.unit_map)
|
)
|
||||||
elif isinstance(ground_object, MissileSiteGroundObject):
|
elif isinstance(ground_object, MissileSiteGroundObject):
|
||||||
generator = MissileSiteGenerator(
|
generator = MissileSiteGenerator(
|
||||||
ground_object, country, self.game, self.m,
|
ground_object, country, self.game, self.m, self.unit_map
|
||||||
self.unit_map)
|
)
|
||||||
else:
|
else:
|
||||||
generator = GenericGroundObjectGenerator(
|
generator = GenericGroundObjectGenerator(
|
||||||
ground_object, country, self.game, self.m,
|
ground_object, country, self.game, self.m, self.unit_map
|
||||||
self.unit_map)
|
)
|
||||||
generator.generate()
|
generator.generate()
|
||||||
|
|||||||
113
gen/kneeboard.py
113
gen/kneeboard.py
@ -49,7 +49,7 @@ class KneeboardPageWriter:
|
|||||||
"""Creates kneeboard images."""
|
"""Creates kneeboard images."""
|
||||||
|
|
||||||
def __init__(self, page_margin: int = 24, line_spacing: int = 12) -> None:
|
def __init__(self, page_margin: int = 24, line_spacing: int = 12) -> None:
|
||||||
self.image = Image.new('RGB', (768, 1024), (0xff, 0xff, 0xff))
|
self.image = Image.new("RGB", (768, 1024), (0xFF, 0xFF, 0xFF))
|
||||||
# These font sizes create a relatively full page for current sorties. If
|
# These font sizes create a relatively full page for current sorties. If
|
||||||
# we start generating more complicated flight plans, or start including
|
# we start generating more complicated flight plans, or start including
|
||||||
# more information in the comm ladder (the latter of which we should
|
# more information in the comm ladder (the latter of which we should
|
||||||
@ -58,8 +58,7 @@ class KneeboardPageWriter:
|
|||||||
self.title_font = ImageFont.truetype("arial.ttf", 32)
|
self.title_font = ImageFont.truetype("arial.ttf", 32)
|
||||||
self.heading_font = ImageFont.truetype("arial.ttf", 24)
|
self.heading_font = ImageFont.truetype("arial.ttf", 24)
|
||||||
self.content_font = ImageFont.truetype("arial.ttf", 20)
|
self.content_font = ImageFont.truetype("arial.ttf", 20)
|
||||||
self.table_font = ImageFont.truetype(
|
self.table_font = ImageFont.truetype("resources/fonts/Inconsolata.otf", 20)
|
||||||
"resources/fonts/Inconsolata.otf", 20)
|
|
||||||
self.draw = ImageDraw.Draw(self.image)
|
self.draw = ImageDraw.Draw(self.image)
|
||||||
self.x = page_margin
|
self.x = page_margin
|
||||||
self.y = page_margin
|
self.y = page_margin
|
||||||
@ -69,8 +68,9 @@ class KneeboardPageWriter:
|
|||||||
def position(self) -> Tuple[int, int]:
|
def position(self) -> Tuple[int, int]:
|
||||||
return self.x, self.y
|
return self.x, self.y
|
||||||
|
|
||||||
def text(self, text: str, font=None,
|
def text(
|
||||||
fill: Tuple[int, int, int] = (0, 0, 0)) -> None:
|
self, text: str, font=None, fill: Tuple[int, int, int] = (0, 0, 0)
|
||||||
|
) -> None:
|
||||||
if font is None:
|
if font is None:
|
||||||
font = self.content_font
|
font = self.content_font
|
||||||
|
|
||||||
@ -84,8 +84,9 @@ class KneeboardPageWriter:
|
|||||||
def heading(self, text: str) -> None:
|
def heading(self, text: str) -> None:
|
||||||
self.text(text, font=self.heading_font)
|
self.text(text, font=self.heading_font)
|
||||||
|
|
||||||
def table(self, cells: List[List[str]],
|
def table(
|
||||||
headers: Optional[List[str]] = None) -> None:
|
self, cells: List[List[str]], headers: Optional[List[str]] = None
|
||||||
|
) -> None:
|
||||||
if headers is None:
|
if headers is None:
|
||||||
headers = []
|
headers = []
|
||||||
table = tabulate(cells, headers=headers, numalign="right")
|
table = tabulate(cells, headers=headers, numalign="right")
|
||||||
@ -157,7 +158,8 @@ class FlightPlanBuilder:
|
|||||||
first_waypoint_num = self.target_points[0].number
|
first_waypoint_num = self.target_points[0].number
|
||||||
last_waypoint_num = self.target_points[-1].number
|
last_waypoint_num = self.target_points[-1].number
|
||||||
|
|
||||||
self.rows.append([
|
self.rows.append(
|
||||||
|
[
|
||||||
f"{first_waypoint_num}-{last_waypoint_num}",
|
f"{first_waypoint_num}-{last_waypoint_num}",
|
||||||
"Target points",
|
"Target points",
|
||||||
"0",
|
"0",
|
||||||
@ -165,21 +167,25 @@ class FlightPlanBuilder:
|
|||||||
self._ground_speed(self.target_points[0].waypoint),
|
self._ground_speed(self.target_points[0].waypoint),
|
||||||
self._format_time(self.target_points[0].waypoint.tot),
|
self._format_time(self.target_points[0].waypoint.tot),
|
||||||
self._format_time(self.target_points[0].waypoint.departure_time),
|
self._format_time(self.target_points[0].waypoint.departure_time),
|
||||||
])
|
]
|
||||||
|
)
|
||||||
self.last_waypoint = self.target_points[-1].waypoint
|
self.last_waypoint = self.target_points[-1].waypoint
|
||||||
|
|
||||||
def add_waypoint_row(self, waypoint: NumberedWaypoint) -> None:
|
def add_waypoint_row(self, waypoint: NumberedWaypoint) -> None:
|
||||||
self.rows.append([
|
self.rows.append(
|
||||||
|
[
|
||||||
str(waypoint.number),
|
str(waypoint.number),
|
||||||
KneeboardPageWriter.wrap_line(
|
KneeboardPageWriter.wrap_line(
|
||||||
waypoint.waypoint.pretty_name,
|
waypoint.waypoint.pretty_name,
|
||||||
FlightPlanBuilder.WAYPOINT_DESC_MAX_LEN),
|
FlightPlanBuilder.WAYPOINT_DESC_MAX_LEN,
|
||||||
|
),
|
||||||
str(int(waypoint.waypoint.alt.feet)),
|
str(int(waypoint.waypoint.alt.feet)),
|
||||||
self._waypoint_distance(waypoint.waypoint),
|
self._waypoint_distance(waypoint.waypoint),
|
||||||
self._ground_speed(waypoint.waypoint),
|
self._ground_speed(waypoint.waypoint),
|
||||||
self._format_time(waypoint.waypoint.tot),
|
self._format_time(waypoint.waypoint.tot),
|
||||||
self._format_time(waypoint.waypoint.departure_time),
|
self._format_time(waypoint.waypoint.departure_time),
|
||||||
])
|
]
|
||||||
|
)
|
||||||
|
|
||||||
def _format_time(self, time: Optional[datetime.timedelta]) -> str:
|
def _format_time(self, time: Optional[datetime.timedelta]) -> str:
|
||||||
if time is None:
|
if time is None:
|
||||||
@ -191,9 +197,9 @@ class FlightPlanBuilder:
|
|||||||
if self.last_waypoint is None:
|
if self.last_waypoint is None:
|
||||||
return "-"
|
return "-"
|
||||||
|
|
||||||
distance = meters(self.last_waypoint.position.distance_to_point(
|
distance = meters(
|
||||||
waypoint.position
|
self.last_waypoint.position.distance_to_point(waypoint.position)
|
||||||
))
|
)
|
||||||
return f"{distance.nautical_miles:.1f} NM"
|
return f"{distance.nautical_miles:.1f} NM"
|
||||||
|
|
||||||
def _ground_speed(self, waypoint: FlightWaypoint) -> str:
|
def _ground_speed(self, waypoint: FlightWaypoint) -> str:
|
||||||
@ -210,9 +216,9 @@ class FlightPlanBuilder:
|
|||||||
else:
|
else:
|
||||||
return "-"
|
return "-"
|
||||||
|
|
||||||
distance = meters(self.last_waypoint.position.distance_to_point(
|
distance = meters(
|
||||||
waypoint.position
|
self.last_waypoint.position.distance_to_point(waypoint.position)
|
||||||
))
|
)
|
||||||
duration = (waypoint.tot - last_time).total_seconds() / 3600
|
duration = (waypoint.tot - last_time).total_seconds() / 3600
|
||||||
return f"{int(distance.nautical_miles / duration)} kt"
|
return f"{int(distance.nautical_miles / duration)} kt"
|
||||||
|
|
||||||
@ -222,9 +228,16 @@ class FlightPlanBuilder:
|
|||||||
|
|
||||||
class BriefingPage(KneeboardPage):
|
class BriefingPage(KneeboardPage):
|
||||||
"""A kneeboard page containing briefing information."""
|
"""A kneeboard page containing briefing information."""
|
||||||
def __init__(self, flight: FlightData, comms: List[CommInfo],
|
|
||||||
awacs: List[AwacsInfo], tankers: List[TankerInfo],
|
def __init__(
|
||||||
jtacs: List[JtacInfo], start_time: datetime.datetime) -> None:
|
self,
|
||||||
|
flight: FlightData,
|
||||||
|
comms: List[CommInfo],
|
||||||
|
awacs: List[AwacsInfo],
|
||||||
|
tankers: List[TankerInfo],
|
||||||
|
jtacs: List[JtacInfo],
|
||||||
|
start_time: datetime.datetime,
|
||||||
|
) -> None:
|
||||||
self.flight = flight
|
self.flight = flight
|
||||||
self.comms = list(comms)
|
self.comms = list(comms)
|
||||||
self.awacs = awacs
|
self.awacs = awacs
|
||||||
@ -239,49 +252,59 @@ class BriefingPage(KneeboardPage):
|
|||||||
|
|
||||||
# TODO: Handle carriers.
|
# TODO: Handle carriers.
|
||||||
writer.heading("Airfield Info")
|
writer.heading("Airfield Info")
|
||||||
writer.table([
|
writer.table(
|
||||||
|
[
|
||||||
self.airfield_info_row("Departure", self.flight.departure),
|
self.airfield_info_row("Departure", self.flight.departure),
|
||||||
self.airfield_info_row("Arrival", self.flight.arrival),
|
self.airfield_info_row("Arrival", self.flight.arrival),
|
||||||
self.airfield_info_row("Divert", self.flight.divert),
|
self.airfield_info_row("Divert", self.flight.divert),
|
||||||
], headers=["", "Airbase", "ATC", "TCN", "I(C)LS", "RWY"])
|
],
|
||||||
|
headers=["", "Airbase", "ATC", "TCN", "I(C)LS", "RWY"],
|
||||||
|
)
|
||||||
|
|
||||||
writer.heading("Flight Plan")
|
writer.heading("Flight Plan")
|
||||||
flight_plan_builder = FlightPlanBuilder(self.start_time)
|
flight_plan_builder = FlightPlanBuilder(self.start_time)
|
||||||
for num, waypoint in enumerate(self.flight.waypoints):
|
for num, waypoint in enumerate(self.flight.waypoints):
|
||||||
flight_plan_builder.add_waypoint(num, waypoint)
|
flight_plan_builder.add_waypoint(num, waypoint)
|
||||||
writer.table(flight_plan_builder.build(), headers=[
|
writer.table(
|
||||||
"#", "Action", "Alt", "Dist", "GSPD", "Time", "Departure"
|
flight_plan_builder.build(),
|
||||||
])
|
headers=["#", "Action", "Alt", "Dist", "GSPD", "Time", "Departure"],
|
||||||
|
)
|
||||||
|
|
||||||
flight_plan_builder
|
flight_plan_builder
|
||||||
writer.table([
|
writer.table(
|
||||||
["{}lbs".format(self.flight.bingo_fuel), "{}lbs".format(self.flight.joker_fuel)]
|
[
|
||||||
], ['Bingo', 'Joker'])
|
[
|
||||||
|
"{}lbs".format(self.flight.bingo_fuel),
|
||||||
|
"{}lbs".format(self.flight.joker_fuel),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
["Bingo", "Joker"],
|
||||||
|
)
|
||||||
|
|
||||||
# Package Section
|
# Package Section
|
||||||
writer.heading("Comm ladder")
|
writer.heading("Comm ladder")
|
||||||
comm_ladder = []
|
comm_ladder = []
|
||||||
for comm in self.comms:
|
for comm in self.comms:
|
||||||
comm_ladder.append([comm.name, '', '', '', self.format_frequency(comm.freq)])
|
comm_ladder.append(
|
||||||
|
[comm.name, "", "", "", self.format_frequency(comm.freq)]
|
||||||
|
)
|
||||||
|
|
||||||
for a in self.awacs:
|
for a in self.awacs:
|
||||||
comm_ladder.append([
|
comm_ladder.append(
|
||||||
a.callsign,
|
[a.callsign, "AWACS", "", "", self.format_frequency(a.freq)]
|
||||||
'AWACS',
|
)
|
||||||
'',
|
|
||||||
'',
|
|
||||||
self.format_frequency(a.freq)
|
|
||||||
])
|
|
||||||
for tanker in self.tankers:
|
for tanker in self.tankers:
|
||||||
comm_ladder.append([
|
comm_ladder.append(
|
||||||
|
[
|
||||||
tanker.callsign,
|
tanker.callsign,
|
||||||
"Tanker",
|
"Tanker",
|
||||||
tanker.variant,
|
tanker.variant,
|
||||||
str(tanker.tacan),
|
str(tanker.tacan),
|
||||||
self.format_frequency(tanker.freq),
|
self.format_frequency(tanker.freq),
|
||||||
])
|
]
|
||||||
|
)
|
||||||
|
|
||||||
writer.table(comm_ladder, headers=["Callsign","Task", "Type", "TACAN", "FREQ"])
|
writer.table(comm_ladder, headers=["Callsign", "Task", "Type", "TACAN", "FREQ"])
|
||||||
|
|
||||||
writer.heading("JTAC")
|
writer.heading("JTAC")
|
||||||
jtacs = []
|
jtacs = []
|
||||||
@ -291,8 +314,9 @@ class BriefingPage(KneeboardPage):
|
|||||||
|
|
||||||
writer.write(path)
|
writer.write(path)
|
||||||
|
|
||||||
def airfield_info_row(self, row_title: str,
|
def airfield_info_row(
|
||||||
runway: Optional[RunwayData]) -> List[str]:
|
self, row_title: str, runway: Optional[RunwayData]
|
||||||
|
) -> List[str]:
|
||||||
"""Creates a table row for a given airfield.
|
"""Creates a table row for a given airfield.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -372,7 +396,8 @@ class KneeboardGenerator(MissionInfoGenerator):
|
|||||||
if not flight.client_units:
|
if not flight.client_units:
|
||||||
continue
|
continue
|
||||||
all_flights[flight.aircraft_type].extend(
|
all_flights[flight.aircraft_type].extend(
|
||||||
self.generate_flight_kneeboard(flight))
|
self.generate_flight_kneeboard(flight)
|
||||||
|
)
|
||||||
return all_flights
|
return all_flights
|
||||||
|
|
||||||
def generate_flight_kneeboard(self, flight: FlightData) -> List[KneeboardPage]:
|
def generate_flight_kneeboard(self, flight: FlightData) -> List[KneeboardPage]:
|
||||||
@ -384,6 +409,6 @@ class KneeboardGenerator(MissionInfoGenerator):
|
|||||||
self.awacs,
|
self.awacs,
|
||||||
self.tankers,
|
self.tankers,
|
||||||
self.jtacs,
|
self.jtacs,
|
||||||
self.mission.start_time
|
self.mission.start_time,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -9,9 +9,10 @@ from gen.locations.preset_locations import PresetLocation
|
|||||||
|
|
||||||
|
|
||||||
class MizDataLocationFinder:
|
class MizDataLocationFinder:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def compute_possible_locations(terrain_name: str, cp_name: str) -> PresetControlPointLocations:
|
def compute_possible_locations(
|
||||||
|
terrain_name: str, cp_name: str
|
||||||
|
) -> PresetControlPointLocations:
|
||||||
"""
|
"""
|
||||||
Extract the list of preset locations from miz data
|
Extract the list of preset locations from miz data
|
||||||
:param terrain_name: Terrain/Map name
|
:param terrain_name: Terrain/Map name
|
||||||
@ -32,28 +33,54 @@ class MizDataLocationFinder:
|
|||||||
|
|
||||||
for vehicle_group in m.country("USA").vehicle_group:
|
for vehicle_group in m.country("USA").vehicle_group:
|
||||||
if len(vehicle_group.units) > 0:
|
if len(vehicle_group.units) > 0:
|
||||||
ashore_locations.append(PresetLocation(vehicle_group.position,
|
ashore_locations.append(
|
||||||
|
PresetLocation(
|
||||||
|
vehicle_group.position,
|
||||||
vehicle_group.units[0].heading,
|
vehicle_group.units[0].heading,
|
||||||
vehicle_group.name))
|
vehicle_group.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
for ship_group in m.country("USA").ship_group:
|
for ship_group in m.country("USA").ship_group:
|
||||||
if len(ship_group.units) > 0 and ship_group.units[0].type == ships.Oliver_Hazzard_Perry_class.id:
|
if (
|
||||||
offshore_locations.append(PresetLocation(ship_group.position,
|
len(ship_group.units) > 0
|
||||||
|
and ship_group.units[0].type == ships.Oliver_Hazzard_Perry_class.id
|
||||||
|
):
|
||||||
|
offshore_locations.append(
|
||||||
|
PresetLocation(
|
||||||
|
ship_group.position,
|
||||||
ship_group.units[0].heading,
|
ship_group.units[0].heading,
|
||||||
ship_group.name))
|
ship_group.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
for static_group in m.country("USA").static_group:
|
for static_group in m.country("USA").static_group:
|
||||||
if len(static_group.units) > 0:
|
if len(static_group.units) > 0:
|
||||||
powerplants_locations.append(PresetLocation(static_group.position,
|
powerplants_locations.append(
|
||||||
|
PresetLocation(
|
||||||
|
static_group.position,
|
||||||
static_group.units[0].heading,
|
static_group.units[0].heading,
|
||||||
static_group.name))
|
static_group.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if m.country("Iran") is not None:
|
if m.country("Iran") is not None:
|
||||||
for vehicle_group in m.country("Iran").vehicle_group:
|
for vehicle_group in m.country("Iran").vehicle_group:
|
||||||
if len(vehicle_group.units) > 0 and vehicle_group.units[0].type == MissilesSS.SS_N_2_Silkworm.id:
|
if (
|
||||||
antiship_locations.append(PresetLocation(vehicle_group.position,
|
len(vehicle_group.units) > 0
|
||||||
|
and vehicle_group.units[0].type == MissilesSS.SS_N_2_Silkworm.id
|
||||||
|
):
|
||||||
|
antiship_locations.append(
|
||||||
|
PresetLocation(
|
||||||
|
vehicle_group.position,
|
||||||
vehicle_group.units[0].heading,
|
vehicle_group.units[0].heading,
|
||||||
vehicle_group.name))
|
vehicle_group.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return PresetControlPointLocations(ashore_locations, offshore_locations,
|
return PresetControlPointLocations(
|
||||||
antiship_locations, powerplants_locations)
|
ashore_locations,
|
||||||
|
offshore_locations,
|
||||||
|
antiship_locations,
|
||||||
|
powerplants_locations,
|
||||||
|
)
|
||||||
|
|||||||
@ -6,10 +6,16 @@ from dcs import Point
|
|||||||
@dataclass
|
@dataclass
|
||||||
class PresetLocation:
|
class PresetLocation:
|
||||||
"""A preset location"""
|
"""A preset location"""
|
||||||
|
|
||||||
position: Point
|
position: Point
|
||||||
heading: int
|
heading: int
|
||||||
id: str
|
id: str
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "-" * 10 + "X: {}\n Y: {}\nHdg: {}°\nId: {}".format(self.position.x, self.position.y, self.heading,
|
return (
|
||||||
self.id) + "-" * 10
|
"-" * 10
|
||||||
|
+ "X: {}\n Y: {}\nHdg: {}°\nId: {}".format(
|
||||||
|
self.position.x, self.position.y, self.heading, self.id
|
||||||
|
)
|
||||||
|
+ "-" * 10
|
||||||
|
)
|
||||||
|
|||||||
@ -4,10 +4,7 @@ from game import db
|
|||||||
from gen.missiles.scud_site import ScudGenerator
|
from gen.missiles.scud_site import ScudGenerator
|
||||||
from gen.missiles.v1_group import V1GroupGenerator
|
from gen.missiles.v1_group import V1GroupGenerator
|
||||||
|
|
||||||
MISSILES_MAP = {
|
MISSILES_MAP = {"V1GroupGenerator": V1GroupGenerator, "ScudGenerator": ScudGenerator}
|
||||||
"V1GroupGenerator": V1GroupGenerator,
|
|
||||||
"ScudGenerator": ScudGenerator
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def generate_missile_group(game, ground_object, faction_name: str):
|
def generate_missile_group(game, ground_object, faction_name: str):
|
||||||
@ -25,5 +22,9 @@ def generate_missile_group(game, ground_object, faction_name: str):
|
|||||||
generator.generate()
|
generator.generate()
|
||||||
return generator.get_generated_group()
|
return generator.get_generated_group()
|
||||||
else:
|
else:
|
||||||
logging.info("Unable to generate missile group, generator : " + str(gen) + "does not exists")
|
logging.info(
|
||||||
|
"Unable to generate missile group, generator : "
|
||||||
|
+ str(gen)
|
||||||
|
+ "does not exists"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
@ -6,7 +6,6 @@ from gen.sam.group_generator import GroupGenerator
|
|||||||
|
|
||||||
|
|
||||||
class ScudGenerator(GroupGenerator):
|
class ScudGenerator(GroupGenerator):
|
||||||
|
|
||||||
def __init__(self, game, ground_object, faction):
|
def __init__(self, game, ground_object, faction):
|
||||||
super(ScudGenerator, self).__init__(game, ground_object)
|
super(ScudGenerator, self).__init__(game, ground_object)
|
||||||
self.faction = faction
|
self.faction = faction
|
||||||
@ -14,17 +13,50 @@ class ScudGenerator(GroupGenerator):
|
|||||||
def generate(self):
|
def generate(self):
|
||||||
|
|
||||||
# Scuds
|
# Scuds
|
||||||
self.add_unit(MissilesSS.SRBM_SS_1C_Scud_B_9K72_LN_9P117M, "V1#0", self.position.x, self.position.y + random.randint(1, 8), self.heading)
|
self.add_unit(
|
||||||
self.add_unit(MissilesSS.SRBM_SS_1C_Scud_B_9K72_LN_9P117M, "V1#1", self.position.x + 50, self.position.y + random.randint(1, 8), self.heading)
|
MissilesSS.SRBM_SS_1C_Scud_B_9K72_LN_9P117M,
|
||||||
self.add_unit(MissilesSS.SRBM_SS_1C_Scud_B_9K72_LN_9P117M, "V1#2", self.position.x + 100, self.position.y + random.randint(1, 8), self.heading)
|
"V1#0",
|
||||||
|
self.position.x,
|
||||||
|
self.position.y + random.randint(1, 8),
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
MissilesSS.SRBM_SS_1C_Scud_B_9K72_LN_9P117M,
|
||||||
|
"V1#1",
|
||||||
|
self.position.x + 50,
|
||||||
|
self.position.y + random.randint(1, 8),
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
MissilesSS.SRBM_SS_1C_Scud_B_9K72_LN_9P117M,
|
||||||
|
"V1#2",
|
||||||
|
self.position.x + 100,
|
||||||
|
self.position.y + random.randint(1, 8),
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
# Commander
|
# Commander
|
||||||
self.add_unit(Unarmed.Transport_UAZ_469, "Kubel#0", self.position.x - 35, self.position.y - 20,
|
self.add_unit(
|
||||||
self.heading)
|
Unarmed.Transport_UAZ_469,
|
||||||
|
"Kubel#0",
|
||||||
|
self.position.x - 35,
|
||||||
|
self.position.y - 20,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
# Shorad
|
# Shorad
|
||||||
self.add_unit(AirDefence.SPAAA_ZSU_23_4_Shilka, "SHILKA#0", self.position.x - 55, self.position.y - 38,
|
self.add_unit(
|
||||||
self.heading)
|
AirDefence.SPAAA_ZSU_23_4_Shilka,
|
||||||
|
"SHILKA#0",
|
||||||
|
self.position.x - 55,
|
||||||
|
self.position.y - 38,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
self.add_unit(AirDefence.SAM_SA_9_Strela_1_9P31, "STRELA#0",
|
self.add_unit(
|
||||||
self.position.x + 200, self.position.y + 15, 90)
|
AirDefence.SAM_SA_9_Strela_1_9P31,
|
||||||
|
"STRELA#0",
|
||||||
|
self.position.x + 200,
|
||||||
|
self.position.y + 15,
|
||||||
|
90,
|
||||||
|
)
|
||||||
|
|||||||
@ -6,7 +6,6 @@ from gen.sam.group_generator import GroupGenerator
|
|||||||
|
|
||||||
|
|
||||||
class V1GroupGenerator(GroupGenerator):
|
class V1GroupGenerator(GroupGenerator):
|
||||||
|
|
||||||
def __init__(self, game, ground_object, faction):
|
def __init__(self, game, ground_object, faction):
|
||||||
super(V1GroupGenerator, self).__init__(game, ground_object)
|
super(V1GroupGenerator, self).__init__(game, ground_object)
|
||||||
self.faction = faction
|
self.faction = faction
|
||||||
@ -14,19 +13,54 @@ class V1GroupGenerator(GroupGenerator):
|
|||||||
def generate(self):
|
def generate(self):
|
||||||
|
|
||||||
# Ramps
|
# Ramps
|
||||||
self.add_unit(MissilesSS.V_1_ramp, "V1#0", self.position.x, self.position.y + random.randint(1, 8), self.heading)
|
self.add_unit(
|
||||||
self.add_unit(MissilesSS.V_1_ramp, "V1#1", self.position.x + 50, self.position.y + random.randint(1, 8), self.heading)
|
MissilesSS.V_1_ramp,
|
||||||
self.add_unit(MissilesSS.V_1_ramp, "V1#2", self.position.x + 100, self.position.y + random.randint(1, 8), self.heading)
|
"V1#0",
|
||||||
|
self.position.x,
|
||||||
|
self.position.y + random.randint(1, 8),
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
MissilesSS.V_1_ramp,
|
||||||
|
"V1#1",
|
||||||
|
self.position.x + 50,
|
||||||
|
self.position.y + random.randint(1, 8),
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
MissilesSS.V_1_ramp,
|
||||||
|
"V1#2",
|
||||||
|
self.position.x + 100,
|
||||||
|
self.position.y + random.randint(1, 8),
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
# Commander
|
# Commander
|
||||||
self.add_unit(Unarmed.Kübelwagen_82, "Kubel#0", self.position.x - 35, self.position.y - 20,
|
self.add_unit(
|
||||||
self.heading)
|
Unarmed.Kübelwagen_82,
|
||||||
|
"Kubel#0",
|
||||||
|
self.position.x - 35,
|
||||||
|
self.position.y - 20,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
# Self defense flak
|
# Self defense flak
|
||||||
flak_unit = random.choice([AirDefence.AAA_Flak_Vierling_38, AirDefence.AAA_Flak_38])
|
flak_unit = random.choice(
|
||||||
|
[AirDefence.AAA_Flak_Vierling_38, AirDefence.AAA_Flak_38]
|
||||||
|
)
|
||||||
|
|
||||||
self.add_unit(flak_unit, "FLAK#0", self.position.x - 55, self.position.y - 38,
|
self.add_unit(
|
||||||
self.heading)
|
flak_unit,
|
||||||
|
"FLAK#0",
|
||||||
|
self.position.x - 55,
|
||||||
|
self.position.y - 38,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
self.add_unit(Unarmed.Blitz_3_6_6700A, "Blitz#0",
|
self.add_unit(
|
||||||
self.position.x + 200, self.position.y + 15, 90)
|
Unarmed.Blitz_3_6_6700A,
|
||||||
|
"Blitz#0",
|
||||||
|
self.position.x + 200,
|
||||||
|
self.position.y + 15,
|
||||||
|
90,
|
||||||
|
)
|
||||||
|
|||||||
304
gen/naming.py
304
gen/naming.py
@ -9,39 +9,242 @@ from game import db
|
|||||||
|
|
||||||
from gen.flights.flight import Flight
|
from gen.flights.flight import Flight
|
||||||
|
|
||||||
ALPHA_MILITARY = ["Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot",
|
ALPHA_MILITARY = [
|
||||||
"Golf", "Hotel", "India", "Juliet", "Kilo", "Lima", "Mike",
|
"Alpha",
|
||||||
"November", "Oscar", "Papa", "Quebec", "Romeo", "Sierra",
|
"Bravo",
|
||||||
"Tango", "Uniform", "Victor", "Whisky", "XRay", "Yankee",
|
"Charlie",
|
||||||
"Zulu", "Zero"]
|
"Delta",
|
||||||
|
"Echo",
|
||||||
|
"Foxtrot",
|
||||||
|
"Golf",
|
||||||
|
"Hotel",
|
||||||
|
"India",
|
||||||
|
"Juliet",
|
||||||
|
"Kilo",
|
||||||
|
"Lima",
|
||||||
|
"Mike",
|
||||||
|
"November",
|
||||||
|
"Oscar",
|
||||||
|
"Papa",
|
||||||
|
"Quebec",
|
||||||
|
"Romeo",
|
||||||
|
"Sierra",
|
||||||
|
"Tango",
|
||||||
|
"Uniform",
|
||||||
|
"Victor",
|
||||||
|
"Whisky",
|
||||||
|
"XRay",
|
||||||
|
"Yankee",
|
||||||
|
"Zulu",
|
||||||
|
"Zero",
|
||||||
|
]
|
||||||
|
|
||||||
ANIMALS = [
|
ANIMALS = [
|
||||||
"SHARK", "TORTOISE", "BAT", "PANGOLIN", "AARDWOLF",
|
"SHARK",
|
||||||
"MONKEY", "BUFFALO", "DOG", "BOBCAT", "LYNX", "PANTHER", "TIGER",
|
"TORTOISE",
|
||||||
"LION", "OWL", "BUTTERFLY", "BISON", "DUCK", "COBRA", "MAMBA",
|
"BAT",
|
||||||
"DOLPHIN", "PHEASANT", "ARMADILLLO", "RACOON", "ZEBRA", "COW", "COYOTE", "FOX",
|
"PANGOLIN",
|
||||||
"LIGHTFOOT", "COTTONMOUTH", "TAURUS", "VIPER", "CASTOR", "GIRAFFE", "SNAKE",
|
"AARDWOLF",
|
||||||
"MONSTER", "ALBATROSS", "HAWK", "DOVE", "MOCKINGBIRD", "GECKO", "ORYX", "GORILLA",
|
"MONKEY",
|
||||||
"HARAMBE", "GOOSE", "MAVERICK", "HARE", "JACKAL", "LEOPARD", "CAT", "MUSK", "ORCA",
|
"BUFFALO",
|
||||||
"OCELOT", "BEAR", "PANDA", "GULL", "PENGUIN", "PYTHON", "RAVEN", "DEER", "MOOSE",
|
"DOG",
|
||||||
"REINDEER", "SHEEP", "GAZELLE", "INSECT", "VULTURE", "WALLABY", "KANGAROO", "KOALA",
|
"BOBCAT",
|
||||||
"KIWI", "WHALE", "FISH", "RHINO", "HIPPO", "RAT", "WOODPECKER", "WORM", "BABOON",
|
"LYNX",
|
||||||
"YAK", "SCORPIO", "HORSE", "POODLE", "CENTIPEDE", "CHICKEN", "CHEETAH", "CHAMELEON",
|
"PANTHER",
|
||||||
"CATFISH", "CATERPILLAR", "CARACAL", "CAMEL", "CAIMAN", "BARRACUDA", "BANDICOOT",
|
"TIGER",
|
||||||
"ALLIGATOR", "BONGO", "CORAL", "ELEPHANT", "ANTELOPE", "CRAB", "DACHSHUND", "DODO",
|
"LION",
|
||||||
"FLAMINGO", "FERRET", "FALCON", "BULLDOG", "DONKEY", "IGUANA", "TAMARIN", "HARRIER",
|
"OWL",
|
||||||
"GRIZZLY", "GREYHOUND", "GRASSHOPPER", "JAGUAR", "LADYBUG", "KOMODO", "DRAGON", "LIZARD",
|
"BUTTERFLY",
|
||||||
"LLAMA", "LOBSTER", "OCTOPUS", "MANATEE", "MAGPIE", "MACAW", "OSTRICH", "OYSTER",
|
"BISON",
|
||||||
"MOLE", "MULE", "MOTH", "MONGOOSE", "MOLLY", "MEERKAT", "MOUSE", "PEACOCK", "PIKE", "ROBIN",
|
"DUCK",
|
||||||
"RAGDOLL", "PLATYPUS", "PELICAN", "PARROT", "PORCUPINE", "PIRANHA", "PUMA", "PUG", "TAPIR",
|
"COBRA",
|
||||||
"TERMITE", "URCHIN", "SHRIMP", "TURKEY", "TOUCAN", "TETRA", "HUSKY", "STARFISH", "SWAN",
|
"MAMBA",
|
||||||
"FROG", "SQUIRREL", "WALRUS", "WARTHOG", "CORGI", "WEASEL", "WOMBAT", "WOLVERINE", "MAMMOTH",
|
"DOLPHIN",
|
||||||
"TOAD", "WOLF", "ZEBU", "SEAL", "SKATE", "JELLYFISH", "MOSQUITO", "LOCUST", "SLUG", "SNAIL",
|
"PHEASANT",
|
||||||
"HEDGEHOG", "PIGLET", "FENNEC", "BADGER", "ALPACA", "DINGO", "COLT", "SKUNK", "BUNNY", "IMPALA",
|
"ARMADILLLO",
|
||||||
"GUANACO", "CAPYBARA", "ELK", "MINK", "PRONGHORN", "CROW", "BUMBLEBEE", "FAWN", "OTTER", "WATERBUCK",
|
"RACOON",
|
||||||
"JERBOA", "KITTEN", "ARGALI", "OX", "MARE", "FINCH", "BASILISK", "GOPHER", "HAMSTER", "CANARY", "WOODCHUCK",
|
"ZEBRA",
|
||||||
"ANACONDA"
|
"COW",
|
||||||
]
|
"COYOTE",
|
||||||
|
"FOX",
|
||||||
|
"LIGHTFOOT",
|
||||||
|
"COTTONMOUTH",
|
||||||
|
"TAURUS",
|
||||||
|
"VIPER",
|
||||||
|
"CASTOR",
|
||||||
|
"GIRAFFE",
|
||||||
|
"SNAKE",
|
||||||
|
"MONSTER",
|
||||||
|
"ALBATROSS",
|
||||||
|
"HAWK",
|
||||||
|
"DOVE",
|
||||||
|
"MOCKINGBIRD",
|
||||||
|
"GECKO",
|
||||||
|
"ORYX",
|
||||||
|
"GORILLA",
|
||||||
|
"HARAMBE",
|
||||||
|
"GOOSE",
|
||||||
|
"MAVERICK",
|
||||||
|
"HARE",
|
||||||
|
"JACKAL",
|
||||||
|
"LEOPARD",
|
||||||
|
"CAT",
|
||||||
|
"MUSK",
|
||||||
|
"ORCA",
|
||||||
|
"OCELOT",
|
||||||
|
"BEAR",
|
||||||
|
"PANDA",
|
||||||
|
"GULL",
|
||||||
|
"PENGUIN",
|
||||||
|
"PYTHON",
|
||||||
|
"RAVEN",
|
||||||
|
"DEER",
|
||||||
|
"MOOSE",
|
||||||
|
"REINDEER",
|
||||||
|
"SHEEP",
|
||||||
|
"GAZELLE",
|
||||||
|
"INSECT",
|
||||||
|
"VULTURE",
|
||||||
|
"WALLABY",
|
||||||
|
"KANGAROO",
|
||||||
|
"KOALA",
|
||||||
|
"KIWI",
|
||||||
|
"WHALE",
|
||||||
|
"FISH",
|
||||||
|
"RHINO",
|
||||||
|
"HIPPO",
|
||||||
|
"RAT",
|
||||||
|
"WOODPECKER",
|
||||||
|
"WORM",
|
||||||
|
"BABOON",
|
||||||
|
"YAK",
|
||||||
|
"SCORPIO",
|
||||||
|
"HORSE",
|
||||||
|
"POODLE",
|
||||||
|
"CENTIPEDE",
|
||||||
|
"CHICKEN",
|
||||||
|
"CHEETAH",
|
||||||
|
"CHAMELEON",
|
||||||
|
"CATFISH",
|
||||||
|
"CATERPILLAR",
|
||||||
|
"CARACAL",
|
||||||
|
"CAMEL",
|
||||||
|
"CAIMAN",
|
||||||
|
"BARRACUDA",
|
||||||
|
"BANDICOOT",
|
||||||
|
"ALLIGATOR",
|
||||||
|
"BONGO",
|
||||||
|
"CORAL",
|
||||||
|
"ELEPHANT",
|
||||||
|
"ANTELOPE",
|
||||||
|
"CRAB",
|
||||||
|
"DACHSHUND",
|
||||||
|
"DODO",
|
||||||
|
"FLAMINGO",
|
||||||
|
"FERRET",
|
||||||
|
"FALCON",
|
||||||
|
"BULLDOG",
|
||||||
|
"DONKEY",
|
||||||
|
"IGUANA",
|
||||||
|
"TAMARIN",
|
||||||
|
"HARRIER",
|
||||||
|
"GRIZZLY",
|
||||||
|
"GREYHOUND",
|
||||||
|
"GRASSHOPPER",
|
||||||
|
"JAGUAR",
|
||||||
|
"LADYBUG",
|
||||||
|
"KOMODO",
|
||||||
|
"DRAGON",
|
||||||
|
"LIZARD",
|
||||||
|
"LLAMA",
|
||||||
|
"LOBSTER",
|
||||||
|
"OCTOPUS",
|
||||||
|
"MANATEE",
|
||||||
|
"MAGPIE",
|
||||||
|
"MACAW",
|
||||||
|
"OSTRICH",
|
||||||
|
"OYSTER",
|
||||||
|
"MOLE",
|
||||||
|
"MULE",
|
||||||
|
"MOTH",
|
||||||
|
"MONGOOSE",
|
||||||
|
"MOLLY",
|
||||||
|
"MEERKAT",
|
||||||
|
"MOUSE",
|
||||||
|
"PEACOCK",
|
||||||
|
"PIKE",
|
||||||
|
"ROBIN",
|
||||||
|
"RAGDOLL",
|
||||||
|
"PLATYPUS",
|
||||||
|
"PELICAN",
|
||||||
|
"PARROT",
|
||||||
|
"PORCUPINE",
|
||||||
|
"PIRANHA",
|
||||||
|
"PUMA",
|
||||||
|
"PUG",
|
||||||
|
"TAPIR",
|
||||||
|
"TERMITE",
|
||||||
|
"URCHIN",
|
||||||
|
"SHRIMP",
|
||||||
|
"TURKEY",
|
||||||
|
"TOUCAN",
|
||||||
|
"TETRA",
|
||||||
|
"HUSKY",
|
||||||
|
"STARFISH",
|
||||||
|
"SWAN",
|
||||||
|
"FROG",
|
||||||
|
"SQUIRREL",
|
||||||
|
"WALRUS",
|
||||||
|
"WARTHOG",
|
||||||
|
"CORGI",
|
||||||
|
"WEASEL",
|
||||||
|
"WOMBAT",
|
||||||
|
"WOLVERINE",
|
||||||
|
"MAMMOTH",
|
||||||
|
"TOAD",
|
||||||
|
"WOLF",
|
||||||
|
"ZEBU",
|
||||||
|
"SEAL",
|
||||||
|
"SKATE",
|
||||||
|
"JELLYFISH",
|
||||||
|
"MOSQUITO",
|
||||||
|
"LOCUST",
|
||||||
|
"SLUG",
|
||||||
|
"SNAIL",
|
||||||
|
"HEDGEHOG",
|
||||||
|
"PIGLET",
|
||||||
|
"FENNEC",
|
||||||
|
"BADGER",
|
||||||
|
"ALPACA",
|
||||||
|
"DINGO",
|
||||||
|
"COLT",
|
||||||
|
"SKUNK",
|
||||||
|
"BUNNY",
|
||||||
|
"IMPALA",
|
||||||
|
"GUANACO",
|
||||||
|
"CAPYBARA",
|
||||||
|
"ELK",
|
||||||
|
"MINK",
|
||||||
|
"PRONGHORN",
|
||||||
|
"CROW",
|
||||||
|
"BUMBLEBEE",
|
||||||
|
"FAWN",
|
||||||
|
"OTTER",
|
||||||
|
"WATERBUCK",
|
||||||
|
"JERBOA",
|
||||||
|
"KITTEN",
|
||||||
|
"ARGALI",
|
||||||
|
"OX",
|
||||||
|
"MARE",
|
||||||
|
"FINCH",
|
||||||
|
"BASILISK",
|
||||||
|
"GOPHER",
|
||||||
|
"HAMSTER",
|
||||||
|
"CANARY",
|
||||||
|
"WOODCHUCK",
|
||||||
|
"ANACONDA",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class NameGenerator:
|
class NameGenerator:
|
||||||
number = 0
|
number = 0
|
||||||
@ -72,21 +275,36 @@ class NameGenerator:
|
|||||||
name_str = flight.custom_name
|
name_str = flight.custom_name
|
||||||
else:
|
else:
|
||||||
name_str = "{} {}".format(
|
name_str = "{} {}".format(
|
||||||
flight.package.target.name, flight.flight_type)
|
flight.package.target.name, flight.flight_type
|
||||||
|
)
|
||||||
except AttributeError: # Here to maintain save compatibility with 2.3
|
except AttributeError: # Here to maintain save compatibility with 2.3
|
||||||
name_str = "{} {}".format(
|
name_str = "{} {}".format(flight.package.target.name, flight.flight_type)
|
||||||
flight.package.target.name, flight.flight_type)
|
return "{}|{}|{}|{}|{}|".format(
|
||||||
return "{}|{}|{}|{}|{}|".format(name_str, country.id, cls.aircraft_number, parent_base_id, db.unit_type_name(flight.unit_type))
|
name_str,
|
||||||
|
country.id,
|
||||||
|
cls.aircraft_number,
|
||||||
|
parent_base_id,
|
||||||
|
db.unit_type_name(flight.unit_type),
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def next_unit_name(cls, country: Country, parent_base_id: int, unit_type: UnitType):
|
def next_unit_name(cls, country: Country, parent_base_id: int, unit_type: UnitType):
|
||||||
cls.number += 1
|
cls.number += 1
|
||||||
return "unit|{}|{}|{}|{}|".format(country.id, cls.number, parent_base_id, db.unit_type_name(unit_type))
|
return "unit|{}|{}|{}|{}|".format(
|
||||||
|
country.id, cls.number, parent_base_id, db.unit_type_name(unit_type)
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def next_infantry_name(cls, country: Country, parent_base_id: int, unit_type: UnitType):
|
def next_infantry_name(
|
||||||
|
cls, country: Country, parent_base_id: int, unit_type: UnitType
|
||||||
|
):
|
||||||
cls.infantry_number += 1
|
cls.infantry_number += 1
|
||||||
return "infantry|{}|{}|{}|{}|".format(country.id, cls.infantry_number, parent_base_id, db.unit_type_name(unit_type))
|
return "infantry|{}|{}|{}|{}|".format(
|
||||||
|
country.id,
|
||||||
|
cls.infantry_number,
|
||||||
|
parent_base_id,
|
||||||
|
db.unit_type_name(unit_type),
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def next_basedefense_name():
|
def next_basedefense_name():
|
||||||
@ -100,7 +318,9 @@ class NameGenerator:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def next_tanker_name(cls, country: Country, unit_type: UnitType):
|
def next_tanker_name(cls, country: Country, unit_type: UnitType):
|
||||||
cls.number += 1
|
cls.number += 1
|
||||||
return "tanker|{}|{}|0|{}".format(country.id, cls.number, db.unit_type_name(unit_type))
|
return "tanker|{}|{}|0|{}".format(
|
||||||
|
country.id, cls.number, db.unit_type_name(unit_type)
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def next_carrier_name(cls, country: Country):
|
def next_carrier_name(cls, country: Country):
|
||||||
@ -112,7 +332,11 @@ class NameGenerator:
|
|||||||
if len(cls.ANIMALS) == 0:
|
if len(cls.ANIMALS) == 0:
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
new_name_generated = True
|
new_name_generated = True
|
||||||
alpha_mil_name = random.choice(ALPHA_MILITARY).upper() + "#" + str(random.randint(0, 100))
|
alpha_mil_name = (
|
||||||
|
random.choice(ALPHA_MILITARY).upper()
|
||||||
|
+ "#"
|
||||||
|
+ str(random.randint(0, 100))
|
||||||
|
)
|
||||||
for existing_name in cls.existing_alphas:
|
for existing_name in cls.existing_alphas:
|
||||||
if existing_name == alpha_mil_name:
|
if existing_name == alpha_mil_name:
|
||||||
new_name_generated = False
|
new_name_generated = False
|
||||||
|
|||||||
@ -68,9 +68,10 @@ class Radio:
|
|||||||
|
|
||||||
def range(self) -> Iterator[RadioFrequency]:
|
def range(self) -> Iterator[RadioFrequency]:
|
||||||
"""Returns an iterator over the usable frequencies of this radio."""
|
"""Returns an iterator over the usable frequencies of this radio."""
|
||||||
return (RadioFrequency(x) for x in range(
|
return (
|
||||||
self.minimum.hertz, self.maximum.hertz, self.step.hertz
|
RadioFrequency(x)
|
||||||
))
|
for x in range(self.minimum.hertz, self.maximum.hertz, self.step.hertz)
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def last_channel(self) -> RadioFrequency:
|
def last_channel(self) -> RadioFrequency:
|
||||||
@ -99,14 +100,12 @@ RADIOS: List[Radio] = [
|
|||||||
Radio("SCR-522", MHz(100), MHz(156), step=MHz(1)),
|
Radio("SCR-522", MHz(100), MHz(156), step=MHz(1)),
|
||||||
Radio("A.R.I. 1063", MHz(100), MHz(156), step=MHz(1)),
|
Radio("A.R.I. 1063", MHz(100), MHz(156), step=MHz(1)),
|
||||||
Radio("BC-1206", kHz(200), kHz(400), step=kHz(10)),
|
Radio("BC-1206", kHz(200), kHz(400), step=kHz(10)),
|
||||||
|
|
||||||
# Note: The M2000C V/UHF can operate in both ranges, but has a gap between
|
# Note: The M2000C V/UHF can operate in both ranges, but has a gap between
|
||||||
# 150 MHz and 225 MHz. We can't allocate in that gap, and the current
|
# 150 MHz and 225 MHz. We can't allocate in that gap, and the current
|
||||||
# system doesn't model gaps, so just pretend it ends at 150 MHz for now. We
|
# system doesn't model gaps, so just pretend it ends at 150 MHz for now. We
|
||||||
# can model gaps later if needed.
|
# can model gaps later if needed.
|
||||||
Radio("TRT ERA 7000 V/UHF", MHz(118), MHz(150), step=MHz(1)),
|
Radio("TRT ERA 7000 V/UHF", MHz(118), MHz(150), step=MHz(1)),
|
||||||
Radio("TRT ERA 7200 UHF", MHz(225), MHz(400), step=MHz(1)),
|
Radio("TRT ERA 7200 UHF", MHz(225), MHz(400), step=MHz(1)),
|
||||||
|
|
||||||
# Tomcat radios
|
# Tomcat radios
|
||||||
# # https://www.heatblur.se/F-14Manual/general.html#an-arc-159-uhf-1-radio
|
# # https://www.heatblur.se/F-14Manual/general.html#an-arc-159-uhf-1-radio
|
||||||
Radio("AN/ARC-159", MHz(225), MHz(400), step=MHz(1)),
|
Radio("AN/ARC-159", MHz(225), MHz(400), step=MHz(1)),
|
||||||
@ -114,31 +113,23 @@ RADIOS: List[Radio] = [
|
|||||||
# to 400 MHz range, but we can't model gaps with the current implementation.
|
# to 400 MHz range, but we can't model gaps with the current implementation.
|
||||||
# https://www.heatblur.se/F-14Manual/general.html#an-arc-182-v-uhf-2-radio
|
# https://www.heatblur.se/F-14Manual/general.html#an-arc-182-v-uhf-2-radio
|
||||||
Radio("AN/ARC-182", MHz(108), MHz(174), step=MHz(1)),
|
Radio("AN/ARC-182", MHz(108), MHz(174), step=MHz(1)),
|
||||||
|
|
||||||
# Also capable of [103, 156) at 25 kHz intervals, but we can't do gaps.
|
# Also capable of [103, 156) at 25 kHz intervals, but we can't do gaps.
|
||||||
Radio("FR 22", MHz(225), MHz(400), step=kHz(50)),
|
Radio("FR 22", MHz(225), MHz(400), step=kHz(50)),
|
||||||
|
|
||||||
# P-51 / P-47 Radio
|
# P-51 / P-47 Radio
|
||||||
# 4 preset channels (A/B/C/D)
|
# 4 preset channels (A/B/C/D)
|
||||||
Radio("SCR522", MHz(100), MHz(156), step=kHz(25)),
|
Radio("SCR522", MHz(100), MHz(156), step=kHz(25)),
|
||||||
|
|
||||||
Radio("R&S M3AR VHF", MHz(120), MHz(174), step=MHz(1)),
|
Radio("R&S M3AR VHF", MHz(120), MHz(174), step=MHz(1)),
|
||||||
Radio("R&S M3AR UHF", MHz(225), MHz(400), step=MHz(1)),
|
Radio("R&S M3AR UHF", MHz(225), MHz(400), step=MHz(1)),
|
||||||
|
|
||||||
# MiG-15bis
|
# MiG-15bis
|
||||||
Radio("RSI-6K HF", MHz(3, 750), MHz(5), step=kHz(25)),
|
Radio("RSI-6K HF", MHz(3, 750), MHz(5), step=kHz(25)),
|
||||||
|
|
||||||
# MiG-19P
|
# MiG-19P
|
||||||
Radio("RSIU-4V", MHz(100), MHz(150), step=MHz(1)),
|
Radio("RSIU-4V", MHz(100), MHz(150), step=MHz(1)),
|
||||||
|
|
||||||
# MiG-21bis
|
# MiG-21bis
|
||||||
Radio("RSIU-5V", MHz(118), MHz(140), step=MHz(1)),
|
Radio("RSIU-5V", MHz(118), MHz(140), step=MHz(1)),
|
||||||
|
|
||||||
# Ka-50
|
# Ka-50
|
||||||
# Note: Also capable of 100MHz-150MHz, but we can't model gaps.
|
# Note: Also capable of 100MHz-150MHz, but we can't model gaps.
|
||||||
Radio("R-800L1", MHz(220), MHz(400), step=kHz(25)),
|
Radio("R-800L1", MHz(220), MHz(400), step=kHz(25)),
|
||||||
Radio("R-828", MHz(20), MHz(60), step=kHz(25)),
|
Radio("R-828", MHz(20), MHz(60), step=kHz(25)),
|
||||||
|
|
||||||
# UH-1H
|
# UH-1H
|
||||||
Radio("AN/ARC-51BX", MHz(225), MHz(400), step=kHz(50)),
|
Radio("AN/ARC-51BX", MHz(225), MHz(400), step=kHz(50)),
|
||||||
Radio("AN/ARC-131", MHz(30), MHz(76), step=kHz(50)),
|
Radio("AN/ARC-131", MHz(30), MHz(76), step=kHz(50)),
|
||||||
@ -218,7 +209,8 @@ class RadioRegistry:
|
|||||||
# https://github.com/Khopa/dcs_liberation/issues/598
|
# https://github.com/Khopa/dcs_liberation/issues/598
|
||||||
channel = radio.last_channel
|
channel = radio.last_channel
|
||||||
logging.warning(
|
logging.warning(
|
||||||
f"No more free channels for {radio.name}. Reusing {channel}.")
|
f"No more free channels for {radio.name}. Reusing {channel}."
|
||||||
|
)
|
||||||
return channel
|
return channel
|
||||||
|
|
||||||
def alloc_uhf(self) -> RadioFrequency:
|
def alloc_uhf(self) -> RadioFrequency:
|
||||||
|
|||||||
@ -25,8 +25,9 @@ class RunwayData:
|
|||||||
icls: Optional[int] = None
|
icls: Optional[int] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def for_airfield(cls, airport: Airport, runway_heading: int,
|
def for_airfield(
|
||||||
runway_name: str) -> RunwayData:
|
cls, airport: Airport, runway_heading: int, runway_name: str
|
||||||
|
) -> RunwayData:
|
||||||
"""Creates RunwayData for the given runway of an airfield.
|
"""Creates RunwayData for the given runway of an airfield.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -56,7 +57,7 @@ class RunwayData:
|
|||||||
atc=atc,
|
atc=atc,
|
||||||
tacan=tacan,
|
tacan=tacan,
|
||||||
tacan_callsign=tacan_callsign,
|
tacan_callsign=tacan_callsign,
|
||||||
ils=ils
|
ils=ils,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -20,15 +20,19 @@ class BoforsGenerator(AirDefenseGroupGenerator):
|
|||||||
grid_x = random.randint(2, 3)
|
grid_x = random.randint(2, 3)
|
||||||
grid_y = random.randint(2, 3)
|
grid_y = random.randint(2, 3)
|
||||||
|
|
||||||
spacing = random.randint(10,40)
|
spacing = random.randint(10, 40)
|
||||||
|
|
||||||
index = 0
|
index = 0
|
||||||
for i in range(grid_x):
|
for i in range(grid_x):
|
||||||
for j in range(grid_y):
|
for j in range(grid_y):
|
||||||
index = index+1
|
index = index + 1
|
||||||
self.add_unit(AirDefence.AAA_Bofors_40mm, "AAA#" + str(index),
|
self.add_unit(
|
||||||
self.position.x + spacing*i,
|
AirDefence.AAA_Bofors_40mm,
|
||||||
self.position.y + spacing*j, self.heading)
|
"AAA#" + str(index),
|
||||||
|
self.position.x + spacing * i,
|
||||||
|
self.position.y + spacing * j,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def range(cls) -> AirDefenseRange:
|
def range(cls) -> AirDefenseRange:
|
||||||
|
|||||||
@ -37,34 +37,64 @@ class FlakGenerator(AirDefenseGroupGenerator):
|
|||||||
|
|
||||||
for i in range(grid_x):
|
for i in range(grid_x):
|
||||||
for j in range(grid_y):
|
for j in range(grid_y):
|
||||||
index = index+1
|
index = index + 1
|
||||||
self.add_unit(unit_type, "AAA#" + str(index),
|
self.add_unit(
|
||||||
self.position.x + spacing*i + random.randint(1,5),
|
unit_type,
|
||||||
self.position.y + spacing*j + random.randint(1,5), self.heading)
|
"AAA#" + str(index),
|
||||||
|
self.position.x + spacing * i + random.randint(1, 5),
|
||||||
|
self.position.y + spacing * j + random.randint(1, 5),
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
if(mixed):
|
if mixed:
|
||||||
unit_type = random.choice(GFLAK)
|
unit_type = random.choice(GFLAK)
|
||||||
|
|
||||||
# Search lights
|
# Search lights
|
||||||
search_pos = self.get_circular_position(random.randint(2,3), 80)
|
search_pos = self.get_circular_position(random.randint(2, 3), 80)
|
||||||
for index, pos in enumerate(search_pos):
|
for index, pos in enumerate(search_pos):
|
||||||
self.add_unit(AirDefence.Flak_Searchlight_37, "SearchLight#" + str(index), pos[0], pos[1], self.heading)
|
self.add_unit(
|
||||||
|
AirDefence.Flak_Searchlight_37,
|
||||||
|
"SearchLight#" + str(index),
|
||||||
|
pos[0],
|
||||||
|
pos[1],
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
# Support
|
# Support
|
||||||
self.add_unit(AirDefence.Maschinensatz_33, "MC33#", self.position.x-20, self.position.y-20, self.heading)
|
self.add_unit(
|
||||||
self.add_unit(AirDefence.AAA_Kdo_G_40, "KDO#", self.position.x - 25, self.position.y - 20,
|
AirDefence.Maschinensatz_33,
|
||||||
self.heading)
|
"MC33#",
|
||||||
|
self.position.x - 20,
|
||||||
|
self.position.y - 20,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
AirDefence.AAA_Kdo_G_40,
|
||||||
|
"KDO#",
|
||||||
|
self.position.x - 25,
|
||||||
|
self.position.y - 20,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
# Commander
|
# Commander
|
||||||
self.add_unit(Unarmed.Kübelwagen_82, "Kubel#", self.position.x - 35, self.position.y - 20,
|
self.add_unit(
|
||||||
self.heading)
|
Unarmed.Kübelwagen_82,
|
||||||
|
"Kubel#",
|
||||||
|
self.position.x - 35,
|
||||||
|
self.position.y - 20,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
# Some Opel Blitz trucks
|
# Some Opel Blitz trucks
|
||||||
for i in range(int(max(1,grid_x/2))):
|
for i in range(int(max(1, grid_x / 2))):
|
||||||
for j in range(int(max(1,grid_x/2))):
|
for j in range(int(max(1, grid_x / 2))):
|
||||||
self.add_unit(Unarmed.Blitz_3_6_6700A, "BLITZ#" + str(index),
|
self.add_unit(
|
||||||
self.position.x + 125 + 15*i + random.randint(1,5),
|
Unarmed.Blitz_3_6_6700A,
|
||||||
self.position.y + 15*j + random.randint(1,5), 75)
|
"BLITZ#" + str(index),
|
||||||
|
self.position.x + 125 + 15 * i + random.randint(1, 5),
|
||||||
|
self.position.y + 15 * j + random.randint(1, 5),
|
||||||
|
75,
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def range(cls) -> AirDefenseRange:
|
def range(cls) -> AirDefenseRange:
|
||||||
|
|||||||
@ -24,12 +24,22 @@ class Flak18Generator(AirDefenseGroupGenerator):
|
|||||||
for i in range(3):
|
for i in range(3):
|
||||||
for j in range(2):
|
for j in range(2):
|
||||||
index = index + 1
|
index = index + 1
|
||||||
self.add_unit(AirDefence.AAA_8_8cm_Flak_18, "AAA#" + str(index),
|
self.add_unit(
|
||||||
|
AirDefence.AAA_8_8cm_Flak_18,
|
||||||
|
"AAA#" + str(index),
|
||||||
self.position.x + spacing * i + random.randint(1, 5),
|
self.position.x + spacing * i + random.randint(1, 5),
|
||||||
self.position.y + spacing * j + random.randint(1, 5), self.heading)
|
self.position.y + spacing * j + random.randint(1, 5),
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
# Add a commander truck
|
# Add a commander truck
|
||||||
self.add_unit(Unarmed.Blitz_3_6_6700A, "Blitz#", self.position.x - 35, self.position.y - 20, self.heading)
|
self.add_unit(
|
||||||
|
Unarmed.Blitz_3_6_6700A,
|
||||||
|
"Blitz#",
|
||||||
|
self.position.x - 35,
|
||||||
|
self.position.y - 20,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def range(cls) -> AirDefenseRange:
|
def range(cls) -> AirDefenseRange:
|
||||||
|
|||||||
@ -19,15 +19,25 @@ class KS19Generator(AirDefenseGroupGenerator):
|
|||||||
|
|
||||||
spacing = random.randint(10, 40)
|
spacing = random.randint(10, 40)
|
||||||
|
|
||||||
self.add_unit(highdigitsams.AAA_SON_9_Fire_Can, "TR", self.position.x - 20, self.position.y - 20, self.heading)
|
self.add_unit(
|
||||||
|
highdigitsams.AAA_SON_9_Fire_Can,
|
||||||
|
"TR",
|
||||||
|
self.position.x - 20,
|
||||||
|
self.position.y - 20,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
index = 0
|
index = 0
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
for j in range(3):
|
for j in range(3):
|
||||||
index = index + 1
|
index = index + 1
|
||||||
self.add_unit(highdigitsams.AAA_100mm_KS_19, "AAA#" + str(index),
|
self.add_unit(
|
||||||
|
highdigitsams.AAA_100mm_KS_19,
|
||||||
|
"AAA#" + str(index),
|
||||||
self.position.x + spacing * i,
|
self.position.x + spacing * i,
|
||||||
self.position.y + spacing * j, self.heading)
|
self.position.y + spacing * j,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def range(cls) -> AirDefenseRange:
|
def range(cls) -> AirDefenseRange:
|
||||||
|
|||||||
@ -20,21 +20,63 @@ class AllyWW2FlakGenerator(AirDefenseGroupGenerator):
|
|||||||
|
|
||||||
positions = self.get_circular_position(4, launcher_distance=30, coverage=360)
|
positions = self.get_circular_position(4, launcher_distance=30, coverage=360)
|
||||||
for i, position in enumerate(positions):
|
for i, position in enumerate(positions):
|
||||||
self.add_unit(AirDefence.AA_gun_QF_3_7, "AA#" + str(i), position[0], position[1], position[2])
|
self.add_unit(
|
||||||
|
AirDefence.AA_gun_QF_3_7,
|
||||||
|
"AA#" + str(i),
|
||||||
|
position[0],
|
||||||
|
position[1],
|
||||||
|
position[2],
|
||||||
|
)
|
||||||
|
|
||||||
positions = self.get_circular_position(8, launcher_distance=60, coverage=360)
|
positions = self.get_circular_position(8, launcher_distance=60, coverage=360)
|
||||||
for i, position in enumerate(positions):
|
for i, position in enumerate(positions):
|
||||||
self.add_unit(AirDefence.AAA_M1_37mm, "AA#" + str(4 + i), position[0], position[1], position[2])
|
self.add_unit(
|
||||||
|
AirDefence.AAA_M1_37mm,
|
||||||
|
"AA#" + str(4 + i),
|
||||||
|
position[0],
|
||||||
|
position[1],
|
||||||
|
position[2],
|
||||||
|
)
|
||||||
|
|
||||||
positions = self.get_circular_position(8, launcher_distance=90, coverage=360)
|
positions = self.get_circular_position(8, launcher_distance=90, coverage=360)
|
||||||
for i, position in enumerate(positions):
|
for i, position in enumerate(positions):
|
||||||
self.add_unit(AirDefence.AAA_M45_Quadmount, "AA#" + str(12 + i), position[0], position[1], position[2])
|
self.add_unit(
|
||||||
|
AirDefence.AAA_M45_Quadmount,
|
||||||
|
"AA#" + str(12 + i),
|
||||||
|
position[0],
|
||||||
|
position[1],
|
||||||
|
position[2],
|
||||||
|
)
|
||||||
|
|
||||||
# Add a commander truck
|
# Add a commander truck
|
||||||
self.add_unit(Unarmed.Willys_MB, "CMD#1", self.position.x, self.position.y - 20, random.randint(0, 360))
|
self.add_unit(
|
||||||
self.add_unit(Armor.M30_Cargo_Carrier, "LOG#1", self.position.x, self.position.y + 20, random.randint(0, 360))
|
Unarmed.Willys_MB,
|
||||||
self.add_unit(Armor.M4_Tractor, "LOG#2", self.position.x + 20, self.position.y, random.randint(0, 360))
|
"CMD#1",
|
||||||
self.add_unit(Unarmed.Bedford_MWD, "LOG#3", self.position.x - 20, self.position.y, random.randint(0, 360))
|
self.position.x,
|
||||||
|
self.position.y - 20,
|
||||||
|
random.randint(0, 360),
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
Armor.M30_Cargo_Carrier,
|
||||||
|
"LOG#1",
|
||||||
|
self.position.x,
|
||||||
|
self.position.y + 20,
|
||||||
|
random.randint(0, 360),
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
Armor.M4_Tractor,
|
||||||
|
"LOG#2",
|
||||||
|
self.position.x + 20,
|
||||||
|
self.position.y,
|
||||||
|
random.randint(0, 360),
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
Unarmed.Bedford_MWD,
|
||||||
|
"LOG#3",
|
||||||
|
self.position.x - 20,
|
||||||
|
self.position.y,
|
||||||
|
random.randint(0, 360),
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def range(cls) -> AirDefenseRange:
|
def range(cls) -> AirDefenseRange:
|
||||||
|
|||||||
@ -16,9 +16,17 @@ class ZSU57Generator(AirDefenseGroupGenerator):
|
|||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
num_launchers = 5
|
num_launchers = 5
|
||||||
positions = self.get_circular_position(num_launchers, launcher_distance=110, coverage=360)
|
positions = self.get_circular_position(
|
||||||
|
num_launchers, launcher_distance=110, coverage=360
|
||||||
|
)
|
||||||
for i, position in enumerate(positions):
|
for i, position in enumerate(positions):
|
||||||
self.add_unit(AirDefence.AAA_ZSU_57_2, "SPAA#" + str(i), position[0], position[1], position[2])
|
self.add_unit(
|
||||||
|
AirDefence.AAA_ZSU_57_2,
|
||||||
|
"SPAA#" + str(i),
|
||||||
|
position[0],
|
||||||
|
position[1],
|
||||||
|
position[2],
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def range(cls) -> AirDefenseRange:
|
def range(cls) -> AirDefenseRange:
|
||||||
|
|||||||
@ -20,15 +20,19 @@ class ZU23InsurgentGenerator(AirDefenseGroupGenerator):
|
|||||||
grid_x = random.randint(2, 3)
|
grid_x = random.randint(2, 3)
|
||||||
grid_y = random.randint(2, 3)
|
grid_y = random.randint(2, 3)
|
||||||
|
|
||||||
spacing = random.randint(10,40)
|
spacing = random.randint(10, 40)
|
||||||
|
|
||||||
index = 0
|
index = 0
|
||||||
for i in range(grid_x):
|
for i in range(grid_x):
|
||||||
for j in range(grid_y):
|
for j in range(grid_y):
|
||||||
index = index+1
|
index = index + 1
|
||||||
self.add_unit(AirDefence.AAA_ZU_23_Insurgent_Closed, "AAA#" + str(index),
|
self.add_unit(
|
||||||
self.position.x + spacing*i,
|
AirDefence.AAA_ZU_23_Insurgent_Closed,
|
||||||
self.position.y + spacing*j, self.heading)
|
"AAA#" + str(index),
|
||||||
|
self.position.x + spacing * i,
|
||||||
|
self.position.y + spacing * j,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def range(cls) -> AirDefenseRange:
|
def range(cls) -> AirDefenseRange:
|
||||||
|
|||||||
@ -28,8 +28,9 @@ class AirDefenseGroupGenerator(GroupGenerator, ABC):
|
|||||||
self.auxiliary_groups: List[VehicleGroup] = []
|
self.auxiliary_groups: List[VehicleGroup] = []
|
||||||
|
|
||||||
def add_auxiliary_group(self, name_suffix: str) -> VehicleGroup:
|
def add_auxiliary_group(self, name_suffix: str) -> VehicleGroup:
|
||||||
group = VehicleGroup(self.game.next_group_id(),
|
group = VehicleGroup(
|
||||||
"|".join([self.go.group_name, name_suffix]))
|
self.game.next_group_id(), "|".join([self.go.group_name, name_suffix])
|
||||||
|
)
|
||||||
self.auxiliary_groups.append(group)
|
self.auxiliary_groups.append(group)
|
||||||
return group
|
return group
|
||||||
|
|
||||||
@ -37,7 +38,8 @@ class AirDefenseGroupGenerator(GroupGenerator, ABC):
|
|||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Deprecated call to AirDefenseGroupGenerator.get_generated_group "
|
"Deprecated call to AirDefenseGroupGenerator.get_generated_group "
|
||||||
"misses auxiliary groups. Use AirDefenseGroupGenerator.groups "
|
"misses auxiliary groups. Use AirDefenseGroupGenerator.groups "
|
||||||
"instead.")
|
"instead."
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def groups(self) -> Iterator[VehicleGroup]:
|
def groups(self) -> Iterator[VehicleGroup]:
|
||||||
|
|||||||
@ -29,18 +29,38 @@ class EarlyColdWarFlakGenerator(AirDefenseGroupGenerator):
|
|||||||
for i in range(3):
|
for i in range(3):
|
||||||
for j in range(2):
|
for j in range(2):
|
||||||
index = index + 1
|
index = index + 1
|
||||||
self.add_unit(AirDefence.AAA_8_8cm_Flak_18, "AAA#" + str(index),
|
self.add_unit(
|
||||||
|
AirDefence.AAA_8_8cm_Flak_18,
|
||||||
|
"AAA#" + str(index),
|
||||||
self.position.x + spacing * i + random.randint(1, 5),
|
self.position.x + spacing * i + random.randint(1, 5),
|
||||||
self.position.y + spacing * j + random.randint(1, 5), self.heading)
|
self.position.y + spacing * j + random.randint(1, 5),
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
# Short range guns
|
# Short range guns
|
||||||
self.add_unit(AirDefence.AAA_Bofors_40mm, "SHO#1",
|
self.add_unit(
|
||||||
self.position.x - 40, self.position.y - 40, self.heading + 180),
|
AirDefence.AAA_Bofors_40mm,
|
||||||
self.add_unit(AirDefence.AAA_Bofors_40mm, "SHO#2",
|
"SHO#1",
|
||||||
self.position.x + spacing * 2 + 40, self.position.y + spacing + 40, self.heading),
|
self.position.x - 40,
|
||||||
|
self.position.y - 40,
|
||||||
|
self.heading + 180,
|
||||||
|
),
|
||||||
|
self.add_unit(
|
||||||
|
AirDefence.AAA_Bofors_40mm,
|
||||||
|
"SHO#2",
|
||||||
|
self.position.x + spacing * 2 + 40,
|
||||||
|
self.position.y + spacing + 40,
|
||||||
|
self.heading,
|
||||||
|
),
|
||||||
|
|
||||||
# Add a truck
|
# Add a truck
|
||||||
self.add_unit(Unarmed.Transport_KAMAZ_43101, "Truck#", self.position.x - 60, self.position.y - 20, self.heading)
|
self.add_unit(
|
||||||
|
Unarmed.Transport_KAMAZ_43101,
|
||||||
|
"Truck#",
|
||||||
|
self.position.x - 60,
|
||||||
|
self.position.y - 20,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def range(cls) -> AirDefenseRange:
|
def range(cls) -> AirDefenseRange:
|
||||||
@ -66,18 +86,38 @@ class ColdWarFlakGenerator(AirDefenseGroupGenerator):
|
|||||||
for i in range(3):
|
for i in range(3):
|
||||||
for j in range(2):
|
for j in range(2):
|
||||||
index = index + 1
|
index = index + 1
|
||||||
self.add_unit(AirDefence.AAA_8_8cm_Flak_18, "AAA#" + str(index),
|
self.add_unit(
|
||||||
|
AirDefence.AAA_8_8cm_Flak_18,
|
||||||
|
"AAA#" + str(index),
|
||||||
self.position.x + spacing * i + random.randint(1, 5),
|
self.position.x + spacing * i + random.randint(1, 5),
|
||||||
self.position.y + spacing * j + random.randint(1, 5), self.heading)
|
self.position.y + spacing * j + random.randint(1, 5),
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
# Short range guns
|
# Short range guns
|
||||||
self.add_unit(AirDefence.AAA_ZU_23_Closed, "SHO#1",
|
self.add_unit(
|
||||||
self.position.x - 40, self.position.y - 40, self.heading + 180),
|
AirDefence.AAA_ZU_23_Closed,
|
||||||
self.add_unit(AirDefence.AAA_ZU_23_Closed, "SHO#2",
|
"SHO#1",
|
||||||
self.position.x + spacing * 2 + 40, self.position.y + spacing + 40, self.heading),
|
self.position.x - 40,
|
||||||
|
self.position.y - 40,
|
||||||
|
self.heading + 180,
|
||||||
|
),
|
||||||
|
self.add_unit(
|
||||||
|
AirDefence.AAA_ZU_23_Closed,
|
||||||
|
"SHO#2",
|
||||||
|
self.position.x + spacing * 2 + 40,
|
||||||
|
self.position.y + spacing + 40,
|
||||||
|
self.heading,
|
||||||
|
),
|
||||||
|
|
||||||
# Add a P19 Radar for EWR
|
# Add a P19 Radar for EWR
|
||||||
self.add_unit(AirDefence.SAM_SR_P_19, "SR#0", self.position.x - 60, self.position.y - 20, self.heading)
|
self.add_unit(
|
||||||
|
AirDefence.SAM_SR_P_19,
|
||||||
|
"SR#0",
|
||||||
|
self.position.x - 60,
|
||||||
|
self.position.y - 20,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def range(cls) -> AirDefenseRange:
|
def range(cls) -> AirDefenseRange:
|
||||||
|
|||||||
@ -10,8 +10,9 @@ class EwrGenerator(GroupGenerator):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def generate(self) -> None:
|
def generate(self) -> None:
|
||||||
self.add_unit(self.unit_type, "EWR", self.position.x, self.position.y,
|
self.add_unit(
|
||||||
self.heading)
|
self.unit_type, "EWR", self.position.x, self.position.y, self.heading
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BoxSpringGenerator(EwrGenerator):
|
class BoxSpringGenerator(EwrGenerator):
|
||||||
|
|||||||
@ -17,27 +17,93 @@ class FreyaGenerator(AirDefenseGroupGenerator):
|
|||||||
def generate(self):
|
def generate(self):
|
||||||
|
|
||||||
# TODO : would be better with the Concrete structure that is supposed to protect it
|
# TODO : would be better with the Concrete structure that is supposed to protect it
|
||||||
self.add_unit(AirDefence.EWR_FuMG_401_Freya_LZ, "EWR#1", self.position.x, self.position.y, self.heading)
|
self.add_unit(
|
||||||
|
AirDefence.EWR_FuMG_401_Freya_LZ,
|
||||||
|
"EWR#1",
|
||||||
|
self.position.x,
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
positions = self.get_circular_position(4, launcher_distance=50, coverage=360)
|
positions = self.get_circular_position(4, launcher_distance=50, coverage=360)
|
||||||
for i, position in enumerate(positions):
|
for i, position in enumerate(positions):
|
||||||
self.add_unit(AirDefence.AAA_Flak_Vierling_38, "AA#" + str(i), position[0], position[1], position[2])
|
self.add_unit(
|
||||||
|
AirDefence.AAA_Flak_Vierling_38,
|
||||||
|
"AA#" + str(i),
|
||||||
|
position[0],
|
||||||
|
position[1],
|
||||||
|
position[2],
|
||||||
|
)
|
||||||
|
|
||||||
positions = self.get_circular_position(4, launcher_distance=100, coverage=360)
|
positions = self.get_circular_position(4, launcher_distance=100, coverage=360)
|
||||||
for i, position in enumerate(positions):
|
for i, position in enumerate(positions):
|
||||||
self.add_unit(AirDefence.AAA_8_8cm_Flak_18, "AA#" + str(4+i), position[0], position[1], position[2])
|
self.add_unit(
|
||||||
|
AirDefence.AAA_8_8cm_Flak_18,
|
||||||
|
"AA#" + str(4 + i),
|
||||||
|
position[0],
|
||||||
|
position[1],
|
||||||
|
position[2],
|
||||||
|
)
|
||||||
|
|
||||||
# Command/Logi
|
# Command/Logi
|
||||||
self.add_unit(Unarmed.Kübelwagen_82, "Kubel#1", self.position.x - 20, self.position.y - 20, self.heading)
|
self.add_unit(
|
||||||
self.add_unit(Unarmed.Sd_Kfz_7, "Sdkfz#1", self.position.x + 20, self.position.y + 22, self.heading)
|
Unarmed.Kübelwagen_82,
|
||||||
self.add_unit(Unarmed.Sd_Kfz_2, "Sdkfz#2", self.position.x - 22, self.position.y + 20, self.heading)
|
"Kubel#1",
|
||||||
|
self.position.x - 20,
|
||||||
|
self.position.y - 20,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
Unarmed.Sd_Kfz_7,
|
||||||
|
"Sdkfz#1",
|
||||||
|
self.position.x + 20,
|
||||||
|
self.position.y + 22,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
Unarmed.Sd_Kfz_2,
|
||||||
|
"Sdkfz#2",
|
||||||
|
self.position.x - 22,
|
||||||
|
self.position.y + 20,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
# Maschinensatz_33 and Kdo.g 40 Telemeter
|
# Maschinensatz_33 and Kdo.g 40 Telemeter
|
||||||
self.add_unit(AirDefence.Maschinensatz_33, "Energy#1", self.position.x + 20, self.position.y - 20, self.heading)
|
self.add_unit(
|
||||||
self.add_unit(AirDefence.AAA_Kdo_G_40, "Telemeter#1", self.position.x + 20, self.position.y - 10, self.heading)
|
AirDefence.Maschinensatz_33,
|
||||||
self.add_unit(Infantry.Infantry_Mauser_98, "Inf#1", self.position.x + 20, self.position.y - 14, self.heading)
|
"Energy#1",
|
||||||
self.add_unit(Infantry.Infantry_Mauser_98, "Inf#2", self.position.x + 20, self.position.y - 22, self.heading)
|
self.position.x + 20,
|
||||||
self.add_unit(Infantry.Infantry_Mauser_98, "Inf#3", self.position.x + 20, self.position.y - 24, self.heading + 45)
|
self.position.y - 20,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
AirDefence.AAA_Kdo_G_40,
|
||||||
|
"Telemeter#1",
|
||||||
|
self.position.x + 20,
|
||||||
|
self.position.y - 10,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
Infantry.Infantry_Mauser_98,
|
||||||
|
"Inf#1",
|
||||||
|
self.position.x + 20,
|
||||||
|
self.position.y - 14,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
Infantry.Infantry_Mauser_98,
|
||||||
|
"Inf#2",
|
||||||
|
self.position.x + 20,
|
||||||
|
self.position.y - 22,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
Infantry.Infantry_Mauser_98,
|
||||||
|
"Inf#3",
|
||||||
|
self.position.x + 20,
|
||||||
|
self.position.y - 24,
|
||||||
|
self.heading + 45,
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def range(cls) -> AirDefenseRange:
|
def range(cls) -> AirDefenseRange:
|
||||||
|
|||||||
@ -23,14 +23,12 @@ if TYPE_CHECKING:
|
|||||||
# care about in the format we want if we just generate our own group description
|
# care about in the format we want if we just generate our own group description
|
||||||
# types rather than pydcs groups.
|
# types rather than pydcs groups.
|
||||||
class GroupGenerator:
|
class GroupGenerator:
|
||||||
|
|
||||||
def __init__(self, game: Game, ground_object: TheaterGroundObject) -> None:
|
def __init__(self, game: Game, ground_object: TheaterGroundObject) -> None:
|
||||||
self.game = game
|
self.game = game
|
||||||
self.go = ground_object
|
self.go = ground_object
|
||||||
self.position = ground_object.position
|
self.position = ground_object.position
|
||||||
self.heading = random.randint(0, 359)
|
self.heading = random.randint(0, 359)
|
||||||
self.vg = unitgroup.VehicleGroup(self.game.next_group_id(),
|
self.vg = unitgroup.VehicleGroup(self.game.next_group_id(), self.go.group_name)
|
||||||
self.go.group_name)
|
|
||||||
wp = self.vg.add_waypoint(self.position, PointAction.OffRoad, 0)
|
wp = self.vg.add_waypoint(self.position, PointAction.OffRoad, 0)
|
||||||
wp.ETA_locked = True
|
wp.ETA_locked = True
|
||||||
|
|
||||||
@ -40,16 +38,27 @@ class GroupGenerator:
|
|||||||
def get_generated_group(self) -> unitgroup.VehicleGroup:
|
def get_generated_group(self) -> unitgroup.VehicleGroup:
|
||||||
return self.vg
|
return self.vg
|
||||||
|
|
||||||
def add_unit(self, unit_type: Type[VehicleType], name: str, pos_x: float,
|
def add_unit(
|
||||||
pos_y: float, heading: int) -> Vehicle:
|
self,
|
||||||
return self.add_unit_to_group(self.vg, unit_type, name,
|
unit_type: Type[VehicleType],
|
||||||
Point(pos_x, pos_y), heading)
|
name: str,
|
||||||
|
pos_x: float,
|
||||||
|
pos_y: float,
|
||||||
|
heading: int,
|
||||||
|
) -> Vehicle:
|
||||||
|
return self.add_unit_to_group(
|
||||||
|
self.vg, unit_type, name, Point(pos_x, pos_y), heading
|
||||||
|
)
|
||||||
|
|
||||||
def add_unit_to_group(self, group: unitgroup.VehicleGroup,
|
def add_unit_to_group(
|
||||||
unit_type: Type[VehicleType], name: str,
|
self,
|
||||||
position: Point, heading: int) -> Vehicle:
|
group: unitgroup.VehicleGroup,
|
||||||
unit = Vehicle(self.game.next_unit_id(),
|
unit_type: Type[VehicleType],
|
||||||
f"{group.name}|{name}", unit_type.id)
|
name: str,
|
||||||
|
position: Point,
|
||||||
|
heading: int,
|
||||||
|
) -> Vehicle:
|
||||||
|
unit = Vehicle(self.game.next_unit_id(), f"{group.name}|{name}", unit_type.id)
|
||||||
unit.position = position
|
unit.position = position
|
||||||
unit.heading = heading
|
unit.heading = heading
|
||||||
group.add_unit(unit)
|
group.add_unit(unit)
|
||||||
@ -82,31 +91,36 @@ class GroupGenerator:
|
|||||||
current_offset = self.heading
|
current_offset = self.heading
|
||||||
current_offset -= outer_offset * (math.ceil(num_units / 2) - 1)
|
current_offset -= outer_offset * (math.ceil(num_units / 2) - 1)
|
||||||
for x in range(1, num_units + 1):
|
for x in range(1, num_units + 1):
|
||||||
positions.append((
|
positions.append(
|
||||||
self.position.x + launcher_distance * math.cos(math.radians(current_offset)),
|
(
|
||||||
self.position.y + launcher_distance * math.sin(math.radians(current_offset)),
|
self.position.x
|
||||||
|
+ launcher_distance * math.cos(math.radians(current_offset)),
|
||||||
|
self.position.y
|
||||||
|
+ launcher_distance * math.sin(math.radians(current_offset)),
|
||||||
current_offset,
|
current_offset,
|
||||||
))
|
)
|
||||||
|
)
|
||||||
current_offset += outer_offset
|
current_offset += outer_offset
|
||||||
return positions
|
return positions
|
||||||
|
|
||||||
|
|
||||||
class ShipGroupGenerator(GroupGenerator):
|
class ShipGroupGenerator(GroupGenerator):
|
||||||
"""Abstract class for other ship generator classes"""
|
"""Abstract class for other ship generator classes"""
|
||||||
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
|
|
||||||
|
def __init__(
|
||||||
|
self, game: Game, ground_object: TheaterGroundObject, faction: Faction
|
||||||
|
):
|
||||||
self.game = game
|
self.game = game
|
||||||
self.go = ground_object
|
self.go = ground_object
|
||||||
self.position = ground_object.position
|
self.position = ground_object.position
|
||||||
self.heading = random.randint(0, 359)
|
self.heading = random.randint(0, 359)
|
||||||
self.faction = faction
|
self.faction = faction
|
||||||
self.vg = unitgroup.ShipGroup(self.game.next_group_id(),
|
self.vg = unitgroup.ShipGroup(self.game.next_group_id(), self.go.group_name)
|
||||||
self.go.group_name)
|
|
||||||
wp = self.vg.add_waypoint(self.position, 0)
|
wp = self.vg.add_waypoint(self.position, 0)
|
||||||
wp.ETA_locked = True
|
wp.ETA_locked = True
|
||||||
|
|
||||||
def add_unit(self, unit_type, name, pos_x, pos_y, heading) -> Ship:
|
def add_unit(self, unit_type, name, pos_x, pos_y, heading) -> Ship:
|
||||||
unit = Ship(self.game.next_unit_id(),
|
unit = Ship(self.game.next_unit_id(), f"{self.go.group_name}|{name}", unit_type)
|
||||||
f"{self.go.group_name}|{name}", unit_type)
|
|
||||||
unit.position.x = pos_x
|
unit.position.x = pos_x
|
||||||
unit.position.y = pos_y
|
unit.position.y = pos_y
|
||||||
unit.heading = heading
|
unit.heading = heading
|
||||||
|
|||||||
@ -19,10 +19,24 @@ class AvengerGenerator(AirDefenseGroupGenerator):
|
|||||||
def generate(self):
|
def generate(self):
|
||||||
num_launchers = random.randint(2, 3)
|
num_launchers = random.randint(2, 3)
|
||||||
|
|
||||||
self.add_unit(Unarmed.Transport_M818, "TRUCK", self.position.x, self.position.y, self.heading)
|
self.add_unit(
|
||||||
positions = self.get_circular_position(num_launchers, launcher_distance=110, coverage=180)
|
Unarmed.Transport_M818,
|
||||||
|
"TRUCK",
|
||||||
|
self.position.x,
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
positions = self.get_circular_position(
|
||||||
|
num_launchers, launcher_distance=110, coverage=180
|
||||||
|
)
|
||||||
for i, position in enumerate(positions):
|
for i, position in enumerate(positions):
|
||||||
self.add_unit(AirDefence.SAM_Avenger_M1097, "SPAA#" + str(i), position[0], position[1], position[2])
|
self.add_unit(
|
||||||
|
AirDefence.SAM_Avenger_M1097,
|
||||||
|
"SPAA#" + str(i),
|
||||||
|
position[0],
|
||||||
|
position[1],
|
||||||
|
position[2],
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def range(cls) -> AirDefenseRange:
|
def range(cls) -> AirDefenseRange:
|
||||||
|
|||||||
@ -19,10 +19,24 @@ class ChaparralGenerator(AirDefenseGroupGenerator):
|
|||||||
def generate(self):
|
def generate(self):
|
||||||
num_launchers = random.randint(2, 4)
|
num_launchers = random.randint(2, 4)
|
||||||
|
|
||||||
self.add_unit(Unarmed.Transport_M818, "TRUCK", self.position.x, self.position.y, self.heading)
|
self.add_unit(
|
||||||
positions = self.get_circular_position(num_launchers, launcher_distance=110, coverage=180)
|
Unarmed.Transport_M818,
|
||||||
|
"TRUCK",
|
||||||
|
self.position.x,
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
positions = self.get_circular_position(
|
||||||
|
num_launchers, launcher_distance=110, coverage=180
|
||||||
|
)
|
||||||
for i, position in enumerate(positions):
|
for i, position in enumerate(positions):
|
||||||
self.add_unit(AirDefence.SAM_Chaparral_M48, "SPAA#" + str(i), position[0], position[1], position[2])
|
self.add_unit(
|
||||||
|
AirDefence.SAM_Chaparral_M48,
|
||||||
|
"SPAA#" + str(i),
|
||||||
|
position[0],
|
||||||
|
position[1],
|
||||||
|
position[2],
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def range(cls) -> AirDefenseRange:
|
def range(cls) -> AirDefenseRange:
|
||||||
|
|||||||
@ -17,10 +17,28 @@ class GepardGenerator(AirDefenseGroupGenerator):
|
|||||||
price = 50
|
price = 50
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
self.add_unit(AirDefence.SPAAA_Gepard, "SPAAA", self.position.x, self.position.y, self.heading)
|
self.add_unit(
|
||||||
|
AirDefence.SPAAA_Gepard,
|
||||||
|
"SPAAA",
|
||||||
|
self.position.x,
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
if random.randint(0, 1) == 1:
|
if random.randint(0, 1) == 1:
|
||||||
self.add_unit(AirDefence.SPAAA_Gepard, "SPAAA2", self.position.x, self.position.y, self.heading)
|
self.add_unit(
|
||||||
self.add_unit(Unarmed.Transport_M818, "TRUCK", self.position.x + 80, self.position.y, self.heading)
|
AirDefence.SPAAA_Gepard,
|
||||||
|
"SPAAA2",
|
||||||
|
self.position.x,
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
Unarmed.Transport_M818,
|
||||||
|
"TRUCK",
|
||||||
|
self.position.x + 80,
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def range(cls) -> AirDefenseRange:
|
def range(cls) -> AirDefenseRange:
|
||||||
|
|||||||
@ -49,7 +49,12 @@ from gen.sam.sam_roland import RolandGenerator
|
|||||||
from gen.sam.sam_sa10 import (
|
from gen.sam.sam_sa10 import (
|
||||||
SA10Generator,
|
SA10Generator,
|
||||||
Tier2SA10Generator,
|
Tier2SA10Generator,
|
||||||
Tier3SA10Generator, SA10BGenerator, SA12Generator, SA20Generator, SA20BGenerator, SA23Generator,
|
Tier3SA10Generator,
|
||||||
|
SA10BGenerator,
|
||||||
|
SA12Generator,
|
||||||
|
SA20Generator,
|
||||||
|
SA20BGenerator,
|
||||||
|
SA23Generator,
|
||||||
)
|
)
|
||||||
from gen.sam.sam_sa11 import SA11Generator
|
from gen.sam.sam_sa11 import SA11Generator
|
||||||
from gen.sam.sam_sa13 import SA13Generator
|
from gen.sam.sam_sa13 import SA13Generator
|
||||||
@ -103,7 +108,6 @@ SAM_MAP: Dict[str, Type[AirDefenseGroupGenerator]] = {
|
|||||||
"FreyaGenerator": FreyaGenerator,
|
"FreyaGenerator": FreyaGenerator,
|
||||||
"AllyWW2FlakGenerator": AllyWW2FlakGenerator,
|
"AllyWW2FlakGenerator": AllyWW2FlakGenerator,
|
||||||
"ZSU57Generator": ZSU57Generator,
|
"ZSU57Generator": ZSU57Generator,
|
||||||
|
|
||||||
"KS19Generator": KS19Generator,
|
"KS19Generator": KS19Generator,
|
||||||
"SA10BGenerator": SA10BGenerator,
|
"SA10BGenerator": SA10BGenerator,
|
||||||
"SA12Generator": SA12Generator,
|
"SA12Generator": SA12Generator,
|
||||||
@ -145,7 +149,7 @@ SAM_PRICES = {
|
|||||||
AirDefence.SAM_SA_13_Strela_10M3_9A35M3: 30,
|
AirDefence.SAM_SA_13_Strela_10M3_9A35M3: 30,
|
||||||
AirDefence.SAM_SA_15_Tor_9A331: 40,
|
AirDefence.SAM_SA_15_Tor_9A331: 40,
|
||||||
AirDefence.SAM_SA_19_Tunguska_2S6: 35,
|
AirDefence.SAM_SA_19_Tunguska_2S6: 35,
|
||||||
AirDefence.HQ_7_Self_Propelled_LN: 35
|
AirDefence.HQ_7_Self_Propelled_LN: 35,
|
||||||
}
|
}
|
||||||
|
|
||||||
EWR_MAP = {
|
EWR_MAP = {
|
||||||
@ -163,7 +167,8 @@ EWR_MAP = {
|
|||||||
|
|
||||||
|
|
||||||
def get_faction_possible_sams_generator(
|
def get_faction_possible_sams_generator(
|
||||||
faction: Faction) -> List[Type[AirDefenseGroupGenerator]]:
|
faction: Faction,
|
||||||
|
) -> List[Type[AirDefenseGroupGenerator]]:
|
||||||
"""
|
"""
|
||||||
Return the list of possible SAM generator for the given faction
|
Return the list of possible SAM generator for the given faction
|
||||||
:param faction: Faction name to search units for
|
:param faction: Faction name to search units for
|
||||||
@ -180,8 +185,10 @@ def get_faction_possible_ewrs_generator(faction: Faction) -> List[Type[GroupGene
|
|||||||
|
|
||||||
|
|
||||||
def _generate_anti_air_from(
|
def _generate_anti_air_from(
|
||||||
generators: Sequence[Type[AirDefenseGroupGenerator]], game: Game,
|
generators: Sequence[Type[AirDefenseGroupGenerator]],
|
||||||
ground_object: SamGroundObject) -> List[VehicleGroup]:
|
game: Game,
|
||||||
|
ground_object: SamGroundObject,
|
||||||
|
) -> List[VehicleGroup]:
|
||||||
if not generators:
|
if not generators:
|
||||||
return []
|
return []
|
||||||
sam_generator_class = random.choice(generators)
|
sam_generator_class = random.choice(generators)
|
||||||
@ -191,8 +198,10 @@ def _generate_anti_air_from(
|
|||||||
|
|
||||||
|
|
||||||
def generate_anti_air_group(
|
def generate_anti_air_group(
|
||||||
game: Game, ground_object: SamGroundObject, faction: Faction,
|
game: Game,
|
||||||
ranges: Optional[Iterable[Set[AirDefenseRange]]] = None
|
ground_object: SamGroundObject,
|
||||||
|
faction: Faction,
|
||||||
|
ranges: Optional[Iterable[Set[AirDefenseRange]]] = None,
|
||||||
) -> List[VehicleGroup]:
|
) -> List[VehicleGroup]:
|
||||||
"""
|
"""
|
||||||
This generate a SAM group
|
This generate a SAM group
|
||||||
@ -213,24 +222,25 @@ def generate_anti_air_group(
|
|||||||
"""
|
"""
|
||||||
generators = get_faction_possible_sams_generator(faction)
|
generators = get_faction_possible_sams_generator(faction)
|
||||||
if ranges is None:
|
if ranges is None:
|
||||||
ranges = [{
|
ranges = [
|
||||||
|
{
|
||||||
AirDefenseRange.Long,
|
AirDefenseRange.Long,
|
||||||
AirDefenseRange.Medium,
|
AirDefenseRange.Medium,
|
||||||
AirDefenseRange.Short,
|
AirDefenseRange.Short,
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
|
|
||||||
for range_options in ranges:
|
for range_options in ranges:
|
||||||
generators_for_range = [g for g in generators if
|
generators_for_range = [g for g in generators if g.range() in range_options]
|
||||||
g.range() in range_options]
|
groups = _generate_anti_air_from(generators_for_range, game, ground_object)
|
||||||
groups = _generate_anti_air_from(generators_for_range, game,
|
|
||||||
ground_object)
|
|
||||||
if groups:
|
if groups:
|
||||||
return groups
|
return groups
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def generate_ewr_group(game: Game, ground_object: TheaterGroundObject,
|
def generate_ewr_group(
|
||||||
faction: Faction) -> Optional[VehicleGroup]:
|
game: Game, ground_object: TheaterGroundObject, faction: Faction
|
||||||
|
) -> Optional[VehicleGroup]:
|
||||||
"""Generates an early warning radar group.
|
"""Generates an early warning radar group.
|
||||||
|
|
||||||
:param game: The Game.
|
:param game: The Game.
|
||||||
|
|||||||
@ -18,20 +18,51 @@ class HawkGenerator(AirDefenseGroupGenerator):
|
|||||||
price = 115
|
price = 115
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
self.add_unit(AirDefence.SAM_Hawk_SR_AN_MPQ_50, "SR", self.position.x + 20, self.position.y, self.heading)
|
self.add_unit(
|
||||||
self.add_unit(AirDefence.SAM_Hawk_PCP, "PCP", self.position.x, self.position.y, self.heading)
|
AirDefence.SAM_Hawk_SR_AN_MPQ_50,
|
||||||
self.add_unit(AirDefence.SAM_Hawk_TR_AN_MPQ_46, "TR", self.position.x + 40, self.position.y, self.heading)
|
"SR",
|
||||||
|
self.position.x + 20,
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
AirDefence.SAM_Hawk_PCP,
|
||||||
|
"PCP",
|
||||||
|
self.position.x,
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
AirDefence.SAM_Hawk_TR_AN_MPQ_46,
|
||||||
|
"TR",
|
||||||
|
self.position.x + 40,
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
# Triple A for close range defense
|
# Triple A for close range defense
|
||||||
aa_group = self.add_auxiliary_group("AA")
|
aa_group = self.add_auxiliary_group("AA")
|
||||||
self.add_unit_to_group(aa_group, AirDefence.AAA_Vulcan_M163, "AAA",
|
self.add_unit_to_group(
|
||||||
self.position + Point(20, 30), self.heading)
|
aa_group,
|
||||||
|
AirDefence.AAA_Vulcan_M163,
|
||||||
|
"AAA",
|
||||||
|
self.position + Point(20, 30),
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
num_launchers = random.randint(3, 6)
|
num_launchers = random.randint(3, 6)
|
||||||
positions = self.get_circular_position(num_launchers, launcher_distance=120, coverage=180)
|
positions = self.get_circular_position(
|
||||||
|
num_launchers, launcher_distance=120, coverage=180
|
||||||
|
)
|
||||||
|
|
||||||
for i, position in enumerate(positions):
|
for i, position in enumerate(positions):
|
||||||
self.add_unit(AirDefence.SAM_Hawk_LN_M192, "LN#" + str(i), position[0], position[1], position[2])
|
self.add_unit(
|
||||||
|
AirDefence.SAM_Hawk_LN_M192,
|
||||||
|
"LN#" + str(i),
|
||||||
|
position[0],
|
||||||
|
position[1],
|
||||||
|
position[2],
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def range(cls) -> AirDefenseRange:
|
def range(cls) -> AirDefenseRange:
|
||||||
|
|||||||
@ -18,23 +18,51 @@ class HQ7Generator(AirDefenseGroupGenerator):
|
|||||||
price = 120
|
price = 120
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
self.add_unit(AirDefence.HQ_7_Self_Propelled_STR, "STR", self.position.x, self.position.y, self.heading)
|
self.add_unit(
|
||||||
self.add_unit(AirDefence.HQ_7_Self_Propelled_LN, "LN", self.position.x + 20, self.position.y, self.heading)
|
AirDefence.HQ_7_Self_Propelled_STR,
|
||||||
|
"STR",
|
||||||
|
self.position.x,
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
AirDefence.HQ_7_Self_Propelled_LN,
|
||||||
|
"LN",
|
||||||
|
self.position.x + 20,
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
# Triple A for close range defense
|
# Triple A for close range defense
|
||||||
aa_group = self.add_auxiliary_group("AA")
|
aa_group = self.add_auxiliary_group("AA")
|
||||||
self.add_unit_to_group(aa_group, AirDefence.AAA_ZU_23_on_Ural_375,
|
self.add_unit_to_group(
|
||||||
"AAA1", self.position + Point(20, 30),
|
aa_group,
|
||||||
self.heading)
|
AirDefence.AAA_ZU_23_on_Ural_375,
|
||||||
self.add_unit_to_group(aa_group, AirDefence.AAA_ZU_23_on_Ural_375,
|
"AAA1",
|
||||||
"AAA2", self.position - Point(20, 30),
|
self.position + Point(20, 30),
|
||||||
self.heading)
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit_to_group(
|
||||||
|
aa_group,
|
||||||
|
AirDefence.AAA_ZU_23_on_Ural_375,
|
||||||
|
"AAA2",
|
||||||
|
self.position - Point(20, 30),
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
num_launchers = random.randint(0, 3)
|
num_launchers = random.randint(0, 3)
|
||||||
if num_launchers > 0:
|
if num_launchers > 0:
|
||||||
positions = self.get_circular_position(num_launchers, launcher_distance=120, coverage=360)
|
positions = self.get_circular_position(
|
||||||
|
num_launchers, launcher_distance=120, coverage=360
|
||||||
|
)
|
||||||
for i, position in enumerate(positions):
|
for i, position in enumerate(positions):
|
||||||
self.add_unit(AirDefence.HQ_7_Self_Propelled_LN, "LN#" + str(i), position[0], position[1], position[2])
|
self.add_unit(
|
||||||
|
AirDefence.HQ_7_Self_Propelled_LN,
|
||||||
|
"LN#" + str(i),
|
||||||
|
position[0],
|
||||||
|
position[1],
|
||||||
|
position[2],
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def range(cls) -> AirDefenseRange:
|
def range(cls) -> AirDefenseRange:
|
||||||
|
|||||||
@ -19,10 +19,24 @@ class LinebackerGenerator(AirDefenseGroupGenerator):
|
|||||||
def generate(self):
|
def generate(self):
|
||||||
num_launchers = random.randint(2, 4)
|
num_launchers = random.randint(2, 4)
|
||||||
|
|
||||||
self.add_unit(Unarmed.Transport_M818, "TRUCK", self.position.x, self.position.y, self.heading)
|
self.add_unit(
|
||||||
positions = self.get_circular_position(num_launchers, launcher_distance=110, coverage=180)
|
Unarmed.Transport_M818,
|
||||||
|
"TRUCK",
|
||||||
|
self.position.x,
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
positions = self.get_circular_position(
|
||||||
|
num_launchers, launcher_distance=110, coverage=180
|
||||||
|
)
|
||||||
for i, position in enumerate(positions):
|
for i, position in enumerate(positions):
|
||||||
self.add_unit(AirDefence.SAM_Linebacker_M6, "M6#" + str(i), position[0], position[1], position[2])
|
self.add_unit(
|
||||||
|
AirDefence.SAM_Linebacker_M6,
|
||||||
|
"M6#" + str(i),
|
||||||
|
position[0],
|
||||||
|
position[1],
|
||||||
|
position[2],
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def range(cls) -> AirDefenseRange:
|
def range(cls) -> AirDefenseRange:
|
||||||
|
|||||||
@ -19,24 +19,65 @@ class PatriotGenerator(AirDefenseGroupGenerator):
|
|||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
# Command Post
|
# Command Post
|
||||||
self.add_unit(AirDefence.SAM_Patriot_STR_AN_MPQ_53, "STR", self.position.x + 30, self.position.y + 30, self.heading)
|
self.add_unit(
|
||||||
self.add_unit(AirDefence.SAM_Patriot_AMG_AN_MRC_137, "MRC", self.position.x, self.position.y, self.heading)
|
AirDefence.SAM_Patriot_STR_AN_MPQ_53,
|
||||||
self.add_unit(AirDefence.SAM_Patriot_ECS_AN_MSQ_104, "MSQ", self.position.x + 30, self.position.y, self.heading)
|
"STR",
|
||||||
self.add_unit(AirDefence.SAM_Patriot_ICC, "ICC", self.position.x + 60, self.position.y, self.heading)
|
self.position.x + 30,
|
||||||
self.add_unit(AirDefence.SAM_Patriot_EPP_III, "EPP", self.position.x, self.position.y + 30, self.heading)
|
self.position.y + 30,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
AirDefence.SAM_Patriot_AMG_AN_MRC_137,
|
||||||
|
"MRC",
|
||||||
|
self.position.x,
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
AirDefence.SAM_Patriot_ECS_AN_MSQ_104,
|
||||||
|
"MSQ",
|
||||||
|
self.position.x + 30,
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
AirDefence.SAM_Patriot_ICC,
|
||||||
|
"ICC",
|
||||||
|
self.position.x + 60,
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
AirDefence.SAM_Patriot_EPP_III,
|
||||||
|
"EPP",
|
||||||
|
self.position.x,
|
||||||
|
self.position.y + 30,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
num_launchers = random.randint(3, 4)
|
num_launchers = random.randint(3, 4)
|
||||||
positions = self.get_circular_position(num_launchers, launcher_distance=120, coverage=360)
|
positions = self.get_circular_position(
|
||||||
|
num_launchers, launcher_distance=120, coverage=360
|
||||||
|
)
|
||||||
for i, position in enumerate(positions):
|
for i, position in enumerate(positions):
|
||||||
self.add_unit(AirDefence.SAM_Patriot_LN_M901, "LN#" + str(i), position[0], position[1], position[2])
|
self.add_unit(
|
||||||
|
AirDefence.SAM_Patriot_LN_M901,
|
||||||
|
"LN#" + str(i),
|
||||||
|
position[0],
|
||||||
|
position[1],
|
||||||
|
position[2],
|
||||||
|
)
|
||||||
|
|
||||||
# Short range protection for high value site
|
# Short range protection for high value site
|
||||||
aa_group = self.add_auxiliary_group("AA")
|
aa_group = self.add_auxiliary_group("AA")
|
||||||
num_launchers = random.randint(3, 4)
|
num_launchers = random.randint(3, 4)
|
||||||
positions = self.get_circular_position(num_launchers, launcher_distance=200, coverage=360)
|
positions = self.get_circular_position(
|
||||||
|
num_launchers, launcher_distance=200, coverage=360
|
||||||
|
)
|
||||||
for i, (x, y, heading) in enumerate(positions):
|
for i, (x, y, heading) in enumerate(positions):
|
||||||
self.add_unit_to_group(aa_group, AirDefence.AAA_Vulcan_M163,
|
self.add_unit_to_group(
|
||||||
f"SPAAA#{i}", Point(x, y), heading)
|
aa_group, AirDefence.AAA_Vulcan_M163, f"SPAAA#{i}", Point(x, y), heading
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def range(cls) -> AirDefenseRange:
|
def range(cls) -> AirDefenseRange:
|
||||||
|
|||||||
@ -17,14 +17,34 @@ class RapierGenerator(AirDefenseGroupGenerator):
|
|||||||
price = 50
|
price = 50
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
self.add_unit(AirDefence.Rapier_FSA_Blindfire_Tracker, "BT", self.position.x, self.position.y, self.heading)
|
self.add_unit(
|
||||||
self.add_unit(AirDefence.Rapier_FSA_Optical_Tracker, "OT", self.position.x + 20, self.position.y, self.heading)
|
AirDefence.Rapier_FSA_Blindfire_Tracker,
|
||||||
|
"BT",
|
||||||
|
self.position.x,
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
self.add_unit(
|
||||||
|
AirDefence.Rapier_FSA_Optical_Tracker,
|
||||||
|
"OT",
|
||||||
|
self.position.x + 20,
|
||||||
|
self.position.y,
|
||||||
|
self.heading,
|
||||||
|
)
|
||||||
|
|
||||||
num_launchers = random.randint(3, 6)
|
num_launchers = random.randint(3, 6)
|
||||||
positions = self.get_circular_position(num_launchers, launcher_distance=80, coverage=240)
|
positions = self.get_circular_position(
|
||||||
|
num_launchers, launcher_distance=80, coverage=240
|
||||||
|
)
|
||||||
|
|
||||||
for i, position in enumerate(positions):
|
for i, position in enumerate(positions):
|
||||||
self.add_unit(AirDefence.Rapier_FSA_Launcher, "LN#" + str(i), position[0], position[1], position[2])
|
self.add_unit(
|
||||||
|
AirDefence.Rapier_FSA_Launcher,
|
||||||
|
"LN#" + str(i),
|
||||||
|
position[0],
|
||||||
|
position[1],
|
||||||
|
position[2],
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def range(cls) -> AirDefenseRange:
|
def range(cls) -> AirDefenseRange:
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user