mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Ran black reformater on 2.4x branch
This commit is contained in:
parent
c2310453d8
commit
523ef08697
@ -17,5 +17,5 @@ AAA_UNITS = [
|
||||
AirDefence.AAA_Flak_Vierling_38,
|
||||
AirDefence.AAA_Kdo_G_40,
|
||||
AirDefence.AAA_8_8cm_Flak_41,
|
||||
AirDefence.AAA_Bofors_40mm
|
||||
]
|
||||
AirDefence.AAA_Bofors_40mm,
|
||||
]
|
||||
|
||||
@ -1,17 +1,70 @@
|
||||
import inspect
|
||||
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_GERMANY_BUILDINGS = ['fuel', 'factory', 'ww2bunker', 'ww2bunker', 'ww2bunker', 'allycamp', 'allycamp', 'fob']
|
||||
WW2_ALLIES_BUILDINGS = ['fuel', 'factory', 'allycamp', 'allycamp', 'allycamp', 'allycamp', 'allycamp', 'fob']
|
||||
WW2_FREE = ["fuel", "factory", "ware", "fob"]
|
||||
WW2_GERMANY_BUILDINGS = [
|
||||
"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',
|
||||
'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_BUILDINGS = [
|
||||
"Siegfried Line",
|
||||
"Concertina wire",
|
||||
"Concertina Wire",
|
||||
"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_ID = [c.id for c in vars(dcs.vehicles.Fortification).values() if inspect.isclass(c)]
|
||||
FORTIFICATION_UNITS = [
|
||||
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_30_NA,
|
||||
SpitfireLFMkIX,
|
||||
SpitfireLFMkIXCW
|
||||
SpitfireLFMkIXCW,
|
||||
)
|
||||
|
||||
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
|
||||
"""
|
||||
GUNFIGHTERS = [
|
||||
|
||||
# Cold War
|
||||
MiG_15bis,
|
||||
MiG_19P,
|
||||
@ -34,11 +33,9 @@ GUNFIGHTERS = [
|
||||
F_86F_Sabre,
|
||||
A_4E_C,
|
||||
F_5E_3,
|
||||
|
||||
# Trainers
|
||||
C_101CC,
|
||||
L_39ZA,
|
||||
|
||||
# WW2
|
||||
P_51D_30_NA,
|
||||
P_51D,
|
||||
@ -51,5 +48,4 @@ GUNFIGHTERS = [
|
||||
FW_190D9,
|
||||
FW_190A8,
|
||||
I_16,
|
||||
|
||||
]
|
||||
]
|
||||
|
||||
@ -23,7 +23,6 @@ from dcs.ships import (
|
||||
from dcs.vehicles import AirDefence
|
||||
|
||||
UNITS_WITH_RADAR = [
|
||||
|
||||
# Radars
|
||||
AirDefence.SAM_SA_15_Tor_9A331,
|
||||
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_2_TR_SNR_75_Fan_Song,
|
||||
AirDefence.HQ_7_Self_Propelled_STR,
|
||||
|
||||
# Ships
|
||||
CVN_70_Carl_Vinson,
|
||||
Oliver_Hazzard_Perry_class,
|
||||
@ -70,5 +68,5 @@ UNITS_WITH_RADAR = [
|
||||
LHA_1_Tarawa,
|
||||
Type_052B_Destroyer,
|
||||
Type_054A_Frigate,
|
||||
Type_052C_Destroyer
|
||||
]
|
||||
Type_052C_Destroyer,
|
||||
]
|
||||
|
||||
@ -28,7 +28,8 @@ class Weapon:
|
||||
introduction_year = WEAPON_INTRODUCTION_YEARS.get(self)
|
||||
if introduction_year is None:
|
||||
logging.warning(
|
||||
f"No introduction year for {self}, assuming always available")
|
||||
f"No introduction year for {self}, assuming always available"
|
||||
)
|
||||
return True
|
||||
return date >= datetime.date(introduction_year, 1, 1)
|
||||
|
||||
@ -52,7 +53,7 @@ class Weapon:
|
||||
return cls(
|
||||
cast(str, weapon_data["clsid"]),
|
||||
cast(str, weapon_data["name"]),
|
||||
cast(int, weapon_data["weight"])
|
||||
cast(int, weapon_data["weight"]),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@ -88,8 +89,11 @@ class Pylon:
|
||||
def for_aircraft(cls, aircraft: Type[FlyingType], number: int) -> Pylon:
|
||||
# In pydcs these are all arbitrary inner classes of the aircraft type.
|
||||
# The only way to identify them is by their name.
|
||||
pylons = [v for v in aircraft.__dict__.values() if
|
||||
inspect.isclass(v) and v.__name__.startswith("Pylon")]
|
||||
pylons = [
|
||||
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
|
||||
# values of (pylon number, allowed weapon).
|
||||
@ -117,112 +121,92 @@ _WEAPON_FALLBACKS = [
|
||||
(Weapons.ADM_141A_, None),
|
||||
(Weapons.ADM_141A__, None),
|
||||
(Weapons.ADM_141B, None),
|
||||
|
||||
# AGM-114K Hellfire
|
||||
(Weapons.AGM114x2_OH_58, Weapons.M260_HYDRA), # assuming OH-58 and not MQ-9
|
||||
(Weapons.AGM_114K, None), # Only for RQ-1
|
||||
(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___4, Weapons.LAU_61___19_2_75__rockets_MK151_HE),
|
||||
|
||||
# AGM-119 Penguin
|
||||
(Weapons.AGM_119B_Penguin, Weapons.Mk_82),
|
||||
|
||||
# AGM-122 Sidearm
|
||||
(Weapons.AGM_122, None), # No known aircraft carries this
|
||||
(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, None), # No known aircraft carries this
|
||||
(Weapons.AGM_122_Sidearm, Weapons.GBU_12), # outer pylons harrier
|
||||
(Weapons.AGM_122_Sidearm_, Weapons.LAU_117_AGM_65E), # internal pylons harrier
|
||||
# AGM-154 JSOW
|
||||
(Weapons.AGM_154A, Weapons.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_154C, Weapons.GBU_12),
|
||||
(Weapons.AGM_154C_4, Weapons.GBU_31_8),
|
||||
(Weapons.BRU_55___2_x_AGM_154C, Weapons.BRU_33___2_x_GBU_12),
|
||||
|
||||
# AGM-45 Shrike
|
||||
(Weapons.AGM_45A, None),
|
||||
(Weapons.AGM_45B, Weapons.AGM_45A),
|
||||
(Weapons.AGM_45B_, Weapons.AGM_45A),
|
||||
|
||||
# AGM-62 Walleye
|
||||
(Weapons.AGM_62, Weapons.Mk_84),
|
||||
|
||||
# AGM-65 Maverick
|
||||
(Weapons.AGM_65D, None), # doesn't exist
|
||||
(Weapons.AGM_65E, None), # doesn't exist
|
||||
(Weapons.AGM_65K, 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_65D, Weapons.AGM_62), # Walleye is the predecessor to the maverick
|
||||
(Weapons.AGM_65D, None), # doesn't exist
|
||||
(Weapons.AGM_65E, None), # doesn't exist
|
||||
(Weapons.AGM_65K, 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_65D,
|
||||
Weapons.AGM_62,
|
||||
), # Walleye is the predecessor to the maverick
|
||||
(Weapons.LAU_117_AGM_65E, None),
|
||||
(Weapons.LAU_117_AGM_65F, 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_65K, Weapons.LAU_117_AGM_65D),
|
||||
(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_3, 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_3, Weapons.LAU_88_AGM_65D_3),
|
||||
|
||||
(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_R, Weapons.LAU_88_AGM_65D_2_),
|
||||
(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_3, Weapons.LAU_88_AGM_65D_3),
|
||||
|
||||
# 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_8, Weapons._27_Mk_82),
|
||||
(Weapons.AGM_84D, Weapons.AGM_62),
|
||||
(Weapons.AGM_84E, Weapons.LAU_117_AGM_65F),
|
||||
(Weapons.AGM_84H, Weapons.AGM_84E),
|
||||
|
||||
# AGM-86 ALCM
|
||||
(Weapons.AGM_86C, Weapons._27_Mk_82),
|
||||
(Weapons.AGM_86C_20, Weapons._27_Mk_82),
|
||||
(Weapons.AGM_86C_8, Weapons._27_Mk_82),
|
||||
(Weapons.MER_6_AGM_86C, Weapons.MER_12_Mk_82),
|
||||
|
||||
# AGM-88 HARM
|
||||
(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.LAU_115___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.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),
|
||||
|
||||
# AIM-54 Phoenix
|
||||
(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_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
|
||||
(Weapons.AIM_7E, None),
|
||||
(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.LAU_115C_AIM_7E, None),
|
||||
(Weapons.LAU_115C_AIM_7F, Weapons.LAU_115C_AIM_7E),
|
||||
(Weapons.LAU_115___AIM_7M, Weapons.LAU_115C_AIM_7F),
|
||||
(Weapons.LAU_115C_AIM_7MH, Weapons.LAU_115___AIM_7M),
|
||||
|
||||
# AIM-9 Sidewinder
|
||||
(Weapons.AIM_9L_Sidewinder_IR_AAM, None),
|
||||
(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_9P_Sidewinder_IR_AAM, Weapons.AIM_9L_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_R, None),
|
||||
(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_2_AIM_9L, None),
|
||||
(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_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_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_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_9X, Weapons.LAU_115_LAU_127_AIM_9M),
|
||||
|
||||
(Weapons.LAU_127_AIM_9L, None),
|
||||
(Weapons.LAU_127_AIM_9M, Weapons.LAU_127_AIM_9L),
|
||||
(Weapons.LAU_127_AIM_9X, Weapons.LAU_127_AIM_9M),
|
||||
|
||||
(Weapons.LAU_138_AIM_9L, None),
|
||||
(Weapons.LAU_138_AIM_9M, Weapons.LAU_138_AIM_9L),
|
||||
|
||||
(Weapons.LAU_7_AIM_9L, None),
|
||||
(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_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_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_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_9P_Sidewinder_IR_AAM, Weapons.LAU_7___2_AIM_9L_Sidewinder_IR_AAM),
|
||||
|
||||
(
|
||||
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_9P_Sidewinder_IR_AAM,
|
||||
Weapons.LAU_7___2_AIM_9L_Sidewinder_IR_AAM,
|
||||
),
|
||||
# ALQ ECM Pods
|
||||
(Weapons.ALQ_131, None),
|
||||
(Weapons.ALQ_184, Weapons.ALQ_131),
|
||||
(Weapons.AN_ALQ_164_DECM_Pod, None),
|
||||
|
||||
# TGP Pods
|
||||
(Weapons.AN_AAQ_28_LITENING_, None),
|
||||
(Weapons.AN_AAQ_28_LITENING, Weapons.Lantirn_F_16),
|
||||
@ -300,17 +283,14 @@ _WEAPON_FALLBACKS = [
|
||||
(Weapons.Lantirn_F_16, None),
|
||||
(Weapons.Lantirn_Target_Pod, None),
|
||||
(Weapons.Pavetack_F_111, None),
|
||||
|
||||
# BLU-107
|
||||
(Weapons.BLU_107, None),
|
||||
(Weapons.MER_6_BLU_107, Weapons.MER_6_Mk_82),
|
||||
|
||||
# GBU-10 LGB
|
||||
(Weapons.DIS_GBU_10, Weapons.Mk_84),
|
||||
(Weapons.GBU_10, Weapons.Mk_84),
|
||||
(Weapons.GBU_10_, Weapons.Mk_84),
|
||||
(Weapons.GBU_10_2, Weapons.Mk_84),
|
||||
|
||||
# GBU-12 LGB
|
||||
(Weapons.AUF2_GBU_12_x_2, None),
|
||||
(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._3_GBU_12, Weapons._3_Mk_82_),
|
||||
|
||||
# GBU-15 LGB
|
||||
(Weapons.GBU_15, Weapons.Mk_84),
|
||||
|
||||
# GBU-16 LGB
|
||||
(Weapons.BRU_33___2_x_GBU_16, None),
|
||||
(Weapons.DIS_GBU_16, Weapons.Mk_83),
|
||||
(Weapons.GBU_16, Weapons.Mk_83),
|
||||
(Weapons.GBU_16_, Weapons.Mk_83_),
|
||||
|
||||
(Weapons.DIS_GBU_16, Weapons.Mk_83),
|
||||
(Weapons.GBU_16, Weapons.Mk_83),
|
||||
(Weapons.GBU_16_, Weapons.Mk_83_),
|
||||
# GBU-24 LGB
|
||||
(Weapons.GBU_24, Weapons.GBU_10),
|
||||
(Weapons.GBU_24_, Weapons.GBU_10_),
|
||||
(Weapons.GBU_24__, Weapons.GBU_10_),
|
||||
|
||||
# GBU-27 LGB
|
||||
(Weapons.GBU_24, Weapons.GBU_10),
|
||||
(Weapons.GBU_24_, Weapons.GBU_10_),
|
||||
(Weapons.GBU_24__, Weapons.GBU_10_),
|
||||
|
||||
# GBU-28 LGB
|
||||
(Weapons.GBU_28, Weapons.GBU_15),
|
||||
|
||||
# GBU-31 JDAM
|
||||
(Weapons.GBU_31V3B_8, Weapons.B_1B_Mk_84_8),
|
||||
(Weapons.GBU_31_8, Weapons.B_1B_Mk_84_8),
|
||||
@ -358,150 +332,127 @@ _WEAPON_FALLBACKS = [
|
||||
(Weapons.GBU_31_V_2_B, Weapons.GBU_24_),
|
||||
(Weapons.GBU_31_V_3_B, Weapons.GBU_24_),
|
||||
(Weapons.GBU_31_V_4_B, Weapons.GBU_24_),
|
||||
|
||||
# GBU-32 JDAM
|
||||
(Weapons.GBU_32_V_2_B, Weapons.GBU_16),
|
||||
|
||||
# GBU-32 JDAM
|
||||
(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
|
||||
(Weapons.GBU_38, Weapons.Mk_82),
|
||||
(Weapons.GBU_38_16, Weapons.MK_82_28),
|
||||
(Weapons._2_GBU_38, Weapons._2_Mk_82),
|
||||
(Weapons._2_GBU_38_, Weapons._2_Mk_82_),
|
||||
(Weapons._3_GBU_38, Weapons._3_Mk_82_),
|
||||
|
||||
# GBU-54 LJDAM
|
||||
(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._3_GBU_54_V_1_B, Weapons._3_GBU_38),
|
||||
|
||||
# CBU-52
|
||||
(Weapons.CBU_52B, None),
|
||||
|
||||
# CBU-87 CEM
|
||||
(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___3_x_CBU_87, Weapons.TER_9A___3_x_Mk_82),
|
||||
|
||||
# CBU-97
|
||||
(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___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-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),
|
||||
|
||||
# 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.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_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_),
|
||||
|
||||
(
|
||||
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_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
|
||||
# KAB-1500
|
||||
(Weapons.KAB_1500Kr, None),
|
||||
(Weapons.KAB_1500LG_Pr, Weapons.KAB_1500Kr),
|
||||
(Weapons.KAB_1500L, Weapons.KAB_1500LG_Pr),
|
||||
|
||||
# KAB-500
|
||||
(Weapons.KAB_500kr, Weapons.FAB_500_M62),
|
||||
(Weapons.KAB_500L, Weapons.KAB_500kr),
|
||||
(Weapons.KAB_500S, Weapons.KAB_500L),
|
||||
|
||||
# KH Series
|
||||
(Weapons.Kh_22N, None),
|
||||
(Weapons.Kh_23L, None),
|
||||
|
||||
(Weapons.Kh_25ML, None),
|
||||
(Weapons.Kh_25ML_, None),
|
||||
(Weapons.Kh_25ML__, None),
|
||||
|
||||
(Weapons.Kh_25MP, None),
|
||||
(Weapons.Kh_25MPU, Weapons.Kh_25MP),
|
||||
|
||||
(Weapons.Kh_25MR, None),
|
||||
(Weapons.Kh_25MR_, 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_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_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_6, None),
|
||||
|
||||
(Weapons.Kh_41, None),
|
||||
|
||||
(Weapons.Kh_58U, Weapons.Kh_31P),
|
||||
(Weapons.Kh_58U_, Weapons.Kh_31P_),
|
||||
|
||||
(Weapons.Kh_59M, Weapons.Kh_31A),
|
||||
|
||||
(Weapons.Kh_65, None),
|
||||
(Weapons.Kh_65_6, None),
|
||||
(Weapons.Kh_65_8, None),
|
||||
|
||||
(Weapons.Kh_66_Grom__21__APU_68, None),
|
||||
|
||||
# ECM
|
||||
(Weapons.L175V_Khibiny_ECM_pod, None),
|
||||
|
||||
# R-13
|
||||
(Weapons.R_13M, None),
|
||||
(Weapons.R_13M1, Weapons.R_13M),
|
||||
|
||||
# R-24
|
||||
(Weapons.R_24R, None),
|
||||
(Weapons.R_24T, None),
|
||||
|
||||
# R-27
|
||||
(Weapons.R_27T, Weapons.R_24T),
|
||||
(Weapons.R_27R, Weapons.R_24R),
|
||||
(Weapons.R_27ER, Weapons.R_27R),
|
||||
(Weapons.R_27ET, Weapons.R_27T),
|
||||
|
||||
# R-33
|
||||
(Weapons.R_33, None),
|
||||
|
||||
# R-3
|
||||
(Weapons.R_3S, Weapons.R_13M),
|
||||
(Weapons.R_3R, Weapons.R_3S),
|
||||
|
||||
# R-40
|
||||
(Weapons.R_40R, None),
|
||||
(Weapons.R_40T, None),
|
||||
|
||||
# R-55
|
||||
(Weapons.R_55, None),
|
||||
(Weapons.RS2US, None),
|
||||
|
||||
# R-60
|
||||
(Weapons.R_60, 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.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_x_2, Weapons.R_60M),
|
||||
(Weapons.R_60M_x_2_, Weapons.R_60M),
|
||||
|
||||
# R-73
|
||||
(Weapons.R_73, Weapons.R_60M),
|
||||
(Weapons.R_73_, None),
|
||||
|
||||
# R-77
|
||||
(Weapons.R_77, Weapons.R_27ER),
|
||||
(Weapons.R_77_, None),
|
||||
|
||||
# UK
|
||||
# ALARM
|
||||
(Weapons.ALARM, None),
|
||||
(Weapons.ALARM_2, None),
|
||||
|
||||
# France
|
||||
# BLG-66 Belouga
|
||||
(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),
|
||||
|
||||
# HOT-3
|
||||
(Weapons.HOT3, None),
|
||||
(Weapons.HOT3_, None),
|
||||
|
||||
# Magic 2
|
||||
(Weapons.Matra_Magic_II, None),
|
||||
(Weapons.R_550_Magic_2, None),
|
||||
|
||||
# Super 530D
|
||||
(Weapons.Matra_Super_530D, Weapons.Matra_Magic_II),
|
||||
(Weapons.Super_530D, None)
|
||||
|
||||
(Weapons.Super_530D, None),
|
||||
]
|
||||
|
||||
WEAPON_FALLBACK_MAP: Dict[Weapon, Optional[Weapon]] = defaultdict(
|
||||
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 = {
|
||||
@ -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_141B): 1987,
|
||||
|
||||
# AGM-114K Hellfire
|
||||
Weapon.from_pydcs(Weapons.AGM114x2_OH_58): 1993,
|
||||
Weapon.from_pydcs(Weapons.AGM_114K): 1993,
|
||||
Weapon.from_pydcs(Weapons.AGM_114K___4): 1993,
|
||||
|
||||
# AGM-119 Penguin
|
||||
Weapon.from_pydcs(Weapons.AGM_119B_Penguin): 1972,
|
||||
|
||||
# AGM-122 Sidearm
|
||||
Weapon.from_pydcs(Weapons.AGM_122): 1986,
|
||||
Weapon.from_pydcs(Weapons.AGM_122_Sidearm): 1986,
|
||||
Weapon.from_pydcs(Weapons.AGM_122_Sidearm_): 1986,
|
||||
|
||||
# AGM-154 JSOW
|
||||
Weapon.from_pydcs(Weapons.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.AGM_154B): 2005,
|
||||
|
||||
Weapon.from_pydcs(Weapons.AGM_154C): 2005,
|
||||
Weapon.from_pydcs(Weapons.AGM_154C_4): 2005,
|
||||
Weapon.from_pydcs(Weapons.BRU_55___2_x_AGM_154C): 2005,
|
||||
|
||||
# AGM-45 Shrike
|
||||
Weapon.from_pydcs(Weapons.AGM_45A): 1965,
|
||||
Weapon.from_pydcs(Weapons.AGM_45B): 1970,
|
||||
Weapon.from_pydcs(Weapons.AGM_45B_): 1970,
|
||||
|
||||
# AGM-62 Walleye
|
||||
Weapon.from_pydcs(Weapons.AGM_62): 1972,
|
||||
|
||||
# AGM-65 Maverick
|
||||
Weapon.from_pydcs(Weapons.AGM_65D): 1983,
|
||||
Weapon.from_pydcs(Weapons.AGM_65E): 1985,
|
||||
Weapon.from_pydcs(Weapons.AGM_65K): 2007,
|
||||
|
||||
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_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_65K): 2002,
|
||||
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_3): 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_3): 1985,
|
||||
|
||||
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_R): 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_3): 2007,
|
||||
|
||||
# AGM-84 Harpoon
|
||||
Weapon.from_pydcs(Weapons.AGM_84): 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_SLAM): 1990,
|
||||
Weapon.from_pydcs(Weapons.AGM_84H): 1998,
|
||||
|
||||
# AGM-86 ALCM
|
||||
Weapon.from_pydcs(Weapons.AGM_86C): 1986,
|
||||
Weapon.from_pydcs(Weapons.AGM_86C_20): 1986,
|
||||
Weapon.from_pydcs(Weapons.AGM_86C_8): 1986,
|
||||
Weapon.from_pydcs(Weapons.MER_6_AGM_86C): 1986,
|
||||
|
||||
# AGM-88 HARM
|
||||
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.
|
||||
|
||||
# AIM-120 AMRAAM
|
||||
Weapon.from_pydcs(Weapons.AIM_120B): 1994,
|
||||
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___AIM_120B): 1994,
|
||||
Weapon.from_pydcs(Weapons.LAU_115_2_LAU_127_AIM_120C): 1996,
|
||||
Weapon.from_pydcs(Weapons.LAU_115___AIM_120C): 1996,
|
||||
|
||||
# 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_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_Mk47): 1974,
|
||||
Weapon.from_pydcs(Weapons.AIM_54C_Mk47_): 1974,
|
||||
Weapon.from_pydcs(Weapons.AIM_54C_Mk47__): 1974,
|
||||
|
||||
# AIM-7 Sparrow
|
||||
Weapon.from_pydcs(Weapons.AIM_7E): 1963,
|
||||
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.LAU_115C_AIM_7E): 1963,
|
||||
Weapon.from_pydcs(Weapons.LAU_115C_AIM_7F): 1976,
|
||||
Weapon.from_pydcs(Weapons.LAU_115___AIM_7M): 1982,
|
||||
Weapon.from_pydcs(Weapons.LAU_115C_AIM_7MH): 1987,
|
||||
|
||||
# AIM-9 Sidewinder
|
||||
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_9P5_Sidewinder_IR_AAM): 1980,
|
||||
Weapon.from_pydcs(Weapons.AIM_9P_Sidewinder_IR_AAM): 1978,
|
||||
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_R): 1977,
|
||||
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_2_AIM_9L): 1977,
|
||||
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_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_9M): 1982,
|
||||
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_9M): 1982,
|
||||
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_9M): 1982,
|
||||
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_9M): 1982,
|
||||
|
||||
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_Sidewinder_IR_AAM): 1982,
|
||||
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_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_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_9P_Sidewinder_IR_AAM): 1978,
|
||||
|
||||
# ALQ ECM Pods
|
||||
Weapon.from_pydcs(Weapons.ALQ_131): 1970,
|
||||
Weapon.from_pydcs(Weapons.ALQ_184): 1989,
|
||||
Weapon.from_pydcs(Weapons.AN_ALQ_164_DECM_Pod): 1984,
|
||||
|
||||
# TGP Pods
|
||||
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_Target_Pod): 1985,
|
||||
Weapon.from_pydcs(Weapons.Pavetack_F_111): 1982,
|
||||
|
||||
# BLU-107
|
||||
Weapon.from_pydcs(Weapons.BLU_107): 1983,
|
||||
Weapon.from_pydcs(Weapons.MER_6_BLU_107): 1983,
|
||||
|
||||
# GBU-10 LGB
|
||||
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_2): 1976,
|
||||
|
||||
# GBU-12 LGB
|
||||
Weapon.from_pydcs(Weapons.AUF2_GBU_12_x_2): 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._3_GBU_12): 1976,
|
||||
|
||||
# GBU-15 LGB
|
||||
Weapon.from_pydcs(Weapons.GBU_15): 1975,
|
||||
|
||||
# GBU-16 LGB
|
||||
Weapon.from_pydcs(Weapons.BRU_33___2_x_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._3_GBU_16): 1976,
|
||||
Weapon.from_pydcs(Weapons._3_GBU_16_): 1976,
|
||||
|
||||
# GBU-24 LGB
|
||||
Weapon.from_pydcs(Weapons.GBU_24): 1986,
|
||||
Weapon.from_pydcs(Weapons.GBU_24_): 1986,
|
||||
Weapon.from_pydcs(Weapons.GBU_24__): 1986,
|
||||
|
||||
# GBU-27 LGB
|
||||
Weapon.from_pydcs(Weapons.GBU_27): 1991,
|
||||
Weapon.from_pydcs(Weapons.GBU_27_2): 1991,
|
||||
Weapon.from_pydcs(Weapons.GBU_27_4): 1991,
|
||||
|
||||
# GBU-28
|
||||
Weapon.from_pydcs(Weapons.GBU_28): 1991,
|
||||
|
||||
# GBU-31 JDAM
|
||||
Weapon.from_pydcs(Weapons.GBU_31V3B_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_3_B): 2001,
|
||||
Weapon.from_pydcs(Weapons.GBU_31_V_4_B): 2001,
|
||||
|
||||
# GBU-32 JDAM
|
||||
Weapon.from_pydcs(Weapons.GBU_32_V_2_B): 2002,
|
||||
|
||||
# GBU-38 JDAM
|
||||
Weapon.from_pydcs(Weapons.BRU_55___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._3_GBU_38): 2005,
|
||||
|
||||
# GBU-54 LJDAM
|
||||
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._3_GBU_54_V_1_B): 2008,
|
||||
|
||||
# CBU-52
|
||||
Weapon.from_pydcs(Weapons.CBU_52B): 1970,
|
||||
|
||||
# CBU-87 CEM
|
||||
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___3_x_CBU_87): 1986,
|
||||
|
||||
# CBU-97
|
||||
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___3_x_CBU_97): 1992,
|
||||
|
||||
# CBU-99
|
||||
Weapon.from_pydcs(Weapons.BRU_33___2_x_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.DIS_MK_20): 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.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_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.Mk_20): 1968,
|
||||
Weapon.from_pydcs(Weapons.Mk_20_): 1968,
|
||||
Weapon.from_pydcs(Weapons.Mk_20_18): 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,
|
||||
@ -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._3_Mk_20_Rockeye): 1968,
|
||||
Weapon.from_pydcs(Weapons._3_Mk_20_Rockeye_): 1968,
|
||||
|
||||
# CBU-103
|
||||
Weapon.from_pydcs(Weapons.BRU_57___2_x_CBU_103): 2000,
|
||||
Weapon.from_pydcs(Weapons.CBU_103): 2000,
|
||||
|
||||
# CBU-105
|
||||
Weapon.from_pydcs(Weapons.BRU_57___2_x_CBU_105): 2000,
|
||||
Weapon.from_pydcs(Weapons.CBU_105): 2000,
|
||||
|
||||
# APKWS
|
||||
Weapon.from_pydcs(Weapons.LAU_131_pod___7_x_2_75__Hydra___Laser_Guided_Rkts_M151___HE_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,
|
||||
|
||||
Weapon.from_pydcs(
|
||||
Weapons.LAU_131_pod___7_x_2_75__Hydra___Laser_Guided_Rkts_M151___HE_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
|
||||
|
||||
# KAB-1500
|
||||
Weapon.from_pydcs(Weapons.KAB_1500Kr): 1985,
|
||||
Weapon.from_pydcs(Weapons.KAB_1500L): 1995,
|
||||
Weapon.from_pydcs(Weapons.KAB_1500LG_Pr): 1990,
|
||||
|
||||
# KAB-500
|
||||
Weapon.from_pydcs(Weapons.KAB_500kr): 1980,
|
||||
Weapon.from_pydcs(Weapons.KAB_500L): 1995,
|
||||
Weapon.from_pydcs(Weapons.KAB_500S): 2000,
|
||||
|
||||
# Kh Series
|
||||
Weapon.from_pydcs(Weapons.Kh_22N): 1962,
|
||||
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_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_25MR): 1975,
|
||||
Weapon.from_pydcs(Weapons.Kh_25MR_): 1975,
|
||||
|
||||
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_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_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_6): 2003,
|
||||
|
||||
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_59M): 1990,
|
||||
|
||||
Weapon.from_pydcs(Weapons.Kh_65): 1992,
|
||||
Weapon.from_pydcs(Weapons.Kh_65_6): 1992,
|
||||
Weapon.from_pydcs(Weapons.Kh_65_8): 1992,
|
||||
|
||||
Weapon.from_pydcs(Weapons.Kh_66_Grom__21__APU_68): 1968,
|
||||
|
||||
# ECM
|
||||
Weapon.from_pydcs(Weapons.L175V_Khibiny_ECM_pod): 1982,
|
||||
|
||||
# R-13
|
||||
Weapon.from_pydcs(Weapons.R_13M): 1961,
|
||||
Weapon.from_pydcs(Weapons.R_13M1): 1965,
|
||||
|
||||
# R-24
|
||||
Weapon.from_pydcs(Weapons.R_24R): 1981,
|
||||
Weapon.from_pydcs(Weapons.R_24T): 1981,
|
||||
|
||||
# R-27
|
||||
Weapon.from_pydcs(Weapons.R_27ER): 1983,
|
||||
Weapon.from_pydcs(Weapons.R_27ET): 1986,
|
||||
Weapon.from_pydcs(Weapons.R_27R): 1983,
|
||||
Weapon.from_pydcs(Weapons.R_27T): 1983,
|
||||
|
||||
# R-33
|
||||
Weapon.from_pydcs(Weapons.R_33): 1981,
|
||||
|
||||
# R-3
|
||||
Weapon.from_pydcs(Weapons.R_3R): 1966,
|
||||
Weapon.from_pydcs(Weapons.R_3S): 1962,
|
||||
|
||||
# R-40
|
||||
Weapon.from_pydcs(Weapons.R_40R): 1976,
|
||||
Weapon.from_pydcs(Weapons.R_40T): 1976,
|
||||
|
||||
# R-55
|
||||
Weapon.from_pydcs(Weapons.R_55): 1957,
|
||||
Weapon.from_pydcs(Weapons.RS2US): 1957,
|
||||
|
||||
# R-60
|
||||
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.APU_60_1_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_x_2): 1982,
|
||||
Weapon.from_pydcs(Weapons.R_60M_x_2_): 1982,
|
||||
|
||||
# R-73
|
||||
Weapon.from_pydcs(Weapons.R_73): 1984,
|
||||
Weapon.from_pydcs(Weapons.R_73_): 1984,
|
||||
|
||||
# R-77
|
||||
Weapon.from_pydcs(Weapons.R_77): 2002,
|
||||
Weapon.from_pydcs(Weapons.R_77_): 2002,
|
||||
|
||||
# UK
|
||||
# ALARM
|
||||
Weapon.from_pydcs(Weapons.ALARM): 1990,
|
||||
Weapon.from_pydcs(Weapons.ALARM_2): 1990,
|
||||
|
||||
# France
|
||||
# BLG-66 Belouga
|
||||
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,
|
||||
|
||||
# HOT-3
|
||||
Weapon.from_pydcs(Weapons.HOT3): 1998,
|
||||
Weapon.from_pydcs(Weapons.HOT3_): 1998,
|
||||
|
||||
# Magic 2
|
||||
Weapon.from_pydcs(Weapons.Matra_Magic_II): 1986,
|
||||
Weapon.from_pydcs(Weapons.R_550_Magic_2): 1986,
|
||||
|
||||
# Super 530D
|
||||
Weapon.from_pydcs(Weapons.Matra_Super_530D): 1988,
|
||||
Weapon.from_pydcs(Weapons.Super_530D): 1988,
|
||||
|
||||
}
|
||||
|
||||
348
game/db.py
348
game/db.py
@ -25,6 +25,7 @@ from dcs.helicopters import (
|
||||
helicopter_map,
|
||||
)
|
||||
from dcs.mapping import Point
|
||||
|
||||
# mypy can't resolve these if they're wildcard imports for some reason.
|
||||
from dcs.planes import (
|
||||
AJS37,
|
||||
@ -112,7 +113,7 @@ from dcs.planes import (
|
||||
WingLoong_I,
|
||||
Yak_40,
|
||||
plane_map,
|
||||
I_16
|
||||
I_16,
|
||||
)
|
||||
from dcs.ships import (
|
||||
Armed_speedboat,
|
||||
@ -166,6 +167,7 @@ from dcs.vehicles import (
|
||||
|
||||
import pydcs_extensions.frenchpack.frenchpack as frenchpack
|
||||
import pydcs_extensions.highdigitsams.highdigitsams as highdigitsams
|
||||
|
||||
# PATCH pydcs data with MODS
|
||||
from game.factions.faction_loader import FactionLoader
|
||||
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_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[highdigitsams.SAM_SA_10B_S_300PS_5P85SE_LN.id] = highdigitsams.SAM_SA_10B_S_300PS_5P85SE_LN
|
||||
vehicle_map[highdigitsams.SAM_SA_10B_S_300PS_5P85SU_LN.id] = highdigitsams.SAM_SA_10B_S_300PS_5P85SU_LN
|
||||
vehicle_map[highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85CE.id] = highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85CE
|
||||
vehicle_map[highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85DE.id] = highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85DE
|
||||
vehicle_map[highdigitsams.SAM_SA_10B_S_300PS_30N6_TR.id] = highdigitsams.SAM_SA_10B_S_300PS_30N6_TR
|
||||
vehicle_map[highdigitsams.SAM_SA_10B_S_300PS_40B6M_TR.id] = highdigitsams.SAM_SA_10B_S_300PS_40B6M_TR
|
||||
vehicle_map[highdigitsams.SAM_SA_10B_S_300PS_40B6MD_SR.id] = highdigitsams.SAM_SA_10B_S_300PS_40B6MD_SR
|
||||
vehicle_map[highdigitsams.SAM_SA_10B_S_300PS_64H6E_SR.id] = highdigitsams.SAM_SA_10B_S_300PS_64H6E_SR
|
||||
vehicle_map[highdigitsams.SAM_SA_20_S_300PMU1_CP_54K6.id] = highdigitsams.SAM_SA_20_S_300PMU1_CP_54K6
|
||||
vehicle_map[highdigitsams.SAM_SA_20_S_300PMU1_TR_30N6E.id] = highdigitsams.SAM_SA_20_S_300PMU1_TR_30N6E
|
||||
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_SA_10B_S_300PS_54K6_CP.id
|
||||
] = highdigitsams.SAM_SA_10B_S_300PS_54K6_CP
|
||||
vehicle_map[
|
||||
highdigitsams.SAM_SA_10B_S_300PS_5P85SE_LN.id
|
||||
] = highdigitsams.SAM_SA_10B_S_300PS_5P85SE_LN
|
||||
vehicle_map[
|
||||
highdigitsams.SAM_SA_10B_S_300PS_5P85SU_LN.id
|
||||
] = highdigitsams.SAM_SA_10B_S_300PS_5P85SU_LN
|
||||
vehicle_map[
|
||||
highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85CE.id
|
||||
] = highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85CE
|
||||
vehicle_map[
|
||||
highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85DE.id
|
||||
] = highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85DE
|
||||
vehicle_map[
|
||||
highdigitsams.SAM_SA_10B_S_300PS_30N6_TR.id
|
||||
] = highdigitsams.SAM_SA_10B_S_300PS_30N6_TR
|
||||
vehicle_map[
|
||||
highdigitsams.SAM_SA_10B_S_300PS_40B6M_TR.id
|
||||
] = highdigitsams.SAM_SA_10B_S_300PS_40B6M_TR
|
||||
vehicle_map[
|
||||
highdigitsams.SAM_SA_10B_S_300PS_40B6MD_SR.id
|
||||
] = highdigitsams.SAM_SA_10B_S_300PS_40B6MD_SR
|
||||
vehicle_map[
|
||||
highdigitsams.SAM_SA_10B_S_300PS_64H6E_SR.id
|
||||
] = highdigitsams.SAM_SA_10B_S_300PS_64H6E_SR
|
||||
vehicle_map[
|
||||
highdigitsams.SAM_SA_20_S_300PMU1_CP_54K6.id
|
||||
] = highdigitsams.SAM_SA_20_S_300PMU1_CP_54K6
|
||||
vehicle_map[
|
||||
highdigitsams.SAM_SA_20_S_300PMU1_TR_30N6E.id
|
||||
] = highdigitsams.SAM_SA_20_S_300PMU1_TR_30N6E
|
||||
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_SA_3__V_601P__LN_5P73.id] = 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.SAM_SA_3__V_601P__LN_5P73.id
|
||||
] = 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._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
|
||||
@ -308,7 +386,6 @@ PRICES = {
|
||||
JF_17: 20,
|
||||
Su_30: 24,
|
||||
Su_57: 40,
|
||||
|
||||
SpitfireLFMkIX: 14,
|
||||
SpitfireLFMkIXCW: 14,
|
||||
I_16: 10,
|
||||
@ -317,7 +394,6 @@ PRICES = {
|
||||
FW_190A8: 14,
|
||||
A_20G: 22,
|
||||
Ju_88A4: 24,
|
||||
|
||||
F_5E_3: 8,
|
||||
MiG_15bis: 4,
|
||||
MiG_19P: 6,
|
||||
@ -328,7 +404,6 @@ PRICES = {
|
||||
C_101CC: 6,
|
||||
A_4E_C: 8,
|
||||
MB_339PAN: 6,
|
||||
|
||||
AV8BNA: 14,
|
||||
M_2000C: 16,
|
||||
Mirage_2000_5: 20,
|
||||
@ -342,7 +417,6 @@ PRICES = {
|
||||
F_22A: 40,
|
||||
Tornado_IDS: 20,
|
||||
Tornado_GR4: 20,
|
||||
|
||||
# bomber
|
||||
Su_17M4: 10,
|
||||
Su_25: 15,
|
||||
@ -352,12 +426,10 @@ PRICES = {
|
||||
Su_24M: 20,
|
||||
Su_24MR: 24,
|
||||
MiG_27K: 20,
|
||||
|
||||
A_10A: 16,
|
||||
A_10C: 22,
|
||||
A_10C_2: 24,
|
||||
S_3B: 10,
|
||||
|
||||
# heli
|
||||
Ka_50: 13,
|
||||
SA342M: 8,
|
||||
@ -373,7 +445,6 @@ PRICES = {
|
||||
AH_64D: 30,
|
||||
OH_58D: 6,
|
||||
SH_60B: 6,
|
||||
|
||||
# Bombers
|
||||
B_52H: 35,
|
||||
B_1B: 50,
|
||||
@ -382,7 +453,6 @@ PRICES = {
|
||||
Tu_22M3: 40,
|
||||
Tu_95MS: 35,
|
||||
F_111F: 21,
|
||||
|
||||
# special
|
||||
IL_76MD: 30,
|
||||
An_26B: 25,
|
||||
@ -393,14 +463,12 @@ PRICES = {
|
||||
KC_135: 25,
|
||||
KC130: 25,
|
||||
KC135MPRS: 25,
|
||||
|
||||
A_50: 50,
|
||||
KJ_2000: 50,
|
||||
E_3A: 50,
|
||||
E_2C: 50,
|
||||
C_130: 25,
|
||||
Hercules: 25,
|
||||
|
||||
# WW2
|
||||
P_51D_30_NA: 18,
|
||||
P_51D: 16,
|
||||
@ -408,17 +476,14 @@ PRICES = {
|
||||
P_47D_30bl1: 16,
|
||||
P_47D_40: 18,
|
||||
B_17G: 30,
|
||||
|
||||
# Drones
|
||||
MQ_9_Reaper: 12,
|
||||
RQ_1A_Predator: 6,
|
||||
WingLoong_I: 6,
|
||||
|
||||
# Modded
|
||||
Rafale_M: 26,
|
||||
Rafale_A_S: 26,
|
||||
Rafale_B: 26,
|
||||
|
||||
# armor
|
||||
Armor.APC_MTLB: 4,
|
||||
Armor.FDDM_Grad: 4,
|
||||
@ -437,7 +502,6 @@ PRICES = {
|
||||
Armor.IFV_BMP_3: 18,
|
||||
Armor.ZBD_04A: 12,
|
||||
Armor.ZTZ_96B: 30,
|
||||
|
||||
Armor.APC_Cobra: 4,
|
||||
Armor.APC_M113: 6,
|
||||
Armor.APC_M1043_HMMWV_Armament: 2,
|
||||
@ -457,10 +521,8 @@ PRICES = {
|
||||
Armor.IFV_Marder: 10,
|
||||
Armor.IFV_MCV_80: 10,
|
||||
Armor.IFV_LAV_25: 7,
|
||||
|
||||
Artillery.MLRS_M270: 55,
|
||||
Artillery.SPH_M109_Paladin: 25,
|
||||
|
||||
Artillery.SPH_2S9_Nona: 12,
|
||||
Artillery.SPH_2S1_Gvozdika: 18,
|
||||
Artillery.SPH_2S3_Akatsia: 24,
|
||||
@ -470,14 +532,11 @@ PRICES = {
|
||||
Artillery.MLRS_9A52_Smerch: 40,
|
||||
Artillery._2B11_mortar: 4,
|
||||
Artillery.SpGH_Dana: 26,
|
||||
|
||||
Unarmed.Transport_UAZ_469: 3,
|
||||
Unarmed.Transport_Ural_375: 3,
|
||||
Infantry.Infantry_M4: 1,
|
||||
Infantry.Soldier_AK: 1,
|
||||
|
||||
Unarmed.Transport_M818: 3,
|
||||
|
||||
# WW2
|
||||
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G: 24,
|
||||
Armor.MT_Pz_Kpfw_IV_Ausf_H: 16,
|
||||
@ -504,22 +563,18 @@ PRICES = {
|
||||
Armor.Daimler_Armoured_Car: 8,
|
||||
Armor.LT_Mk_VII_Tetrarch: 8,
|
||||
Armor.M4_Tractor: 2,
|
||||
|
||||
# ship
|
||||
CV_1143_5_Admiral_Kuznetsov: 100,
|
||||
CVN_74_John_C__Stennis: 100,
|
||||
LHA_1_Tarawa: 50,
|
||||
|
||||
Bulk_cargo_ship_Yakushev: 10,
|
||||
Armed_speedboat: 10,
|
||||
Dry_cargo_ship_Ivanov: 10,
|
||||
Tanker_Elnya_160: 10,
|
||||
|
||||
# Air Defence units
|
||||
AirDefence.SAM_SA_19_Tunguska_2S6: 30,
|
||||
AirDefence.SAM_SA_6_Kub_LN_2P25: 20,
|
||||
AirDefence.SAM_SA_3_S_125_LN_5P73: 6,
|
||||
|
||||
AirDefence.SAM_SA_11_Buk_LN_9A310M1: 30,
|
||||
AirDefence.SAM_SA_11_Buk_CC_9S470M1: 25,
|
||||
AirDefence.SAM_SA_11_Buk_SR_9S18M1: 28,
|
||||
@ -558,7 +613,6 @@ PRICES = {
|
||||
AirDefence.SAM_SA_18_Igla_S_comm: 8,
|
||||
AirDefence.EWR_1L13: 30,
|
||||
AirDefence.SAM_SA_6_Kub_STR_9S91: 22,
|
||||
|
||||
AirDefence.EWR_55G6: 30,
|
||||
AirDefence.CP_9S80M1_Sborka: 10,
|
||||
AirDefence.SAM_Hawk_TR_AN_MPQ_46: 14,
|
||||
@ -589,7 +643,6 @@ PRICES = {
|
||||
AirDefence.AAA_M1_37mm: 7,
|
||||
AirDefence.AAA_M45_Quadmount: 4,
|
||||
AirDefence.AA_gun_QF_3_7: 10,
|
||||
|
||||
# FRENCH PACK MOD
|
||||
frenchpack.AMX_10RCR: 10,
|
||||
frenchpack.AMX_10RCR_SEPAR: 12,
|
||||
@ -619,7 +672,6 @@ PRICES = {
|
||||
frenchpack.DIM__TOYOTA_GREEN: 2,
|
||||
frenchpack.DIM__TOYOTA_DESERT: 2,
|
||||
frenchpack.DIM__KAMIKAZE: 6,
|
||||
|
||||
# SA-10
|
||||
AirDefence.SAM_SA_10_S_300PS_CP_54K6: 18,
|
||||
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_LN_5P85C: 22,
|
||||
AirDefence.SAM_SA_10_S_300PS_LN_5P85D: 22,
|
||||
|
||||
# High digit sams mod
|
||||
highdigitsams.AAA_SON_9_Fire_Can: 8,
|
||||
highdigitsams.AAA_100mm_KS_19: 10,
|
||||
|
||||
highdigitsams.SAM_SA_10B_S_300PS_54K6_CP: 20,
|
||||
highdigitsams.SAM_SA_10B_S_300PS_5P85SE_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_40B6MD_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_9A82_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_9S19_SR: 34,
|
||||
highdigitsams.SAM_SA_12_S_300V_9S32_TR: 28,
|
||||
|
||||
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_truck: 32,
|
||||
@ -656,21 +704,17 @@ PRICES = {
|
||||
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_5P85DE: 28,
|
||||
|
||||
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_SR_64N6E2: 40,
|
||||
highdigitsams.SAM_SA_20B_S_300PMU2_LN_5P85SE2: 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_9S19M2_SR: 45,
|
||||
highdigitsams.SAM_SA_23_S_300VM_9S32ME_TR: 35,
|
||||
highdigitsams.SAM_SA_23_S_300VM_9A83ME_LN: 32,
|
||||
highdigitsams.SAM_SA_23_S_300VM_9A82ME_LN: 32,
|
||||
|
||||
highdigitsams.SAM_SA_17_Buk_M1_2_LN_9A310M1_2: 40,
|
||||
|
||||
}
|
||||
|
||||
"""
|
||||
@ -728,7 +772,7 @@ UNIT_BY_TASK = {
|
||||
SpitfireLFMkIX,
|
||||
A_4E_C,
|
||||
Rafale_M,
|
||||
SA342Mistral
|
||||
SA342Mistral,
|
||||
],
|
||||
CAS: [
|
||||
AH_1W,
|
||||
@ -779,18 +823,12 @@ UNIT_BY_TASK = {
|
||||
Tu_160,
|
||||
Tu_22M3,
|
||||
Tu_95MS,
|
||||
UH_1H,
|
||||
UH_1H,
|
||||
SH_60B,
|
||||
WingLoong_I,
|
||||
Hercules
|
||||
],
|
||||
Transport: [
|
||||
IL_76MD,
|
||||
An_26B,
|
||||
An_30M,
|
||||
Yak_40,
|
||||
C_130
|
||||
Hercules,
|
||||
],
|
||||
Transport: [IL_76MD, An_26B, An_30M, Yak_40, C_130],
|
||||
Refueling: [
|
||||
IL_78M,
|
||||
KC_135,
|
||||
@ -798,12 +836,7 @@ UNIT_BY_TASK = {
|
||||
S_3B_Tanker,
|
||||
KC135MPRS,
|
||||
],
|
||||
AWACS: [
|
||||
E_3A,
|
||||
E_2C,
|
||||
A_50,
|
||||
KJ_2000
|
||||
],
|
||||
AWACS: [E_3A, E_2C, A_50, KJ_2000],
|
||||
PinpointStrike: [
|
||||
Armor.APC_MTLB,
|
||||
Armor.APC_MTLB,
|
||||
@ -851,7 +884,6 @@ UNIT_BY_TASK = {
|
||||
Armor.MBT_T_80U,
|
||||
Armor.MBT_T_90,
|
||||
Armor.ZTZ_96B,
|
||||
|
||||
Armor.APC_Cobra,
|
||||
Armor.APC_Cobra,
|
||||
Armor.APC_Cobra,
|
||||
@ -895,7 +927,6 @@ UNIT_BY_TASK = {
|
||||
Armor.MBT_Leopard_2,
|
||||
Armor.MBT_Challenger_II,
|
||||
Armor.MBT_Merkava_Mk__4,
|
||||
|
||||
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G,
|
||||
Armor.MT_Pz_Kpfw_IV_Ausf_H,
|
||||
Armor.HT_Pz_Kpfw_VI_Tiger_I,
|
||||
@ -945,7 +976,6 @@ UNIT_BY_TASK = {
|
||||
Artillery.Sturmpanzer_IV_Brummbär,
|
||||
Armor.Daimler_Armoured_Car,
|
||||
Armor.LT_Mk_VII_Tetrarch,
|
||||
|
||||
Artillery.MLRS_M270,
|
||||
Artillery.SPH_M109_Paladin,
|
||||
Artillery.SPH_2S9_Nona,
|
||||
@ -959,7 +989,6 @@ UNIT_BY_TASK = {
|
||||
Artillery.SpGH_Dana,
|
||||
Artillery.M12_GMC,
|
||||
Artillery.Sturmpanzer_IV_Brummbär,
|
||||
|
||||
AirDefence.AAA_ZU_23_on_Ural_375,
|
||||
AirDefence.AAA_ZU_23_Insurgent_on_Ural_375,
|
||||
AirDefence.AAA_ZSU_57_2,
|
||||
@ -983,12 +1012,10 @@ UNIT_BY_TASK = {
|
||||
AirDefence.AAA_Bofors_40mm,
|
||||
AirDefence.AAA_M1_37mm,
|
||||
AirDefence.AA_gun_QF_3_7,
|
||||
|
||||
frenchpack.DIM__TOYOTA_BLUE,
|
||||
frenchpack.DIM__TOYOTA_DESERT,
|
||||
frenchpack.DIM__TOYOTA_GREEN,
|
||||
frenchpack.DIM__KAMIKAZE,
|
||||
|
||||
frenchpack.AMX_10RCR,
|
||||
frenchpack.AMX_10RCR_SEPAR,
|
||||
frenchpack.ERC_90,
|
||||
@ -1006,15 +1033,29 @@ UNIT_BY_TASK = {
|
||||
frenchpack.DIM__TOYOTA_GREEN,
|
||||
frenchpack.DIM__TOYOTA_DESERT,
|
||||
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: [],
|
||||
Carriage: [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, ]
|
||||
Carriage: [
|
||||
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 = [
|
||||
AirDefence.SAM_Linebacker_M6,
|
||||
|
||||
AirDefence.SAM_SA_9_Strela_1_9P31,
|
||||
AirDefence.SAM_SA_8_Osa_9A33,
|
||||
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_SR_AN_MPQ_50: 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
|
||||
# model, we can safely assume the other was deployed
|
||||
# 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_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
|
||||
"""
|
||||
TAKEOFF_BAN: List[Type[FlyingType]] = [
|
||||
]
|
||||
TAKEOFF_BAN: List[Type[FlyingType]] = []
|
||||
|
||||
"""
|
||||
Units that will be always spawned in the air if launched from the carrier
|
||||
@ -1117,7 +1156,7 @@ COMMON_OVERRIDE = {
|
||||
GroundAttack: "STRIKE",
|
||||
Escort: "CAP",
|
||||
RunwayAttack: "RUNWAY_ATTACK",
|
||||
FighterSweep: "CAP"
|
||||
FighterSweep: "CAP",
|
||||
}
|
||||
|
||||
"""
|
||||
@ -1134,19 +1173,18 @@ EXPANDED_TASK_PAYLOAD_OVERRIDE = {
|
||||
"BARCAP": ("CAP HEAVY", "CAP"),
|
||||
"CAS": ("CAS MAVERICK F", "CAS"),
|
||||
"INTERCEPTION": ("CAP HEAVY", "CAP"),
|
||||
"STRIKE": ("STRIKE",),
|
||||
"STRIKE": ("STRIKE",),
|
||||
"ANTISHIP": ("ANTISHIP",),
|
||||
"SEAD": ("SEAD",),
|
||||
"DEAD": ("SEAD",),
|
||||
"ESCORT": ("CAP HEAVY", "CAP"),
|
||||
"BAI": ( "BAI", "CAS MAVERICK F", "CAS"),
|
||||
"BAI": ("BAI", "CAS MAVERICK F", "CAS"),
|
||||
"SWEEP": ("CAP HEAVY", "CAP"),
|
||||
"OCA_RUNWAY": ("RUNWAY_ATTACK","RUNWAY_STRIKE","STRIKE"),
|
||||
"OCA_AIRCRAFT": ("OCA","CAS MAVERICK F", "CAS")
|
||||
"OCA_RUNWAY": ("RUNWAY_ATTACK", "RUNWAY_STRIKE", "STRIKE"),
|
||||
"OCA_AIRCRAFT": ("OCA", "CAS MAVERICK F", "CAS"),
|
||||
}
|
||||
|
||||
PLANE_PAYLOAD_OVERRIDES: Dict[Type[PlaneType], Dict[Type[Task], str]] = {
|
||||
|
||||
B_1B: COMMON_OVERRIDE,
|
||||
B_52H: COMMON_OVERRIDE,
|
||||
F_117A: COMMON_OVERRIDE,
|
||||
@ -1253,11 +1291,9 @@ PLANE_PAYLOAD_OVERRIDES: Dict[Type[PlaneType], Dict[Type[Task], str]] = {
|
||||
AH_64A: COMMON_OVERRIDE,
|
||||
SH_60B: COMMON_OVERRIDE,
|
||||
Hercules: COMMON_OVERRIDE,
|
||||
|
||||
Su_25TM: {
|
||||
SEAD: "Kh-31P*2_Kh-25ML*4_R-73*2_L-081_MPS410",
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
"""
|
||||
@ -1318,9 +1354,17 @@ TIME_PERIODS = {
|
||||
}
|
||||
|
||||
REWARDS = {
|
||||
"power": 4, "warehouse": 2, "ware": 2, "fuel": 2, "ammo": 2,
|
||||
"farp": 1, "fob": 1, "factory": 10, "comms": 10, "oil": 10,
|
||||
"derrick": 8
|
||||
"power": 4,
|
||||
"warehouse": 2,
|
||||
"ware": 2,
|
||||
"fuel": 2,
|
||||
"ammo": 2,
|
||||
"farp": 1,
|
||||
"fob": 1,
|
||||
"factory": 10,
|
||||
"comms": 10,
|
||||
"oil": 10,
|
||||
"derrick": 8,
|
||||
}
|
||||
|
||||
CARRIER_CAPABLE = [
|
||||
@ -1332,7 +1376,6 @@ CARRIER_CAPABLE = [
|
||||
A_4E_C,
|
||||
Rafale_M,
|
||||
S_3B,
|
||||
|
||||
UH_1H,
|
||||
Mi_8MT,
|
||||
Ka_50,
|
||||
@ -1340,7 +1383,6 @@ CARRIER_CAPABLE = [
|
||||
OH_58D,
|
||||
UH_60A,
|
||||
SH_60B,
|
||||
|
||||
SA342L,
|
||||
SA342M,
|
||||
SA342Minigun,
|
||||
@ -1349,7 +1391,6 @@ CARRIER_CAPABLE = [
|
||||
|
||||
LHA_CAPABLE = [
|
||||
AV8BNA,
|
||||
|
||||
UH_1H,
|
||||
Mi_8MT,
|
||||
Ka_50,
|
||||
@ -1357,11 +1398,10 @@ LHA_CAPABLE = [
|
||||
OH_58D,
|
||||
UH_60A,
|
||||
SH_60B,
|
||||
|
||||
SA342L,
|
||||
SA342M,
|
||||
SA342Minigun,
|
||||
SA342Mistral
|
||||
SA342Mistral,
|
||||
]
|
||||
|
||||
"""
|
||||
@ -1418,27 +1458,50 @@ def find_unittype(for_task: Task, country_name: str) -> List[Type[UnitType]]:
|
||||
MANPADS: List[VehicleType] = [
|
||||
AirDefence.SAM_SA_18_Igla_MANPADS,
|
||||
AirDefence.SAM_SA_18_Igla_S_MANPADS,
|
||||
AirDefence.Stinger_MANPADS
|
||||
AirDefence.Stinger_MANPADS,
|
||||
]
|
||||
|
||||
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.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,
|
||||
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.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_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_Mauser_98,
|
||||
Infantry.Infantry_Soldier_Rus,
|
||||
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_M1_Garand, Infantry.Infantry_M1_Garand, Infantry.Infantry_M1_Garand,
|
||||
Infantry.Infantry_Soldier_Insurgents, Infantry.Infantry_Soldier_Insurgents, Infantry.Infantry_Soldier_Insurgents
|
||||
Infantry.Infantry_Mauser_98,
|
||||
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,
|
||||
]
|
||||
|
||||
|
||||
@ -1461,6 +1524,7 @@ def unit_type_name(unit_type) -> str:
|
||||
def unit_type_name_2(unit_type) -> str:
|
||||
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:
|
||||
original_name = unit_type.name and unit_type.name or unit_type.id
|
||||
default_value = None
|
||||
@ -1489,6 +1553,7 @@ def unit_get_expanded_info(country_name: str, unit_type, request_type: str) -> s
|
||||
return default_value
|
||||
return faction_value
|
||||
|
||||
|
||||
def unit_type_from_name(name: str) -> Optional[Type[UnitType]]:
|
||||
if name in vehicle_map:
|
||||
return vehicle_map[name]
|
||||
@ -1522,9 +1587,13 @@ def task_name(task) -> str:
|
||||
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 = [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])
|
||||
|
||||
idx = int(len(suitable_unittypes) * factor)
|
||||
@ -1572,7 +1641,10 @@ def unitdict_restrict_count(unit_dict: UnitsDict, total_count: int) -> UnitsDict
|
||||
|
||||
|
||||
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:
|
||||
@ -1616,7 +1688,9 @@ def _validate_db():
|
||||
total_set = set()
|
||||
for t, unit_collection in UNIT_BY_TASK.items():
|
||||
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)
|
||||
|
||||
# check prices
|
||||
|
||||
@ -96,13 +96,14 @@ class StateData:
|
||||
# them when they've already dead. Dedup.
|
||||
killed_ground_units=list(set(data["killed_ground_units"])),
|
||||
destroyed_statics=data["destroyed_objects_positions"],
|
||||
base_capture_events=data["base_capture_events"]
|
||||
base_capture_events=data["base_capture_events"],
|
||||
)
|
||||
|
||||
|
||||
class Debriefing:
|
||||
def __init__(self, state_data: Dict[str, Any], game: Game,
|
||||
unit_map: UnitMap) -> None:
|
||||
def __init__(
|
||||
self, state_data: Dict[str, Any], game: Game, unit_map: UnitMap
|
||||
) -> None:
|
||||
self.state_data = StateData.from_json(state_data)
|
||||
self.unit_map = unit_map
|
||||
|
||||
@ -135,12 +136,9 @@ class Debriefing:
|
||||
yield from self.ground_losses.enemy_airfields
|
||||
|
||||
def casualty_count(self, control_point: ControlPoint) -> int:
|
||||
return len(
|
||||
[x for x in self.front_line_losses if x.origin == control_point]
|
||||
)
|
||||
return len([x for x in self.front_line_losses if x.origin == control_point])
|
||||
|
||||
def front_line_losses_by_type(
|
||||
self, player: bool) -> Dict[Type[UnitType], int]:
|
||||
def front_line_losses_by_type(self, player: bool) -> Dict[Type[UnitType], int]:
|
||||
losses_by_type: Dict[Type[UnitType], int] = defaultdict(int)
|
||||
if player:
|
||||
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
|
||||
# units. We should start tracking those and covert this to a
|
||||
# warning.
|
||||
logging.debug(f"Death of untracked ground unit {unit_name} will "
|
||||
"have no effect. This may be normal behavior.")
|
||||
logging.debug(
|
||||
f"Death of untracked ground unit {unit_name} will "
|
||||
"have no effect. This may be normal behavior."
|
||||
)
|
||||
|
||||
return losses
|
||||
|
||||
@ -234,15 +234,16 @@ class Debriefing:
|
||||
for idx, base in enumerate(i.split("||")[0] for i in reversed_captures):
|
||||
if base not in [x[1] for x in last_base_cap_indexes]:
|
||||
last_base_cap_indexes.append((idx, base))
|
||||
return [reversed_captures[idx[0]] for idx in last_base_cap_indexes]
|
||||
return [reversed_captures[idx[0]] for idx in last_base_cap_indexes]
|
||||
|
||||
|
||||
class PollDebriefingFileThread(threading.Thread):
|
||||
"""Thread class with a stop() method. The thread itself has to check
|
||||
regularly for the stopped() condition."""
|
||||
|
||||
def __init__(self, callback: Callable[[Debriefing], None],
|
||||
game: Game, unit_map: UnitMap) -> None:
|
||||
def __init__(
|
||||
self, callback: Callable[[Debriefing], None], game: Game, unit_map: UnitMap
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self._stop_event = threading.Event()
|
||||
self.callback = callback
|
||||
@ -261,7 +262,10 @@ class PollDebriefingFileThread(threading.Thread):
|
||||
else:
|
||||
last_modified = 0
|
||||
while not self.stopped():
|
||||
if os.path.isfile("state.json") and os.path.getmtime("state.json") > last_modified:
|
||||
if (
|
||||
os.path.isfile("state.json")
|
||||
and os.path.getmtime("state.json") > last_modified
|
||||
):
|
||||
with open("state.json", "r") as json_file:
|
||||
json_data = json.load(json_file)
|
||||
debriefing = Debriefing(json_data, self.game, self.unit_map)
|
||||
@ -270,8 +274,9 @@ class PollDebriefingFileThread(threading.Thread):
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
def wait_for_debriefing(callback: Callable[[Debriefing], None],
|
||||
game: Game, unit_map) -> PollDebriefingFileThread:
|
||||
def wait_for_debriefing(
|
||||
callback: Callable[[Debriefing], None], game: Game, unit_map
|
||||
) -> PollDebriefingFileThread:
|
||||
thread = PollDebriefingFileThread(callback, game, unit_map)
|
||||
thread.start()
|
||||
return thread
|
||||
|
||||
@ -37,7 +37,15 @@ class Event:
|
||||
to_cp = None # type: ControlPoint
|
||||
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.from_cp = from_cp
|
||||
self.to_cp = target_cp
|
||||
@ -57,12 +65,14 @@ class Event:
|
||||
Operation.prepare(self.game)
|
||||
unit_map = Operation.generate()
|
||||
Operation.current_mission.save(
|
||||
persistency.mission_path_for("liberation_nextturn.miz"))
|
||||
persistency.mission_path_for("liberation_nextturn.miz")
|
||||
)
|
||||
return unit_map
|
||||
|
||||
@staticmethod
|
||||
def _transfer_aircraft(ato: AirTaskingOrder, losses: AirLosses,
|
||||
for_player: bool) -> None:
|
||||
def _transfer_aircraft(
|
||||
ato: AirTaskingOrder, losses: AirLosses, for_player: bool
|
||||
) -> None:
|
||||
for package in ato.packages:
|
||||
for flight in package.flights:
|
||||
# No need to transfer to the same location.
|
||||
@ -77,13 +87,16 @@ class Event:
|
||||
if flight.arrival.captured != for_player:
|
||||
logging.info(
|
||||
f"Not transferring {flight} because {flight.arrival} "
|
||||
"was captured")
|
||||
"was captured"
|
||||
)
|
||||
continue
|
||||
|
||||
transfer_count = losses.surviving_flight_members(flight)
|
||||
if transfer_count < 0:
|
||||
logging.error(f"{flight} had {flight.count} aircraft but "
|
||||
f"{transfer_count} losses were recorded.")
|
||||
logging.error(
|
||||
f"{flight} had {flight.count} aircraft but "
|
||||
f"{transfer_count} losses were recorded."
|
||||
)
|
||||
continue
|
||||
|
||||
aircraft = flight.unit_type
|
||||
@ -91,7 +104,8 @@ class Event:
|
||||
if available < transfer_count:
|
||||
logging.error(
|
||||
f"Found killed {aircraft} from {flight.departure} but "
|
||||
f"that airbase has only {available} available.")
|
||||
f"that airbase has only {available} available."
|
||||
)
|
||||
continue
|
||||
|
||||
flight.departure.base.aircraft[aircraft] -= transfer_count
|
||||
@ -101,10 +115,12 @@ class Event:
|
||||
flight.arrival.base.aircraft[aircraft] += transfer_count
|
||||
|
||||
def complete_aircraft_transfers(self, debriefing: Debriefing) -> None:
|
||||
self._transfer_aircraft(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.blue_ato, debriefing.air_losses, for_player=True
|
||||
)
|
||||
self._transfer_aircraft(
|
||||
self.game.red_ato, debriefing.air_losses, for_player=False
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def commit_air_losses(debriefing: Debriefing) -> None:
|
||||
@ -115,7 +131,8 @@ class Event:
|
||||
if available <= 0:
|
||||
logging.error(
|
||||
f"Found killed {aircraft} from {cp} but that airbase has "
|
||||
"none available.")
|
||||
"none available."
|
||||
)
|
||||
continue
|
||||
|
||||
logging.info(f"{aircraft} destroyed from {cp}")
|
||||
@ -130,7 +147,8 @@ class Event:
|
||||
if available <= 0:
|
||||
logging.error(
|
||||
f"Found killed {unit_type} from {control_point} but that "
|
||||
"airbase has none available.")
|
||||
"airbase has none available."
|
||||
)
|
||||
continue
|
||||
|
||||
logging.info(f"{unit_type} destroyed from {control_point}")
|
||||
@ -149,11 +167,14 @@ class Event:
|
||||
def commit_building_losses(self, debriefing: Debriefing) -> None:
|
||||
for loss in debriefing.building_losses:
|
||||
loss.ground_object.kill()
|
||||
self.game.informations.append(Information(
|
||||
"Building destroyed",
|
||||
f"{loss.ground_object.dcs_identifier} has been destroyed at "
|
||||
f"location {loss.ground_object.obj_name}", self.game.turn
|
||||
))
|
||||
self.game.informations.append(
|
||||
Information(
|
||||
"Building destroyed",
|
||||
f"{loss.ground_object.dcs_identifier} has been destroyed at "
|
||||
f"location {loss.ground_object.obj_name}",
|
||||
self.game.turn,
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def commit_damaged_runways(debriefing: Debriefing) -> None:
|
||||
@ -171,9 +192,9 @@ class Event:
|
||||
|
||||
# ------------------------------
|
||||
# Captured bases
|
||||
#if self.game.player_country in db.BLUEFOR_FACTIONS:
|
||||
coalition = 2 # Value in DCS mission event for BLUE
|
||||
#else:
|
||||
# if self.game.player_country in db.BLUEFOR_FACTIONS:
|
||||
coalition = 2 # Value in DCS mission event for BLUE
|
||||
# else:
|
||||
# coalition = 1 # Value in DCS mission event for RED
|
||||
|
||||
for captured in debriefing.base_capture_events:
|
||||
@ -187,12 +208,22 @@ class Event:
|
||||
|
||||
if cp.captured and new_owner_coalition != coalition:
|
||||
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)
|
||||
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
|
||||
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)
|
||||
captured_cps.append(cp)
|
||||
else:
|
||||
@ -218,7 +249,12 @@ class Event:
|
||||
for cp in self.game.theater.player_points():
|
||||
enemy_cps = [e for e in cp.connected_points if not e.captured]
|
||||
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
|
||||
player_won = True
|
||||
@ -234,7 +270,11 @@ class Event:
|
||||
|
||||
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:
|
||||
player_won = False
|
||||
@ -259,11 +299,17 @@ class Event:
|
||||
delta = DEFEAT_INFLUENCE
|
||||
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
|
||||
player_won = True
|
||||
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
|
||||
delta = STRONG_DEFEAT_INFLUENCE
|
||||
else:
|
||||
@ -275,7 +321,10 @@ class Event:
|
||||
delta = STRONG_DEFEAT_INFLUENCE
|
||||
|
||||
# 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")
|
||||
delta = MINOR_DEFEAT_INFLUENCE
|
||||
|
||||
@ -283,28 +332,40 @@ class Event:
|
||||
print(cp.name + " won ! factor > " + str(delta))
|
||||
cp.base.affect_strength(delta)
|
||||
enemy_cp.base.affect_strength(-delta)
|
||||
info = Information("Frontline Report",
|
||||
"Our ground forces from " + cp.name + " are making progress toward " + enemy_cp.name,
|
||||
self.game.turn)
|
||||
info = Information(
|
||||
"Frontline Report",
|
||||
"Our ground forces from "
|
||||
+ cp.name
|
||||
+ " are making progress toward "
|
||||
+ enemy_cp.name,
|
||||
self.game.turn,
|
||||
)
|
||||
self.game.informations.append(info)
|
||||
else:
|
||||
print(cp.name + " lost ! factor > " + str(delta))
|
||||
enemy_cp.base.affect_strength(delta)
|
||||
cp.base.affect_strength(-delta)
|
||||
info = Information("Frontline Report",
|
||||
"Our ground forces from " + cp.name + " are losing ground against the enemy forces from " + enemy_cp.name,
|
||||
self.game.turn)
|
||||
info = Information(
|
||||
"Frontline Report",
|
||||
"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)
|
||||
|
||||
def redeploy_units(self, cp: ControlPoint) -> None:
|
||||
""""
|
||||
""" "
|
||||
Auto redeploy units to newly captured base
|
||||
"""
|
||||
|
||||
ally_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]
|
||||
ally_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,
|
||||
# then it is not necessary to redeploy frontline units there.
|
||||
@ -315,8 +376,7 @@ class Event:
|
||||
for ally_cp in ally_connected_cps:
|
||||
self.redeploy_between(cp, ally_cp)
|
||||
|
||||
def redeploy_between(self, destination: ControlPoint,
|
||||
source: ControlPoint) -> None:
|
||||
def redeploy_between(self, destination: ControlPoint, source: ControlPoint) -> None:
|
||||
total_units_redeployed = 0
|
||||
moved_units = {}
|
||||
|
||||
@ -333,8 +393,7 @@ class Event:
|
||||
|
||||
for frontline_unit, count in source.base.armor.items():
|
||||
moved_units[frontline_unit] = int(count * move_factor)
|
||||
total_units_redeployed = total_units_redeployed + int(
|
||||
count * move_factor)
|
||||
total_units_redeployed = total_units_redeployed + int(count * move_factor)
|
||||
|
||||
destination.base.commision_units(moved_units)
|
||||
source.base.commit_losses(moved_units)
|
||||
@ -362,7 +421,6 @@ class Event:
|
||||
|
||||
|
||||
class UnitsDeliveryEvent:
|
||||
|
||||
def __init__(self, control_point: ControlPoint) -> None:
|
||||
self.to_cp = control_point
|
||||
self.units: Dict[Type[UnitType], int] = {}
|
||||
@ -390,8 +448,7 @@ class UnitsDeliveryEvent:
|
||||
logging.error(f"Could not refund {unit_type.id}, price unknown")
|
||||
continue
|
||||
|
||||
logging.info(
|
||||
f"Refunding {count} {unit_type.id} at {self.to_cp.name}")
|
||||
logging.info(f"Refunding {count} {unit_type.id} at {self.to_cp.name}")
|
||||
game.adjust_budget(price * count, player=self.to_cp.captured)
|
||||
|
||||
def available_next_turn(self, unit_type: Type[UnitType]) -> int:
|
||||
@ -409,13 +466,13 @@ class UnitsDeliveryEvent:
|
||||
aircraft = unit_type.id
|
||||
name = self.to_cp.name
|
||||
if count >= 0:
|
||||
bought_units[unit_type] = count
|
||||
bought_units[unit_type] = count
|
||||
game.message(
|
||||
f"{coalition} reinforcements: {aircraft} x {count} at {name}")
|
||||
f"{coalition} reinforcements: {aircraft} x {count} at {name}"
|
||||
)
|
||||
else:
|
||||
sold_units[unit_type] = -count
|
||||
game.message(
|
||||
f"{coalition} sold: {aircraft} x {-count} at {name}")
|
||||
game.message(f"{coalition} sold: {aircraft} x {-count} at {name}")
|
||||
self.to_cp.base.commision_units(bought_units)
|
||||
self.to_cp.base.commit_losses(sold_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
|
||||
future unique Event handling
|
||||
"""
|
||||
|
||||
def __str__(self):
|
||||
return "Frontline attack"
|
||||
|
||||
@ -10,8 +10,18 @@ from dcs.planes import plane_map
|
||||
from dcs.unittype import FlyingType, ShipType, VehicleType, UnitType
|
||||
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.doctrine import Doctrine, MODERN_DOCTRINE, COLDWAR_DOCTRINE, WWII_DOCTRINE
|
||||
from game.data.building_data import (
|
||||
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
|
||||
|
||||
|
||||
@ -103,8 +113,7 @@ class Faction:
|
||||
building_set: List[str] = field(default_factory=list)
|
||||
|
||||
# List of default livery overrides
|
||||
liveries_overrides: Dict[Type[UnitType], List[str]] = field(
|
||||
default_factory=dict)
|
||||
liveries_overrides: Dict[Type[UnitType], List[str]] = field(default_factory=dict)
|
||||
|
||||
#: Set to True if the faction should force the "Unrestricted satnav" option
|
||||
#: for the mission. This option enables GPS for capable aircraft regardless
|
||||
@ -122,7 +131,11 @@ class Faction:
|
||||
|
||||
faction.country = json.get("country", "/")
|
||||
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", "")
|
||||
if not faction.name:
|
||||
@ -135,14 +148,10 @@ class Faction:
|
||||
faction.awacs = load_all_aircraft(json.get("awacs", []))
|
||||
faction.tankers = load_all_aircraft(json.get("tankers", []))
|
||||
|
||||
faction.frontline_units = load_all_vehicles(
|
||||
json.get("frontline_units", []))
|
||||
faction.artillery_units = load_all_vehicles(
|
||||
json.get("artillery_units", []))
|
||||
faction.infantry_units = load_all_vehicles(
|
||||
json.get("infantry_units", []))
|
||||
faction.logistics_units = load_all_vehicles(
|
||||
json.get("logistics_units", []))
|
||||
faction.frontline_units = load_all_vehicles(json.get("frontline_units", []))
|
||||
faction.artillery_units = load_all_vehicles(json.get("artillery_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", [])
|
||||
|
||||
@ -156,13 +165,10 @@ class Faction:
|
||||
faction.requirements = json.get("requirements", {})
|
||||
|
||||
faction.carrier_names = json.get("carrier_names", [])
|
||||
faction.helicopter_carrier_names = json.get(
|
||||
"helicopter_carrier_names", [])
|
||||
faction.helicopter_carrier_names = json.get("helicopter_carrier_names", [])
|
||||
faction.navy_generators = json.get("navy_generators", [])
|
||||
faction.aircraft_carrier = load_all_ships(
|
||||
json.get("aircraft_carrier", []))
|
||||
faction.helicopter_carrier = load_all_ships(
|
||||
json.get("helicopter_carrier", []))
|
||||
faction.aircraft_carrier = load_all_ships(json.get("aircraft_carrier", []))
|
||||
faction.helicopter_carrier = load_all_ships(json.get("helicopter_carrier", []))
|
||||
faction.destroyers = load_all_ships(json.get("destroyers", []))
|
||||
faction.cruisers = load_all_ships(json.get("cruisers", []))
|
||||
faction.has_jtac = json.get("has_jtac", False)
|
||||
@ -212,13 +218,18 @@ class Faction:
|
||||
|
||||
@property
|
||||
def units(self) -> List[Type[UnitType]]:
|
||||
return (self.infantry_units + self.aircrafts + self.awacs +
|
||||
self.artillery_units + self.frontline_units +
|
||||
self.tankers + self.logistics_units)
|
||||
return (
|
||||
self.infantry_units
|
||||
+ self.aircrafts
|
||||
+ self.awacs
|
||||
+ self.artillery_units
|
||||
+ self.frontline_units
|
||||
+ self.tankers
|
||||
+ self.logistics_units
|
||||
)
|
||||
|
||||
|
||||
def unit_loader(
|
||||
unit: str, class_repository: List[Any]) -> Optional[Type[UnitType]]:
|
||||
def unit_loader(unit: str, class_repository: List[Any]) -> Optional[Type[UnitType]]:
|
||||
"""
|
||||
Find unit by name
|
||||
:param unit: Unit name as string
|
||||
@ -242,9 +253,10 @@ def unit_loader(
|
||||
|
||||
|
||||
def load_aircraft(name: str) -> Optional[Type[FlyingType]]:
|
||||
return cast(Optional[FlyingType], unit_loader(
|
||||
name, [dcs.planes, dcs.helicopters, MODDED_AIRPLANES]
|
||||
))
|
||||
return cast(
|
||||
Optional[FlyingType],
|
||||
unit_loader(name, [dcs.planes, dcs.helicopters, MODDED_AIRPLANES]),
|
||||
)
|
||||
|
||||
|
||||
def load_all_aircraft(data) -> List[Type[FlyingType]]:
|
||||
@ -257,9 +269,12 @@ def load_all_aircraft(data) -> List[Type[FlyingType]]:
|
||||
|
||||
|
||||
def load_vehicle(name: str) -> Optional[Type[VehicleType]]:
|
||||
return cast(Optional[FlyingType], unit_loader(
|
||||
name, [Infantry, Unarmed, Armor, AirDefence, Artillery, MODDED_VEHICLES]
|
||||
))
|
||||
return cast(
|
||||
Optional[FlyingType],
|
||||
unit_loader(
|
||||
name, [Infantry, Unarmed, Armor, AirDefence, Artillery, MODDED_VEHICLES]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def load_all_vehicles(data) -> List[Type[VehicleType]]:
|
||||
|
||||
104
game/game.py
104
game/game.py
@ -78,10 +78,16 @@ class TurnState(Enum):
|
||||
|
||||
|
||||
class Game:
|
||||
def __init__(self, player_name: str, enemy_name: str,
|
||||
theater: ConflictTheater, start_date: datetime,
|
||||
settings: Settings, player_budget: float,
|
||||
enemy_budget: float) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
player_name: str,
|
||||
enemy_name: str,
|
||||
theater: ConflictTheater,
|
||||
start_date: datetime,
|
||||
settings: Settings,
|
||||
player_budget: float,
|
||||
enemy_budget: float,
|
||||
) -> None:
|
||||
self.settings = settings
|
||||
self.events: List[Event] = []
|
||||
self.theater = theater
|
||||
@ -112,9 +118,7 @@ class Game:
|
||||
self.blue_ato = AirTaskingOrder()
|
||||
self.red_ato = AirTaskingOrder()
|
||||
|
||||
self.aircraft_inventory = GlobalAircraftInventory(
|
||||
self.theater.controlpoints
|
||||
)
|
||||
self.aircraft_inventory = GlobalAircraftInventory(self.theater.controlpoints)
|
||||
|
||||
self.sanitize_sides()
|
||||
|
||||
@ -147,8 +151,9 @@ class Game:
|
||||
self.on_load()
|
||||
|
||||
def generate_conditions(self) -> Conditions:
|
||||
return Conditions.generate(self.theater, self.date,
|
||||
self.current_turn_time_of_day, self.settings)
|
||||
return Conditions.generate(
|
||||
self.theater, self.date, self.current_turn_time_of_day, self.settings
|
||||
)
|
||||
|
||||
def sanitize_sides(self):
|
||||
"""
|
||||
@ -184,13 +189,24 @@ class Game:
|
||||
return random.randint(1, 100) <= prob * mult
|
||||
|
||||
def _generate_player_event(self, event_class, player_cp, enemy_cp):
|
||||
self.events.append(event_class(self, player_cp, enemy_cp, enemy_cp.position, self.player_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):
|
||||
for front_line in self.theater.conflicts(True):
|
||||
self._generate_player_event(FrontlineAttackEvent,
|
||||
front_line.control_point_a,
|
||||
front_line.control_point_b)
|
||||
self._generate_player_event(
|
||||
FrontlineAttackEvent,
|
||||
front_line.control_point_a,
|
||||
front_line.control_point_b,
|
||||
)
|
||||
|
||||
def adjust_budget(self, amount: float, player: bool) -> None:
|
||||
if player:
|
||||
@ -208,7 +224,7 @@ class Game:
|
||||
self.enemy_budget += Income(self, player=False).total
|
||||
|
||||
def initiate_event(self, event: Event) -> UnitMap:
|
||||
#assert event in self.events
|
||||
# assert event in self.events
|
||||
logging.info("Generating {} (regular)".format(event))
|
||||
return event.generate()
|
||||
|
||||
@ -223,7 +239,11 @@ class Game:
|
||||
|
||||
def is_player_attack(self, 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:
|
||||
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:
|
||||
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
|
||||
|
||||
for control_point in self.theater.controlpoints:
|
||||
@ -281,7 +303,7 @@ class Game:
|
||||
|
||||
# Check for win or loss condition
|
||||
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)
|
||||
|
||||
# Plan flights & combat for next turn
|
||||
@ -305,8 +327,11 @@ class Game:
|
||||
|
||||
self.plan_procurement(blue_planner, red_planner)
|
||||
|
||||
def plan_procurement(self, blue_planner: CoalitionMissionPlanner,
|
||||
red_planner: CoalitionMissionPlanner) -> None:
|
||||
def plan_procurement(
|
||||
self,
|
||||
blue_planner: CoalitionMissionPlanner,
|
||||
red_planner: CoalitionMissionPlanner,
|
||||
) -> None:
|
||||
# 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
|
||||
# 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_front_line=self.settings.automate_front_line_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)
|
||||
|
||||
self.enemy_budget = ProcurementAi(
|
||||
@ -330,7 +355,7 @@ class Game:
|
||||
manage_runways=True,
|
||||
manage_front_line=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)
|
||||
|
||||
def message(self, text: str) -> None:
|
||||
@ -361,10 +386,12 @@ class Game:
|
||||
def compute_threat_zones(self) -> None:
|
||||
self.blue_threat_zone = ThreatZones.for_faction(self, player=True)
|
||||
self.red_threat_zone = ThreatZones.for_faction(self, player=False)
|
||||
self.blue_navmesh = NavMesh.from_threat_zones(self.red_threat_zone,
|
||||
self.theater)
|
||||
self.red_navmesh = NavMesh.from_threat_zones(self.blue_threat_zone,
|
||||
self.theater)
|
||||
self.blue_navmesh = NavMesh.from_threat_zones(
|
||||
self.red_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:
|
||||
if player:
|
||||
@ -386,9 +413,9 @@ class Game:
|
||||
|
||||
# By default, use the existing frontline conflict position
|
||||
for front_line in self.theater.conflicts():
|
||||
position = Conflict.frontline_position(front_line.control_point_a,
|
||||
front_line.control_point_b,
|
||||
self.theater)
|
||||
position = Conflict.frontline_position(
|
||||
front_line.control_point_a, front_line.control_point_b, self.theater
|
||||
)
|
||||
zones.append(position[0])
|
||||
zones.append(front_line.control_point_a.position)
|
||||
zones.append(front_line.control_point_b.position)
|
||||
@ -413,7 +440,10 @@ class Game:
|
||||
d = cp.position.distance_to_point(cp2.position)
|
||||
if d < min_distance:
|
||||
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(cp2.position)
|
||||
break
|
||||
@ -422,8 +452,7 @@ class Game:
|
||||
if cpoint is not None:
|
||||
zones.append(cpoint)
|
||||
|
||||
packages = itertools.chain(self.blue_ato.packages,
|
||||
self.red_ato.packages)
|
||||
packages = itertools.chain(self.blue_ato.packages, self.red_ato.packages)
|
||||
for package in packages:
|
||||
if package.primary_task is FlightType.BARCAP:
|
||||
# BARCAPs will be planned at most locations on smaller theaters,
|
||||
@ -460,7 +489,10 @@ class Game:
|
||||
return False
|
||||
else:
|
||||
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
|
||||
for p in self.__culling_points:
|
||||
if p.distance_to_point(pos) < 2500:
|
||||
@ -502,6 +534,10 @@ class Game:
|
||||
|
||||
def process_win_loss(self, turn_state: TurnState):
|
||||
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:
|
||||
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:
|
||||
if not tgo.is_dead:
|
||||
count += 1
|
||||
self.buildings.append(BuildingIncome(name, category, count,
|
||||
REWARDS[category]))
|
||||
self.buildings.append(
|
||||
BuildingIncome(name, category, count, REWARDS[category])
|
||||
)
|
||||
|
||||
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 = ((self.total_buildings + self.from_bases) *
|
||||
self.multiplier)
|
||||
self.total = (self.total_buildings + self.from_bases) * self.multiplier
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import datetime
|
||||
|
||||
class Information():
|
||||
|
||||
class Information:
|
||||
def __init__(self, title="", text="", turn=0):
|
||||
self.title = title
|
||||
self.text = text
|
||||
@ -9,9 +9,11 @@ class Information():
|
||||
self.timestamp = datetime.datetime.now()
|
||||
|
||||
def __str__(self):
|
||||
return '[{}][{}] {} {}'.format(
|
||||
self.timestamp.strftime("%Y-%m-%d %H:%M:%S") if self.timestamp is not None else '',
|
||||
return "[{}][{}] {} {}".format(
|
||||
self.timestamp.strftime("%Y-%m-%d %H:%M:%S")
|
||||
if self.timestamp is not None
|
||||
else "",
|
||||
self.turn,
|
||||
self.title,
|
||||
self.text
|
||||
)
|
||||
self.text,
|
||||
)
|
||||
|
||||
@ -79,6 +79,7 @@ class ControlPointAircraftInventory:
|
||||
|
||||
class GlobalAircraftInventory:
|
||||
"""Game-wide aircraft inventory."""
|
||||
|
||||
def __init__(self, control_points: Iterable[ControlPoint]) -> None:
|
||||
self.inventories: Dict[ControlPoint, ControlPointAircraftInventory] = {
|
||||
cp: ControlPointAircraftInventory(cp) for cp in control_points
|
||||
@ -100,8 +101,8 @@ class GlobalAircraftInventory:
|
||||
inventory.add_aircraft(aircraft, count)
|
||||
|
||||
def for_control_point(
|
||||
self,
|
||||
control_point: ControlPoint) -> ControlPointAircraftInventory:
|
||||
self, control_point: ControlPoint
|
||||
) -> ControlPointAircraftInventory:
|
||||
"""Returns the inventory specific to the given control point."""
|
||||
return self.inventories[control_point]
|
||||
|
||||
|
||||
@ -7,8 +7,7 @@ class DestroyedUnit:
|
||||
y: int
|
||||
name: str
|
||||
|
||||
def __init__(self, x , y, name):
|
||||
def __init__(self, x, y, name):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.name = name
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ class FrontlineData:
|
||||
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.from_cp = from_cp
|
||||
self.enemy_units_position = []
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
from typing import List
|
||||
|
||||
|
||||
class FactionTurnMetadata:
|
||||
"""
|
||||
Store metadata about a faction
|
||||
@ -20,8 +21,8 @@ class GameTurnMetadata:
|
||||
Store metadata about a game turn
|
||||
"""
|
||||
|
||||
allied_units:FactionTurnMetadata
|
||||
enemy_units:FactionTurnMetadata
|
||||
allied_units: FactionTurnMetadata
|
||||
enemy_units: FactionTurnMetadata
|
||||
|
||||
def __init__(self):
|
||||
self.allied_units = FactionTurnMetadata()
|
||||
@ -53,4 +54,3 @@ class GameStats:
|
||||
turn_data.enemy_units.vehicles_count += sum(cp.base.armor.values())
|
||||
|
||||
self.data_per_turn.append(turn_data)
|
||||
|
||||
|
||||
@ -114,9 +114,11 @@ class NavMesh:
|
||||
return self.travel_cost(a, b)
|
||||
|
||||
@staticmethod
|
||||
def reconstruct_path(came_from: Dict[NavPoint, Optional[NavPoint]],
|
||||
origin: NavPoint,
|
||||
destination: NavPoint) -> List[Point]:
|
||||
def reconstruct_path(
|
||||
came_from: Dict[NavPoint, Optional[NavPoint]],
|
||||
origin: NavPoint,
|
||||
destination: NavPoint,
|
||||
) -> List[Point]:
|
||||
current = destination
|
||||
path: List[Point] = []
|
||||
while current != origin:
|
||||
@ -141,16 +143,14 @@ class NavMesh:
|
||||
raise ValueError(f"Origin point {origin} is outside the navmesh")
|
||||
destination_poly = self.localize(destination)
|
||||
if destination_poly is None:
|
||||
raise ValueError(
|
||||
f"Origin point {destination} is outside the navmesh")
|
||||
raise ValueError(f"Origin point {destination} is outside the navmesh")
|
||||
|
||||
return self._shortest_path(
|
||||
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,
|
||||
destination: NavPoint) -> List[Point]:
|
||||
def _shortest_path(self, origin: NavPoint, destination: NavPoint) -> List[Point]:
|
||||
# Adapted from
|
||||
# https://www.redblobgames.com/pathfinding/a-star/implementation.py.
|
||||
frontier = NavFrontier()
|
||||
@ -167,9 +167,7 @@ class NavMesh:
|
||||
if current.poly == destination.poly:
|
||||
# Made it to the correct nav poly. Add the leg from the border
|
||||
# to the target.
|
||||
cost = best_known[current] + self.travel_cost(
|
||||
current, destination
|
||||
)
|
||||
cost = best_known[current] + self.travel_cost(current, destination)
|
||||
if cost < best_known[destination]:
|
||||
best_known[destination] = cost
|
||||
estimated = cost
|
||||
@ -185,14 +183,10 @@ class NavMesh:
|
||||
raise RuntimeError
|
||||
_, neighbor_point = nearest_points(current.point, boundary)
|
||||
neighbor_nav = NavPoint(neighbor_point, neighbor)
|
||||
cost = best_known[current] + self.travel_cost(
|
||||
current, neighbor_nav
|
||||
)
|
||||
cost = best_known[current] + self.travel_cost(current, neighbor_nav)
|
||||
if cost < best_known[neighbor_nav]:
|
||||
best_known[neighbor_nav] = cost
|
||||
estimated = cost + self.travel_heuristic(
|
||||
neighbor_nav, destination
|
||||
)
|
||||
estimated = cost + self.travel_heuristic(neighbor_nav, destination)
|
||||
frontier.push(neighbor_nav, estimated)
|
||||
came_from[neighbor_nav] = current
|
||||
|
||||
@ -209,13 +203,16 @@ class NavMesh:
|
||||
# threatened airbases at the map edges have room to retreat from the
|
||||
# threat without running off the navmesh.
|
||||
return box(*LineString(points).bounds).buffer(
|
||||
nautical_miles(100).meters, resolution=1)
|
||||
nautical_miles(100).meters, resolution=1
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create_navpolys(polys: List[Polygon],
|
||||
threat_zones: ThreatZones) -> List[NavMeshPoly]:
|
||||
return [NavMeshPoly(i, p, threat_zones.threatened(p))
|
||||
for i, p in enumerate(polys)]
|
||||
def create_navpolys(
|
||||
polys: List[Polygon], threat_zones: ThreatZones
|
||||
) -> List[NavMeshPoly]:
|
||||
return [
|
||||
NavMeshPoly(i, p, threat_zones.threatened(p)) for i, p in enumerate(polys)
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def associate_neighbors(polys: List[NavMeshPoly]) -> None:
|
||||
@ -234,8 +231,7 @@ class NavMesh:
|
||||
point = (int(x), int(y))
|
||||
neighbors = {}
|
||||
for potential_neighbor in points_map[point]:
|
||||
intersection = navpoly.poly.intersection(
|
||||
potential_neighbor.poly)
|
||||
intersection = navpoly.poly.intersection(potential_neighbor.poly)
|
||||
if not intersection.is_empty:
|
||||
potential_neighbor.neighbors[navpoly] = intersection
|
||||
neighbors[potential_neighbor] = intersection
|
||||
@ -243,8 +239,9 @@ class NavMesh:
|
||||
points_map[point].add(navpoly)
|
||||
|
||||
@classmethod
|
||||
def from_threat_zones(cls, threat_zones: ThreatZones,
|
||||
theater: ConflictTheater) -> NavMesh:
|
||||
def from_threat_zones(
|
||||
cls, threat_zones: ThreatZones, theater: ConflictTheater
|
||||
) -> NavMesh:
|
||||
# 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
|
||||
# error margin. This will create a simpler poly around the threat zone.
|
||||
|
||||
@ -41,6 +41,7 @@ if TYPE_CHECKING:
|
||||
|
||||
class Operation:
|
||||
"""Static class for managing the final Mission generation"""
|
||||
|
||||
current_mission = None # type: Mission
|
||||
airgen = None # type: AircraftConflictGenerator
|
||||
triggersgen = None # type: TriggersGenerator
|
||||
@ -84,7 +85,7 @@ class Operation:
|
||||
cls.game.enemy_name,
|
||||
cls.game.player_country,
|
||||
cls.game.enemy_country,
|
||||
frontline.position
|
||||
frontline.position,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@ -93,7 +94,7 @@ class Operation:
|
||||
player_cp, enemy_cp = cls.game.theater.closest_opposing_control_points()
|
||||
mid_point = player_cp.position.point_from_heading(
|
||||
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(
|
||||
cls.game.theater,
|
||||
@ -103,7 +104,7 @@ class Operation:
|
||||
cls.game.enemy_name,
|
||||
cls.game.player_country,
|
||||
cls.game.enemy_country,
|
||||
mid_point
|
||||
mid_point,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@ -118,9 +119,11 @@ class Operation:
|
||||
p_country = cls.game.player_country
|
||||
e_country = cls.game.enemy_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(
|
||||
country_dict[db.country_id_from_name(e_country)]())
|
||||
country_dict[db.country_id_from_name(e_country)]()
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def inject_lua_trigger(cls, contents: str, comment: str) -> None:
|
||||
@ -133,12 +136,11 @@ class Operation:
|
||||
cls.plugin_scripts.append(mnemonic)
|
||||
|
||||
@classmethod
|
||||
def inject_plugin_script(cls, plugin_mnemonic: str, script: str,
|
||||
script_mnemonic: str) -> None:
|
||||
def inject_plugin_script(
|
||||
cls, plugin_mnemonic: str, script: str, script_mnemonic: str
|
||||
) -> None:
|
||||
if script_mnemonic in cls.plugin_scripts:
|
||||
logging.debug(
|
||||
f"Skipping already loaded {script} for {plugin_mnemonic}"
|
||||
)
|
||||
logging.debug(f"Skipping already loaded {script} for {plugin_mnemonic}")
|
||||
else:
|
||||
cls.plugin_scripts.append(script_mnemonic)
|
||||
|
||||
@ -146,15 +148,12 @@ class Operation:
|
||||
|
||||
script_path = Path(plugin_path, script)
|
||||
if not script_path.exists():
|
||||
logging.error(
|
||||
f"Cannot find {script_path} for plugin {plugin_mnemonic}"
|
||||
)
|
||||
logging.error(f"Cannot find {script_path} for plugin {plugin_mnemonic}")
|
||||
return
|
||||
|
||||
trigger = TriggerStart(comment=f"Load {script_mnemonic}")
|
||||
filename = script_path.resolve()
|
||||
fileref = cls.current_mission.map_resource.add_resource_file(
|
||||
filename)
|
||||
fileref = cls.current_mission.map_resource.add_resource_file(filename)
|
||||
trigger.add_action(DoScriptFile(fileref))
|
||||
cls.current_mission.triggerrules.triggers.append(trigger)
|
||||
|
||||
@ -166,11 +165,10 @@ class Operation:
|
||||
jtacs: List[JtacInfo],
|
||||
airgen: AircraftConflictGenerator,
|
||||
):
|
||||
"""Generates subscribed MissionInfoGenerator objects (currently kneeboards and briefings)
|
||||
"""
|
||||
"""Generates subscribed MissionInfoGenerator objects (currently kneeboards and briefings)"""
|
||||
gens: List[MissionInfoGenerator] = [
|
||||
KneeboardGenerator(cls.current_mission, cls.game),
|
||||
BriefingGenerator(cls.current_mission, cls.game)
|
||||
BriefingGenerator(cls.current_mission, cls.game),
|
||||
]
|
||||
for gen in gens:
|
||||
for dynamic_runway in groundobjectgen.runways.values():
|
||||
@ -208,8 +206,9 @@ class Operation:
|
||||
cls.radio_registry.reserve(frequency)
|
||||
|
||||
@classmethod
|
||||
def assign_channels_to_flights(cls, flights: List[FlightData],
|
||||
air_support: AirSupport) -> None:
|
||||
def assign_channels_to_flights(
|
||||
cls, flights: List[FlightData], air_support: AirSupport
|
||||
) -> None:
|
||||
"""Assigns preset radio channels for client flights."""
|
||||
for flight in flights:
|
||||
if not flight.client_units:
|
||||
@ -217,8 +216,7 @@ class Operation:
|
||||
cls.assign_channels_to_flight(flight, air_support)
|
||||
|
||||
@staticmethod
|
||||
def assign_channels_to_flight(flight: FlightData,
|
||||
air_support: AirSupport) -> None:
|
||||
def assign_channels_to_flight(flight: FlightData, air_support: AirSupport) -> None:
|
||||
"""Assigns preset radio channels for a client flight."""
|
||||
airframe = flight.aircraft_type
|
||||
|
||||
@ -234,7 +232,9 @@ class Operation:
|
||||
)
|
||||
|
||||
@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
|
||||
used multiple times.
|
||||
@ -246,13 +246,14 @@ class Operation:
|
||||
unique_map_frequencies.add(beacon.frequency)
|
||||
if beacon.is_tacan:
|
||||
if beacon.channel is None:
|
||||
logging.error(
|
||||
f"TACAN beacon has no channel: {beacon.callsign}")
|
||||
logging.error(f"TACAN beacon has no channel: {beacon.callsign}")
|
||||
else:
|
||||
cls.tacan_registry.reserve(beacon.tacan_channel)
|
||||
|
||||
@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()
|
||||
for data in AIRFIELD_DATA.values():
|
||||
if data.theater == cls.game.theater.terrain.name and data.atc:
|
||||
@ -270,7 +271,7 @@ class Operation:
|
||||
cls.game,
|
||||
cls.radio_registry,
|
||||
cls.tacan_registry,
|
||||
cls.unit_map
|
||||
cls.unit_map,
|
||||
)
|
||||
cls.groundobjectgen.generate()
|
||||
|
||||
@ -284,10 +285,13 @@ class Operation:
|
||||
continue
|
||||
|
||||
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(
|
||||
country=cls.current_mission.country(
|
||||
cls.game.player_country),
|
||||
country=cls.current_mission.country(cls.game.player_country),
|
||||
name="",
|
||||
_type=utype,
|
||||
hidden=True,
|
||||
@ -302,13 +306,13 @@ class Operation:
|
||||
cls.create_unit_map()
|
||||
cls.create_radio_registries()
|
||||
# Set mission time and weather conditions.
|
||||
EnvironmentGenerator(cls.current_mission,
|
||||
cls.game.conditions).generate()
|
||||
EnvironmentGenerator(cls.current_mission, cls.game.conditions).generate()
|
||||
cls._generate_ground_units()
|
||||
cls._generate_destroyed_units()
|
||||
cls._generate_air_units()
|
||||
cls.assign_channels_to_flights(cls.airgen.flights,
|
||||
cls.airsupportgen.air_support)
|
||||
cls.assign_channels_to_flights(
|
||||
cls.airgen.flights, cls.airsupportgen.air_support
|
||||
)
|
||||
cls._generate_ground_conflicts()
|
||||
|
||||
# Triggers
|
||||
@ -317,14 +321,16 @@ class Operation:
|
||||
|
||||
# Setup combined arms parameters
|
||||
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
|
||||
else:
|
||||
cls.current_mission.groundControl.red_tactical_commander = cls.ca_slots
|
||||
|
||||
# Options
|
||||
forcedoptionsgen = ForcedOptionsGenerator(
|
||||
cls.current_mission, cls.game)
|
||||
forcedoptionsgen = ForcedOptionsGenerator(cls.current_mission, cls.game)
|
||||
forcedoptionsgen.generate()
|
||||
|
||||
# Generate Visuals Smoke Effects
|
||||
@ -341,13 +347,11 @@ class Operation:
|
||||
plugin.inject_scripts(cls)
|
||||
plugin.inject_configuration(cls)
|
||||
|
||||
cls.assign_channels_to_flights(cls.airgen.flights,
|
||||
cls.airsupportgen.air_support)
|
||||
cls.assign_channels_to_flights(
|
||||
cls.airgen.flights, cls.airsupportgen.air_support
|
||||
)
|
||||
cls.notify_info_generators(
|
||||
cls.groundobjectgen,
|
||||
cls.airsupportgen,
|
||||
cls.jtacs,
|
||||
cls.airgen
|
||||
cls.groundobjectgen, cls.airsupportgen, cls.jtacs, cls.airgen
|
||||
)
|
||||
cls.reset_naming_ids()
|
||||
return cls.unit_map
|
||||
@ -359,29 +363,38 @@ class Operation:
|
||||
# Air Support (Tanker & Awacs)
|
||||
assert cls.radio_registry and cls.tacan_registry
|
||||
cls.airsupportgen = AirSupportConflictGenerator(
|
||||
cls.current_mission, cls.air_conflict(), cls.game, cls.radio_registry,
|
||||
cls.tacan_registry)
|
||||
cls.current_mission,
|
||||
cls.air_conflict(),
|
||||
cls.game,
|
||||
cls.radio_registry,
|
||||
cls.tacan_registry,
|
||||
)
|
||||
cls.airsupportgen.generate()
|
||||
|
||||
# Generate Aircraft Activity on the map
|
||||
cls.airgen = AircraftConflictGenerator(
|
||||
cls.current_mission, cls.game.settings, cls.game,
|
||||
cls.radio_registry, cls.unit_map)
|
||||
cls.current_mission,
|
||||
cls.game.settings,
|
||||
cls.game,
|
||||
cls.radio_registry,
|
||||
cls.unit_map,
|
||||
)
|
||||
cls.airgen.clear_parking_slots()
|
||||
|
||||
cls.airgen.generate_flights(
|
||||
cls.current_mission.country(cls.game.player_country),
|
||||
cls.game.blue_ato,
|
||||
cls.groundobjectgen.runways
|
||||
cls.groundobjectgen.runways,
|
||||
)
|
||||
cls.airgen.generate_flights(
|
||||
cls.current_mission.country(cls.game.enemy_country),
|
||||
cls.game.red_ato,
|
||||
cls.groundobjectgen.runways
|
||||
cls.groundobjectgen.runways,
|
||||
)
|
||||
cls.airgen.spawn_unused_aircraft(
|
||||
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
|
||||
def _generate_ground_conflicts(cls) -> None:
|
||||
@ -396,17 +409,19 @@ class Operation:
|
||||
cls.current_mission.country(cls.game.enemy_country),
|
||||
player_cp,
|
||||
enemy_cp,
|
||||
cls.game.theater
|
||||
cls.game.theater,
|
||||
)
|
||||
# Generate frontline ops
|
||||
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]
|
||||
ground_conflict_gen = GroundConflictGenerator(
|
||||
cls.current_mission,
|
||||
conflict, cls.game,
|
||||
player_gp, enemy_gp,
|
||||
conflict,
|
||||
cls.game,
|
||||
player_gp,
|
||||
enemy_gp,
|
||||
player_cp.stances[enemy_cp.id],
|
||||
cls.unit_map
|
||||
cls.unit_map,
|
||||
)
|
||||
ground_conflict_gen.generate()
|
||||
cls.jtacs.extend(ground_conflict_gen.jtacs)
|
||||
@ -416,9 +431,12 @@ class Operation:
|
||||
namegen.reset_numbers()
|
||||
|
||||
@classmethod
|
||||
def generate_lua(cls, airgen: AircraftConflictGenerator,
|
||||
airsupportgen: AirSupportConflictGenerator,
|
||||
jtacs: List[JtacInfo]) -> None:
|
||||
def generate_lua(
|
||||
cls,
|
||||
airgen: AircraftConflictGenerator,
|
||||
airsupportgen: AirSupportConflictGenerator,
|
||||
jtacs: List[JtacInfo],
|
||||
) -> None:
|
||||
# TODO: Refactor this
|
||||
luaData = {
|
||||
"AircraftCarriers": {},
|
||||
@ -434,7 +452,7 @@ class Operation:
|
||||
"callsign": tanker.callsign,
|
||||
"variant": tanker.variant,
|
||||
"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:
|
||||
@ -442,7 +460,7 @@ class Operation:
|
||||
luaData["AWACs"][awacs.callsign] = {
|
||||
"dcsGroupName": awacs.dcsGroupName,
|
||||
"callsign": awacs.callsign,
|
||||
"radio": awacs.freq.mhz
|
||||
"radio": awacs.freq.mhz,
|
||||
}
|
||||
|
||||
for jtac in jtacs:
|
||||
@ -451,14 +469,16 @@ class Operation:
|
||||
"callsign": jtac.callsign,
|
||||
"zone": jtac.region,
|
||||
"dcsUnit": jtac.unit_name,
|
||||
"laserCode": jtac.code
|
||||
"laserCode": jtac.code,
|
||||
}
|
||||
|
||||
for flight in airgen.flights:
|
||||
if flight.friendly and flight.flight_type in [FlightType.ANTISHIP,
|
||||
FlightType.DEAD,
|
||||
FlightType.SEAD,
|
||||
FlightType.STRIKE]:
|
||||
if flight.friendly and flight.flight_type in [
|
||||
FlightType.ANTISHIP,
|
||||
FlightType.DEAD,
|
||||
FlightType.SEAD,
|
||||
FlightType.STRIKE,
|
||||
]:
|
||||
flightType = str(flight.flight_type)
|
||||
flightTarget = flight.package.target
|
||||
if flightTarget:
|
||||
@ -466,23 +486,27 @@ class Operation:
|
||||
flightTargetType = None
|
||||
if isinstance(flightTarget, TheaterGroundObject):
|
||||
flightTargetName = flightTarget.obj_name
|
||||
flightTargetType = flightType + \
|
||||
f" TGT ({flightTarget.category})"
|
||||
elif hasattr(flightTarget, 'name'):
|
||||
flightTargetType = (
|
||||
flightType + f" TGT ({flightTarget.category})"
|
||||
)
|
||||
elif hasattr(flightTarget, "name"):
|
||||
flightTargetName = flightTarget.name
|
||||
flightTargetType = flightType + " TGT (Airbase)"
|
||||
luaData["TargetPoints"][flightTargetName] = {
|
||||
"name": flightTargetName,
|
||||
"type": flightTargetType,
|
||||
"position": {"x": flightTarget.position.x,
|
||||
"y": flightTarget.position.y}
|
||||
"position": {
|
||||
"x": flightTarget.position.x,
|
||||
"y": flightTarget.position.y,
|
||||
},
|
||||
}
|
||||
|
||||
# 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
|
||||
# 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(".") + "]]"
|
||||
lua = """
|
||||
lua = (
|
||||
"""
|
||||
-- setting configuration table
|
||||
env.info("DCSLiberation|: setting configuration table")
|
||||
|
||||
@ -490,9 +514,12 @@ class Operation:
|
||||
dcsLiberation = {}
|
||||
|
||||
-- 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
|
||||
lua += """
|
||||
|
||||
|
||||
@ -67,4 +67,3 @@ def autosave(game) -> bool:
|
||||
except Exception:
|
||||
logging.exception("Could not save game")
|
||||
return False
|
||||
|
||||
|
||||
@ -14,9 +14,9 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class LuaPluginWorkOrder:
|
||||
|
||||
def __init__(self, parent_mnemonic: str, filename: str, mnemonic: str,
|
||||
disable: bool) -> None:
|
||||
def __init__(
|
||||
self, parent_mnemonic: str, filename: str, mnemonic: str, disable: bool
|
||||
) -> None:
|
||||
self.parent_mnemonic = parent_mnemonic
|
||||
self.filename = filename
|
||||
self.mnemonic = mnemonic
|
||||
@ -26,8 +26,9 @@ class LuaPluginWorkOrder:
|
||||
if self.disable:
|
||||
operation.bypass_plugin_script(self.mnemonic)
|
||||
else:
|
||||
operation.inject_plugin_script(self.parent_mnemonic, self.filename,
|
||||
self.mnemonic)
|
||||
operation.inject_plugin_script(
|
||||
self.parent_mnemonic, self.filename, self.mnemonic
|
||||
)
|
||||
|
||||
|
||||
class PluginSettings:
|
||||
@ -45,8 +46,7 @@ class PluginSettings:
|
||||
# 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
|
||||
# options added to those plugins, initialize the new settings.
|
||||
self.settings.initialize_plugin_option(self.identifier,
|
||||
self.enabled_by_default)
|
||||
self.settings.initialize_plugin_option(self.identifier, self.enabled_by_default)
|
||||
|
||||
@property
|
||||
def enabled(self) -> bool:
|
||||
@ -57,8 +57,7 @@ class PluginSettings:
|
||||
|
||||
|
||||
class LuaPluginOption(PluginSettings):
|
||||
def __init__(self, identifier: str, name: str,
|
||||
enabled_by_default: bool) -> None:
|
||||
def __init__(self, identifier: str, name: str, enabled_by_default: bool) -> None:
|
||||
super().__init__(identifier, enabled_by_default)
|
||||
self.name = name
|
||||
|
||||
@ -80,24 +79,34 @@ class LuaPluginDefinition:
|
||||
options = []
|
||||
for option in data.get("specificOptions"):
|
||||
option_id = option["mnemonic"]
|
||||
options.append(LuaPluginOption(
|
||||
identifier=f"{name}.{option_id}",
|
||||
name=option.get("nameInUI", name),
|
||||
enabled_by_default=option.get("defaultValue")
|
||||
))
|
||||
options.append(
|
||||
LuaPluginOption(
|
||||
identifier=f"{name}.{option_id}",
|
||||
name=option.get("nameInUI", name),
|
||||
enabled_by_default=option.get("defaultValue"),
|
||||
)
|
||||
)
|
||||
|
||||
work_orders = []
|
||||
for work_order in data.get("scriptsWorkOrders"):
|
||||
work_orders.append(LuaPluginWorkOrder(
|
||||
name, work_order.get("file"), work_order["mnemonic"],
|
||||
work_order.get("disable", False)
|
||||
))
|
||||
work_orders.append(
|
||||
LuaPluginWorkOrder(
|
||||
name,
|
||||
work_order.get("file"),
|
||||
work_order["mnemonic"],
|
||||
work_order.get("disable", False),
|
||||
)
|
||||
)
|
||||
config_work_orders = []
|
||||
for work_order in data.get("configurationWorkOrders"):
|
||||
config_work_orders.append(LuaPluginWorkOrder(
|
||||
name, work_order.get("file"), work_order["mnemonic"],
|
||||
work_order.get("disable", False)
|
||||
))
|
||||
config_work_orders.append(
|
||||
LuaPluginWorkOrder(
|
||||
name,
|
||||
work_order.get("file"),
|
||||
work_order["mnemonic"],
|
||||
work_order.get("disable", False),
|
||||
)
|
||||
)
|
||||
|
||||
return cls(
|
||||
identifier=name,
|
||||
@ -106,16 +115,14 @@ class LuaPluginDefinition:
|
||||
enabled_by_default=data.get("defaultValue", False),
|
||||
options=options,
|
||||
work_orders=work_orders,
|
||||
config_work_orders=config_work_orders
|
||||
config_work_orders=config_work_orders,
|
||||
)
|
||||
|
||||
|
||||
class LuaPlugin(PluginSettings):
|
||||
|
||||
def __init__(self, definition: LuaPluginDefinition) -> None:
|
||||
self.definition = definition
|
||||
super().__init__(self.definition.identifier,
|
||||
self.definition.enabled_by_default)
|
||||
super().__init__(self.definition.identifier, self.definition.enabled_by_default)
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
@ -155,12 +162,12 @@ class LuaPlugin(PluginSettings):
|
||||
for option in self.options:
|
||||
enabled = str(option.enabled).lower()
|
||||
name = option.identifier
|
||||
option_decls.append(
|
||||
f" dcsLiberation.plugins.{name} = {enabled}")
|
||||
option_decls.append(f" dcsLiberation.plugins.{name} = {enabled}")
|
||||
|
||||
joined_options = "\n".join(option_decls)
|
||||
|
||||
lua = textwrap.dedent(f"""\
|
||||
lua = textwrap.dedent(
|
||||
f"""\
|
||||
-- {self.identifier} plugin configuration.
|
||||
|
||||
if dcsLiberation then
|
||||
@ -171,10 +178,10 @@ class LuaPlugin(PluginSettings):
|
||||
{joined_options}
|
||||
end
|
||||
|
||||
""")
|
||||
"""
|
||||
)
|
||||
|
||||
operation.inject_lua_trigger(
|
||||
lua, f"{self.identifier} plugin configuration")
|
||||
operation.inject_lua_trigger(lua, f"{self.identifier} plugin configuration")
|
||||
|
||||
for work_order in self.definition.config_work_orders:
|
||||
work_order.work(operation)
|
||||
|
||||
@ -27,7 +27,8 @@ class LuaPluginManager:
|
||||
if not plugin_path.exists():
|
||||
raise RuntimeError(
|
||||
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}")
|
||||
plugin = LuaPlugin.from_json(name, plugin_path)
|
||||
if plugin is not None:
|
||||
|
||||
@ -35,9 +35,16 @@ class AircraftProcurementRequest:
|
||||
|
||||
|
||||
class ProcurementAi:
|
||||
def __init__(self, game: Game, for_player: bool, faction: Faction,
|
||||
manage_runways: bool, manage_front_line: bool,
|
||||
manage_aircraft: bool, front_line_budget_share: float) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
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:
|
||||
raise ValueError
|
||||
|
||||
@ -51,8 +58,8 @@ class ProcurementAi:
|
||||
self.threat_zones = self.game.threat_zone_for(not self.is_player)
|
||||
|
||||
def spend_budget(
|
||||
self, budget: float,
|
||||
aircraft_requests: List[AircraftProcurementRequest]) -> float:
|
||||
self, budget: float, aircraft_requests: List[AircraftProcurementRequest]
|
||||
) -> float:
|
||||
if self.manage_runways:
|
||||
budget = self.repair_runways(budget)
|
||||
if self.manage_front_line:
|
||||
@ -100,25 +107,30 @@ class ProcurementAi:
|
||||
budget -= db.RUNWAY_REPAIR_COST
|
||||
if self.is_player:
|
||||
self.game.message(
|
||||
"OPFOR has begun repairing the runway at "
|
||||
f"{control_point}"
|
||||
"OPFOR has begun repairing the runway at " f"{control_point}"
|
||||
)
|
||||
else:
|
||||
self.game.message(
|
||||
"We have begun repairing the runway at "
|
||||
f"{control_point}"
|
||||
"We have begun repairing the runway at " f"{control_point}"
|
||||
)
|
||||
return budget
|
||||
|
||||
def random_affordable_ground_unit(
|
||||
self, budget: float,
|
||||
cp: ControlPoint) -> Optional[Type[VehicleType]]:
|
||||
affordable_units = [u for u in self.faction.frontline_units + self.faction.artillery_units if
|
||||
db.PRICES[u] <= budget]
|
||||
self, budget: float, cp: ControlPoint
|
||||
) -> Optional[Type[VehicleType]]:
|
||||
affordable_units = [
|
||||
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_non_aa = cp.base.total_armor + cp.pending_deliveries_count - total_number_aa
|
||||
max_aa = math.ceil(total_non_aa/8)
|
||||
total_number_aa = (
|
||||
cp.base.total_frontline_aa + cp.pending_frontline_aa_deliveries_count
|
||||
)
|
||||
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
|
||||
if not total_number_aa < max_aa:
|
||||
@ -150,8 +162,12 @@ class ProcurementAi:
|
||||
return budget
|
||||
|
||||
def _affordable_aircraft_of_types(
|
||||
self, types: List[Type[FlyingType]], airbase: ControlPoint,
|
||||
number: int, max_price: float) -> Optional[Type[FlyingType]]:
|
||||
self,
|
||||
types: List[Type[FlyingType]],
|
||||
airbase: ControlPoint,
|
||||
number: int,
|
||||
max_price: float,
|
||||
) -> Optional[Type[FlyingType]]:
|
||||
best_choice: Optional[Type[FlyingType]] = None
|
||||
for unit in [u for u in self.faction.aircrafts if u in types]:
|
||||
if db.PRICES[unit] * number > max_price:
|
||||
@ -168,15 +184,15 @@ class ProcurementAi:
|
||||
return best_choice
|
||||
|
||||
def affordable_aircraft_for(
|
||||
self, request: AircraftProcurementRequest,
|
||||
airbase: ControlPoint, budget: float) -> Optional[Type[FlyingType]]:
|
||||
self, request: AircraftProcurementRequest, airbase: ControlPoint, budget: float
|
||||
) -> Optional[Type[FlyingType]]:
|
||||
return self._affordable_aircraft_of_types(
|
||||
aircraft_for_task(request.task_capability),
|
||||
airbase, request.number, budget)
|
||||
aircraft_for_task(request.task_capability), airbase, request.number, budget
|
||||
)
|
||||
|
||||
def purchase_aircraft(
|
||||
self, budget: float,
|
||||
aircraft_requests: List[AircraftProcurementRequest]) -> float:
|
||||
self, budget: float, aircraft_requests: List[AircraftProcurementRequest]
|
||||
) -> float:
|
||||
for request in aircraft_requests:
|
||||
for airbase in self.best_airbases_for(request):
|
||||
unit = self.affordable_aircraft_for(request, airbase, budget)
|
||||
@ -201,11 +217,9 @@ class ProcurementAi:
|
||||
return self.game.theater.enemy_points()
|
||||
|
||||
def best_airbases_for(
|
||||
self,
|
||||
request: AircraftProcurementRequest) -> Iterator[ControlPoint]:
|
||||
distance_cache = ObjectiveDistanceCache.get_closest_airfields(
|
||||
request.near
|
||||
)
|
||||
self, request: AircraftProcurementRequest
|
||||
) -> Iterator[ControlPoint]:
|
||||
distance_cache = ObjectiveDistanceCache.get_closest_airfields(request.near)
|
||||
threatened = []
|
||||
for cp in distance_cache.airfields_within(request.range):
|
||||
if not cp.is_friendly(self.is_player):
|
||||
|
||||
@ -58,8 +58,7 @@ class Settings:
|
||||
def plugin_settings_key(identifier: str) -> str:
|
||||
return f"plugins.{identifier}"
|
||||
|
||||
def initialize_plugin_option(self, identifier: str,
|
||||
default_value: bool) -> None:
|
||||
def initialize_plugin_option(self, identifier: str, default_value: bool) -> None:
|
||||
try:
|
||||
self.plugin_option(identifier)
|
||||
except KeyError:
|
||||
|
||||
@ -22,7 +22,6 @@ BASE_MIN_STRENGTH = 0
|
||||
|
||||
|
||||
class Base:
|
||||
|
||||
def __init__(self):
|
||||
self.aircraft: Dict[Type[FlyingType], int] = {}
|
||||
self.armor: Dict[Type[VehicleType], int] = {}
|
||||
@ -57,23 +56,43 @@ class Base:
|
||||
return sum(self.aa.values())
|
||||
|
||||
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:
|
||||
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
|
||||
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],
|
||||
for_type: Task, count: int) -> Dict[UnitType, int]:
|
||||
def _find_best_unit(
|
||||
self, available_units: Dict[UnitType, int], for_type: Task, count: int
|
||||
) -> Dict[UnitType, int]:
|
||||
if count <= 0:
|
||||
logging.warning("{}: no units for {}".format(self, for_type))
|
||||
return {}
|
||||
|
||||
sorted_units = [key for key in available_units if
|
||||
key in db.UNIT_BY_TASK[for_type]]
|
||||
sorted_units = [
|
||||
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)
|
||||
|
||||
result: Dict[UnitType, int] = {}
|
||||
@ -94,14 +113,18 @@ class Base:
|
||||
logging.info("{} for {} ({}): {}".format(self, for_type, count, 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)
|
||||
|
||||
def _find_best_armor(self, for_type: Task, count: int) -> typing.Dict[Armor, int]:
|
||||
return self._find_best_unit(self.armor, for_type, count)
|
||||
|
||||
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]
|
||||
if points >= 1:
|
||||
self.commision_points[for_type] = points - math.floor(points)
|
||||
@ -110,7 +133,9 @@ class Base:
|
||||
return 0
|
||||
|
||||
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}
|
||||
|
||||
def commision_units(self, units: typing.Dict[typing.Any, int]):
|
||||
@ -149,7 +174,7 @@ class Base:
|
||||
if unit_type not in target_array:
|
||||
print("Base didn't find event type {}".format(unit_type))
|
||||
continue
|
||||
|
||||
|
||||
target_array[unit_type] = max(target_array[unit_type] - count, 0)
|
||||
if target_array[unit_type] == 0:
|
||||
del target_array[unit_type]
|
||||
@ -166,12 +191,20 @@ class Base:
|
||||
|
||||
def scramble_count(self, multiplier: float, task: Task = None) -> int:
|
||||
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:
|
||||
count = self.total_aircraft
|
||||
|
||||
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):
|
||||
return int(self.total_armor * 0.5)
|
||||
@ -202,4 +235,8 @@ class Base:
|
||||
return self._find_best_armor(PinpointStrike, count)
|
||||
|
||||
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(),
|
||||
)
|
||||
|
||||
@ -71,6 +71,7 @@ IMPORTANCE_HIGH = 1.4
|
||||
|
||||
FRONTLINE_MIN_CP_DISTANCE = 5000
|
||||
|
||||
|
||||
def pairwise(iterable):
|
||||
"""
|
||||
itertools recipe
|
||||
@ -156,7 +157,8 @@ class MizCampaignLoader:
|
||||
|
||||
def country(self, blue: bool) -> 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.
|
||||
assert country
|
||||
return country
|
||||
@ -183,7 +185,7 @@ class MizCampaignLoader:
|
||||
for group in self.country(blue).ship_group:
|
||||
if group.units[0].type == self.LHA_UNIT_TYPE:
|
||||
yield group
|
||||
|
||||
|
||||
def fobs(self, blue: bool) -> Iterator[VehicleGroup]:
|
||||
for group in self.country(blue).vehicle_group:
|
||||
if group.units[0].type == self.FOB_UNIT_TYPE:
|
||||
@ -253,22 +255,23 @@ class MizCampaignLoader:
|
||||
|
||||
for blue in (False, True):
|
||||
for group in self.off_map_spawns(blue):
|
||||
control_point = OffMapSpawn(next(self.control_point_id),
|
||||
str(group.name), group.position)
|
||||
control_point = OffMapSpawn(
|
||||
next(self.control_point_id), str(group.name), group.position
|
||||
)
|
||||
control_point.captured = blue
|
||||
control_point.captured_invert = group.late_activation
|
||||
control_points[control_point.id] = control_point
|
||||
for group in self.carriers(blue):
|
||||
# TODO: Name the 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_invert = group.late_activation
|
||||
control_points[control_point.id] = control_point
|
||||
for group in self.lhas(blue):
|
||||
# TODO: Name the LHA.
|
||||
control_point = Lha(
|
||||
"lha", group.position, next(self.control_point_id))
|
||||
control_point = Lha("lha", group.position, next(self.control_point_id))
|
||||
control_point.captured = blue
|
||||
control_point.captured_invert = group.late_activation
|
||||
control_points[control_point.id] = control_point
|
||||
@ -297,24 +300,24 @@ class MizCampaignLoader:
|
||||
# final waypoint at the destination CP. Intermediate waypoints
|
||||
# define the curve of the front line.
|
||||
waypoints = [p.position for p in group.points]
|
||||
origin = self.theater.closest_control_point(waypoints[0])
|
||||
origin = self.theater.closest_control_point(waypoints[0])
|
||||
if origin is None:
|
||||
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])
|
||||
if destination is None:
|
||||
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.
|
||||
waypoints[0] = origin.position
|
||||
waypoints[-1] = destination.position
|
||||
front_line_id = f"{origin.id}|{destination.id}"
|
||||
front_lines[front_line_id] = ComplexFrontLine(origin, waypoints)
|
||||
self.control_points[origin.id].connect(
|
||||
self.control_points[destination.id])
|
||||
self.control_points[destination.id].connect(
|
||||
self.control_points[origin.id])
|
||||
self.control_points[origin.id].connect(self.control_points[destination.id])
|
||||
self.control_points[destination.id].connect(self.control_points[origin.id])
|
||||
return front_lines
|
||||
|
||||
def objective_info(self, group: Group) -> Tuple[ControlPoint, Distance]:
|
||||
@ -328,8 +331,7 @@ class MizCampaignLoader:
|
||||
if distance < self.BASE_DEFENSE_RADIUS:
|
||||
closest.preset_locations.base_garrisons.append(group.position)
|
||||
else:
|
||||
logging.warning(
|
||||
f"Found garrison unit too far from base: {group.name}")
|
||||
logging.warning(f"Found garrison unit too far from base: {group.name}")
|
||||
|
||||
for group in self.sams:
|
||||
closest, distance = self.objective_info(group)
|
||||
@ -344,8 +346,7 @@ class MizCampaignLoader:
|
||||
|
||||
for group in self.offshore_strike_targets:
|
||||
closest, distance = self.objective_info(group)
|
||||
closest.preset_locations.offshore_strike_locations.append(
|
||||
group.position)
|
||||
closest.preset_locations.offshore_strike_locations.append(group.position)
|
||||
|
||||
for group in self.ships:
|
||||
closest, distance = self.objective_info(group)
|
||||
@ -361,15 +362,11 @@ class MizCampaignLoader:
|
||||
|
||||
for group in self.required_long_range_sams:
|
||||
closest, distance = self.objective_info(group)
|
||||
closest.preset_locations.required_long_range_sams.append(
|
||||
group.position
|
||||
)
|
||||
closest.preset_locations.required_long_range_sams.append(group.position)
|
||||
|
||||
for group in self.required_medium_range_sams:
|
||||
closest, distance = self.objective_info(group)
|
||||
closest.preset_locations.required_medium_range_sams.append(
|
||||
group.position
|
||||
)
|
||||
closest.preset_locations.required_medium_range_sams.append(group.position)
|
||||
|
||||
def populate_theater(self) -> None:
|
||||
for control_point in self.control_points.values():
|
||||
@ -423,8 +420,9 @@ class ConflictTheater:
|
||||
logging.warning("Replacing existing frontline data")
|
||||
self._frontline_data = data
|
||||
|
||||
def add_controlpoint(self, point: ControlPoint,
|
||||
connected_to: Optional[List[ControlPoint]] = None):
|
||||
def add_controlpoint(
|
||||
self, point: ControlPoint, connected_to: Optional[List[ControlPoint]] = None
|
||||
):
|
||||
if connected_to is None:
|
||||
connected_to = []
|
||||
for connected_point in connected_to:
|
||||
@ -486,8 +484,8 @@ class ConflictTheater:
|
||||
for inclusion_zone in self.landmap.inclusion_zones:
|
||||
nearest_pair = ops.nearest_points(point, inclusion_zone)
|
||||
nearest_points.append(nearest_pair[1])
|
||||
min_distance = point.distance(nearest_points[0]) # type: geometry.Point
|
||||
nearest_point = nearest_points[0] # type: geometry.Point
|
||||
min_distance = point.distance(nearest_points[0]) # type: geometry.Point
|
||||
nearest_point = nearest_points[0] # type: geometry.Point
|
||||
for pt in nearest_points[1:]:
|
||||
distance = point.distance(pt)
|
||||
if distance < min_distance:
|
||||
@ -498,7 +496,7 @@ class ConflictTheater:
|
||||
nearest_point = Point(nearest_point.x, nearest_point.y)
|
||||
new_point = point.point_from_heading(
|
||||
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
|
||||
|
||||
@ -512,7 +510,9 @@ class ConflictTheater:
|
||||
|
||||
def conflicts(self, from_player=True) -> Iterator[FrontLine]:
|
||||
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)
|
||||
|
||||
def enemy_points(self) -> List[ControlPoint]:
|
||||
@ -547,7 +547,7 @@ class ConflictTheater:
|
||||
closest = conflict
|
||||
closest_distance = distance
|
||||
return closest
|
||||
|
||||
|
||||
def closest_opposing_control_points(self) -> Tuple[ControlPoint, ControlPoint]:
|
||||
"""
|
||||
Returns a tuple of the two nearest opposing ControlPoints in theater.
|
||||
@ -567,17 +567,22 @@ class ConflictTheater:
|
||||
distances[cp.id] = dist
|
||||
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 = [
|
||||
self.find_control_point_by_id(i)
|
||||
for i
|
||||
in min(all_cp_min_distances, key=all_cp_min_distances.get) # type: ignore
|
||||
] # type: List[ControlPoint]
|
||||
for i in min(
|
||||
all_cp_min_distances, key=all_cp_min_distances.get
|
||||
) # type: ignore
|
||||
] # type: List[ControlPoint]
|
||||
assert len(closest_opposing_cps) == 2
|
||||
if closest_opposing_cps[0].captured:
|
||||
return cast(Tuple[ControlPoint, ControlPoint], tuple(closest_opposing_cps))
|
||||
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:
|
||||
for i in self.controlpoints:
|
||||
@ -613,7 +618,7 @@ class ConflictTheater:
|
||||
cp.captured_invert = False
|
||||
|
||||
return cp
|
||||
|
||||
|
||||
@staticmethod
|
||||
def from_json(directory: Path, data: Dict[str, Any]) -> ConflictTheater:
|
||||
theaters = {
|
||||
@ -649,7 +654,7 @@ class ConflictTheater:
|
||||
cps[l[1]].connect(cps[l[0]])
|
||||
|
||||
return t
|
||||
|
||||
|
||||
|
||||
class CaucasusTheater(ConflictTheater):
|
||||
terrain = caucasus.Caucasus()
|
||||
@ -672,8 +677,7 @@ class PersianGulfTheater(ConflictTheater):
|
||||
terrain = persiangulf.PersianGulf()
|
||||
overview_image = "persiangulf.gif"
|
||||
reference_points = (
|
||||
ReferencePoint(persiangulf.Jiroft_Airport.position,
|
||||
Point(1692, 1343)),
|
||||
ReferencePoint(persiangulf.Jiroft_Airport.position, Point(1692, 1343)),
|
||||
ReferencePoint(persiangulf.Liwa_Airbase.position, Point(358, 3238)),
|
||||
)
|
||||
landmap = load_landmap("resources\\gulflandmap.p")
|
||||
@ -722,7 +726,7 @@ class TheChannelTheater(ConflictTheater):
|
||||
overview_image = "thechannel.gif"
|
||||
reference_points = (
|
||||
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")
|
||||
daytime_map = {
|
||||
@ -791,7 +795,7 @@ class FrontLine(MissionTarget):
|
||||
self,
|
||||
control_point_a: ControlPoint,
|
||||
control_point_b: ControlPoint,
|
||||
theater: ConflictTheater
|
||||
theater: ConflictTheater,
|
||||
) -> None:
|
||||
self.control_point_a = control_point_a
|
||||
self.control_point_b = control_point_b
|
||||
@ -935,10 +939,9 @@ class FrontLine(MissionTarget):
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def load_json_frontlines(
|
||||
theater: ConflictTheater
|
||||
theater: ConflictTheater,
|
||||
) -> Optional[Dict[str, ComplexFrontLine]]:
|
||||
"""Load complex frontlines from json"""
|
||||
try:
|
||||
|
||||
@ -231,10 +231,17 @@ class ControlPoint(MissionTarget, ABC):
|
||||
# TODO: Only airbases have IDs.
|
||||
# TODO: has_frontline is only reasonable for airbases.
|
||||
# TODO: cptype is obsolete.
|
||||
def __init__(self, cp_id: int, name: str, position: Point,
|
||||
at: db.StartingPosition, size: int,
|
||||
importance: float, has_frontline=True,
|
||||
cptype=ControlPointType.AIRBASE):
|
||||
def __init__(
|
||||
self,
|
||||
cp_id: int,
|
||||
name: str,
|
||||
position: Point,
|
||||
at: db.StartingPosition,
|
||||
size: int,
|
||||
importance: float,
|
||||
has_frontline=True,
|
||||
cptype=ControlPointType.AIRBASE,
|
||||
):
|
||||
super().__init__(name, position)
|
||||
# TODO: Should be Airbase specific.
|
||||
self.id = cp_id
|
||||
@ -257,17 +264,17 @@ class ControlPoint(MissionTarget, ABC):
|
||||
# TODO: Should be Airbase specific.
|
||||
self.stances: Dict[int, CombatStance] = {}
|
||||
from ..event import UnitsDeliveryEvent
|
||||
|
||||
self.pending_unit_deliveries = UnitsDeliveryEvent(self)
|
||||
|
||||
self.target_position: Optional[Point] = None
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{__class__}: {self.name}>"
|
||||
|
||||
@property
|
||||
def ground_objects(self) -> List[TheaterGroundObject]:
|
||||
return list(
|
||||
itertools.chain(self.connected_objectives, self.base_defenses))
|
||||
return list(itertools.chain(self.connected_objectives, self.base_defenses))
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
@ -342,15 +349,18 @@ class ControlPoint(MissionTarget, ABC):
|
||||
Get the carrier group name if the airbase is a carrier
|
||||
:return: Carrier group name
|
||||
"""
|
||||
if self.cptype in [ControlPointType.AIRCRAFT_CARRIER_GROUP,
|
||||
ControlPointType.LHA_GROUP]:
|
||||
if self.cptype in [
|
||||
ControlPointType.AIRCRAFT_CARRIER_GROUP,
|
||||
ControlPointType.LHA_GROUP,
|
||||
]:
|
||||
for g in self.ground_objects:
|
||||
if g.dcs_identifier == "CARRIER":
|
||||
for group in g.groups:
|
||||
for u in group.units:
|
||||
if db.unit_type_from_name(u.type) in [
|
||||
CVN_74_John_C__Stennis,
|
||||
CV_1143_5_Admiral_Kuznetsov]:
|
||||
CVN_74_John_C__Stennis,
|
||||
CV_1143_5_Admiral_Kuznetsov,
|
||||
]:
|
||||
return group.name
|
||||
elif g.dcs_identifier == "LHA":
|
||||
for group in g.groups:
|
||||
@ -379,17 +389,15 @@ class ControlPoint(MissionTarget, ABC):
|
||||
if isinstance(base_defense, EwrGroundObject):
|
||||
self.preset_locations.ewrs.append(base_defense.position)
|
||||
elif isinstance(base_defense, SamGroundObject):
|
||||
self.preset_locations.base_air_defense.append(
|
||||
base_defense.position)
|
||||
self.preset_locations.base_air_defense.append(base_defense.position)
|
||||
elif isinstance(base_defense, VehicleGroupGroundObject):
|
||||
self.preset_locations.base_garrisons.append(
|
||||
base_defense.position)
|
||||
self.preset_locations.base_garrisons.append(base_defense.position)
|
||||
else:
|
||||
logging.error(
|
||||
"Could not determine preset location type for "
|
||||
f"{base_defense}. Assuming garrison type.")
|
||||
self.preset_locations.base_garrisons.append(
|
||||
base_defense.position)
|
||||
f"{base_defense}. Assuming garrison type."
|
||||
)
|
||||
self.preset_locations.base_garrisons.append(base_defense.position)
|
||||
self.base_defenses = []
|
||||
|
||||
def capture_equipment(self, game: Game) -> None:
|
||||
@ -398,15 +406,18 @@ class ControlPoint(MissionTarget, ABC):
|
||||
game.adjust_budget(total, player=not self.captured)
|
||||
game.message(
|
||||
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):
|
||||
# When there are multiple valid destinations, deliver units to whichever
|
||||
# base is least defended first. The closest approximation of unit
|
||||
# strength we have is price
|
||||
destinations = [GroundUnitDestination(cp)
|
||||
for cp in self.connected_points
|
||||
if cp.captured == self.captured]
|
||||
destinations = [
|
||||
GroundUnitDestination(cp)
|
||||
for cp in self.connected_points
|
||||
if cp.captured == self.captured
|
||||
]
|
||||
if not destinations:
|
||||
self.capture_equipment(game)
|
||||
return
|
||||
@ -419,8 +430,9 @@ class ControlPoint(MissionTarget, ABC):
|
||||
destination.control_point.base.commision_units({unit_type: 1})
|
||||
destination = heapq.heappushpop(destinations, destination)
|
||||
|
||||
def capture_aircraft(self, game: Game, airframe: Type[FlyingType],
|
||||
count: int) -> None:
|
||||
def capture_aircraft(
|
||||
self, game: Game, airframe: Type[FlyingType], count: int
|
||||
) -> None:
|
||||
try:
|
||||
value = PRICES[airframe] * count
|
||||
except KeyError:
|
||||
@ -431,11 +443,12 @@ class ControlPoint(MissionTarget, ABC):
|
||||
game.message(
|
||||
f"No valid retreat destination in range of {self.name} for "
|
||||
f"{airframe.id}. {count} aircraft have been captured and sold for "
|
||||
f"${value}M.")
|
||||
f"${value}M."
|
||||
)
|
||||
|
||||
def aircraft_retreat_destination(
|
||||
self, game: Game,
|
||||
airframe: Type[FlyingType]) -> Optional[ControlPoint]:
|
||||
self, game: Game, airframe: Type[FlyingType]
|
||||
) -> Optional[ControlPoint]:
|
||||
closest = ObjectiveDistanceCache.get_closest_airfields(self)
|
||||
# TODO: Should be airframe dependent.
|
||||
max_retreat_distance = nautical_miles(200)
|
||||
@ -451,8 +464,9 @@ class ControlPoint(MissionTarget, ABC):
|
||||
return airbase
|
||||
return None
|
||||
|
||||
def _retreat_air_units(self, game: Game, airframe: Type[FlyingType],
|
||||
count: int) -> None:
|
||||
def _retreat_air_units(
|
||||
self, game: Game, airframe: Type[FlyingType], count: int
|
||||
) -> None:
|
||||
while count:
|
||||
logging.debug(f"Retreating {count} {airframe.id} from {self.name}")
|
||||
destination = self.aircraft_retreat_destination(game, airframe)
|
||||
@ -485,6 +499,7 @@ class ControlPoint(MissionTarget, ABC):
|
||||
|
||||
self.clear_base_defenses()
|
||||
from .start_generator import BaseDefenseGenerator
|
||||
|
||||
BaseDefenseGenerator(game, self).generate()
|
||||
|
||||
@abstractmethod
|
||||
@ -514,16 +529,19 @@ class ControlPoint(MissionTarget, ABC):
|
||||
if issubclass(unit_bought, FlyingType):
|
||||
on_order += self.pending_unit_deliveries.units[unit_bought]
|
||||
|
||||
return PendingOccupancy(self.base.total_aircraft, on_order,
|
||||
self.aircraft_transferring(game))
|
||||
return PendingOccupancy(
|
||||
self.base.total_aircraft, on_order, self.aircraft_transferring(game)
|
||||
)
|
||||
|
||||
def unclaimed_parking(self, game: Game) -> int:
|
||||
return (self.total_aircraft_parking -
|
||||
self.expected_aircraft_next_turn(game).total)
|
||||
return (
|
||||
self.total_aircraft_parking - self.expected_aircraft_next_turn(game).total
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def active_runway(self, conditions: Conditions,
|
||||
dynamic_runways: Dict[str, RunwayData]) -> RunwayData:
|
||||
def active_runway(
|
||||
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
|
||||
) -> RunwayData:
|
||||
...
|
||||
|
||||
@property
|
||||
@ -574,7 +592,13 @@ class ControlPoint(MissionTarget, ABC):
|
||||
Get number of pending frontline aa units
|
||||
"""
|
||||
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:
|
||||
return 0
|
||||
|
||||
@ -598,9 +622,12 @@ class ControlPoint(MissionTarget, ABC):
|
||||
continue
|
||||
on_order += self.pending_unit_deliveries.units[unit_bought]
|
||||
|
||||
return PendingOccupancy(self.base.total_armor, on_order,
|
||||
# Ground unit transfers not yet implemented.
|
||||
transferring=0)
|
||||
return PendingOccupancy(
|
||||
self.base.total_armor,
|
||||
on_order,
|
||||
# Ground unit transfers not yet implemented.
|
||||
transferring=0,
|
||||
)
|
||||
|
||||
@property
|
||||
def income_per_turn(self) -> int:
|
||||
@ -608,16 +635,23 @@ class ControlPoint(MissionTarget, ABC):
|
||||
|
||||
@property
|
||||
def has_active_frontline(self) -> bool:
|
||||
return any(
|
||||
not c.is_friendly(self.captured) for c in self.connected_points)
|
||||
return any(not c.is_friendly(self.captured) for c in self.connected_points)
|
||||
|
||||
|
||||
class Airfield(ControlPoint):
|
||||
|
||||
def __init__(self, airport: Airport, size: int,
|
||||
importance: float, has_frontline=True):
|
||||
super().__init__(airport.id, airport.name, airport.position, airport,
|
||||
size, importance, has_frontline,
|
||||
cptype=ControlPointType.AIRBASE)
|
||||
def __init__(
|
||||
self, airport: Airport, size: int, importance: float, has_frontline=True
|
||||
):
|
||||
super().__init__(
|
||||
airport.id,
|
||||
airport.name,
|
||||
airport.position,
|
||||
airport,
|
||||
size,
|
||||
importance,
|
||||
has_frontline,
|
||||
cptype=ControlPointType.AIRBASE,
|
||||
)
|
||||
self.airport = airport
|
||||
self._runway_status = RunwayStatus()
|
||||
|
||||
@ -631,6 +665,7 @@ class Airfield(ControlPoint):
|
||||
|
||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||
from gen.flights.flight import FlightType
|
||||
|
||||
if self.is_friendly(for_player):
|
||||
yield from [
|
||||
# TODO: FlightType.INTERCEPTION
|
||||
@ -661,8 +696,9 @@ class Airfield(ControlPoint):
|
||||
def damage_runway(self) -> None:
|
||||
self.runway_status.damage()
|
||||
|
||||
def active_runway(self, conditions: Conditions,
|
||||
dynamic_runways: Dict[str, RunwayData]) -> RunwayData:
|
||||
def active_runway(
|
||||
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
|
||||
) -> RunwayData:
|
||||
assigner = RunwayAssigner(conditions)
|
||||
return assigner.get_preferred_runway(self.airport)
|
||||
|
||||
@ -680,13 +716,13 @@ class Airfield(ControlPoint):
|
||||
|
||||
|
||||
class NavalControlPoint(ControlPoint, ABC):
|
||||
|
||||
@property
|
||||
def is_fleet(self) -> bool:
|
||||
return True
|
||||
|
||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||
from gen.flights.flight import FlightType
|
||||
|
||||
if self.is_friendly(for_player):
|
||||
yield from [
|
||||
# TODO: FlightType.INTERCEPTION
|
||||
@ -710,14 +746,17 @@ class NavalControlPoint(ControlPoint, ABC):
|
||||
for group in g.groups:
|
||||
for u in group.units:
|
||||
if db.unit_type_from_name(u.type) in [
|
||||
CVN_74_John_C__Stennis, LHA_1_Tarawa,
|
||||
CV_1143_5_Admiral_Kuznetsov,
|
||||
Type_071_Amphibious_Transport_Dock]:
|
||||
CVN_74_John_C__Stennis,
|
||||
LHA_1_Tarawa,
|
||||
CV_1143_5_Admiral_Kuznetsov,
|
||||
Type_071_Amphibious_Transport_Dock,
|
||||
]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def active_runway(self, conditions: Conditions,
|
||||
dynamic_runways: Dict[str, RunwayData]) -> RunwayData:
|
||||
def active_runway(
|
||||
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
|
||||
) -> RunwayData:
|
||||
# TODO: Assign TACAN and ICLS earlier so we don't need this.
|
||||
fallback = RunwayData(self.full_name, runway_heading=0, runway_name="")
|
||||
return dynamic_runways.get(self.name, fallback)
|
||||
@ -740,12 +779,19 @@ class NavalControlPoint(ControlPoint, ABC):
|
||||
|
||||
|
||||
class Carrier(NavalControlPoint):
|
||||
|
||||
def __init__(self, name: str, at: Point, cp_id: int):
|
||||
import game.theater.conflicttheater
|
||||
super().__init__(cp_id, name, at, at,
|
||||
game.theater.conflicttheater.SIZE_SMALL, 1,
|
||||
has_frontline=False, cptype=ControlPointType.AIRCRAFT_CARRIER_GROUP)
|
||||
|
||||
super().__init__(
|
||||
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:
|
||||
raise RuntimeError("Carriers cannot be captured")
|
||||
@ -763,12 +809,19 @@ class Carrier(NavalControlPoint):
|
||||
|
||||
|
||||
class Lha(NavalControlPoint):
|
||||
|
||||
def __init__(self, name: str, at: Point, cp_id: int):
|
||||
import game.theater.conflicttheater
|
||||
super().__init__(cp_id, name, at, at,
|
||||
game.theater.conflicttheater.SIZE_SMALL, 1,
|
||||
has_frontline=False, cptype=ControlPointType.LHA_GROUP)
|
||||
|
||||
super().__init__(
|
||||
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:
|
||||
raise RuntimeError("LHAs cannot be captured")
|
||||
@ -786,15 +839,22 @@ class Lha(NavalControlPoint):
|
||||
|
||||
|
||||
class OffMapSpawn(ControlPoint):
|
||||
|
||||
def runway_is_operational(self) -> bool:
|
||||
return True
|
||||
|
||||
def __init__(self, cp_id: int, name: str, position: Point):
|
||||
from . import IMPORTANCE_MEDIUM, SIZE_REGULAR
|
||||
super().__init__(cp_id, name, position, at=position,
|
||||
size=SIZE_REGULAR, importance=IMPORTANCE_MEDIUM,
|
||||
has_frontline=False, cptype=ControlPointType.OFF_MAP)
|
||||
|
||||
super().__init__(
|
||||
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:
|
||||
raise RuntimeError("Off map control points cannot be captured")
|
||||
@ -813,8 +873,9 @@ class OffMapSpawn(ControlPoint):
|
||||
def heading(self) -> int:
|
||||
return 0
|
||||
|
||||
def active_runway(self, conditions: Conditions,
|
||||
dynamic_runways: Dict[str, RunwayData]) -> RunwayData:
|
||||
def active_runway(
|
||||
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
|
||||
) -> RunwayData:
|
||||
logging.warning("TODO: Off map spawns have no runways.")
|
||||
return RunwayData(self.full_name, runway_heading=0, runway_name="")
|
||||
|
||||
@ -828,19 +889,27 @@ class OffMapSpawn(ControlPoint):
|
||||
|
||||
|
||||
class Fob(ControlPoint):
|
||||
|
||||
def __init__(self, name: str, at: Point, cp_id: int):
|
||||
import game.theater.conflicttheater
|
||||
super().__init__(cp_id, name, at, at,
|
||||
game.theater.conflicttheater.SIZE_SMALL, 1,
|
||||
has_frontline=True, cptype=ControlPointType.FOB)
|
||||
|
||||
super().__init__(
|
||||
cp_id,
|
||||
name,
|
||||
at,
|
||||
at,
|
||||
game.theater.conflicttheater.SIZE_SMALL,
|
||||
1,
|
||||
has_frontline=True,
|
||||
cptype=ControlPointType.FOB,
|
||||
)
|
||||
self.name = name
|
||||
|
||||
|
||||
def runway_is_operational(self) -> bool:
|
||||
return False
|
||||
|
||||
def active_runway(self, conditions: Conditions,
|
||||
dynamic_runways: Dict[str, RunwayData]) -> RunwayData:
|
||||
|
||||
def active_runway(
|
||||
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
|
||||
) -> RunwayData:
|
||||
logging.warning("TODO: FOBs have no runways.")
|
||||
return RunwayData(self.full_name, runway_heading=0, runway_name="")
|
||||
|
||||
@ -850,6 +919,7 @@ class Fob(ControlPoint):
|
||||
|
||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||
from gen.flights.flight import FlightType
|
||||
|
||||
if self.is_friendly(for_player):
|
||||
yield from [
|
||||
FlightType.BARCAP,
|
||||
|
||||
@ -46,4 +46,3 @@ def poly_centroid(poly) -> Tuple[float, float]:
|
||||
x = sum(x_list) / len(poly)
|
||||
y = sum(y_list) / len(poly)
|
||||
return (x, y)
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@ class MissionTarget:
|
||||
|
||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||
from gen.flights.flight import FlightType
|
||||
|
||||
if self.is_friendly(for_player):
|
||||
yield FlightType.BARCAP
|
||||
else:
|
||||
|
||||
@ -76,9 +76,14 @@ class GeneratorSettings:
|
||||
|
||||
|
||||
class GameGenerator:
|
||||
def __init__(self, player: str, enemy: str, theater: ConflictTheater,
|
||||
settings: Settings,
|
||||
generator_settings: GeneratorSettings) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
player: str,
|
||||
enemy: str,
|
||||
theater: ConflictTheater,
|
||||
settings: Settings,
|
||||
generator_settings: GeneratorSettings,
|
||||
) -> None:
|
||||
self.player = player
|
||||
self.enemy = enemy
|
||||
self.theater = theater
|
||||
@ -96,7 +101,7 @@ class GameGenerator:
|
||||
start_date=self.generator_settings.start_date,
|
||||
settings=self.settings,
|
||||
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()
|
||||
@ -108,7 +113,7 @@ class GameGenerator:
|
||||
# Auto-capture half the bases if midgame.
|
||||
if self.generator_settings.midgame:
|
||||
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
|
||||
|
||||
# Remove carrier and lha, invert situation if needed
|
||||
@ -140,28 +145,37 @@ class LocationFinder:
|
||||
self.game = game
|
||||
self.control_point = control_point
|
||||
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[Point]:
|
||||
position = self.control_point.preset_locations.random_for(location_type)
|
||||
if position is not None:
|
||||
return position
|
||||
|
||||
logging.warning(f"No campaign location for %s at %s",
|
||||
location_type.value, self.control_point)
|
||||
logging.warning(
|
||||
f"No campaign location for %s at %s",
|
||||
location_type.value,
|
||||
self.control_point,
|
||||
)
|
||||
position = self.random_from_miz_data(
|
||||
location_type == LocationType.OffshoreStrikeTarget)
|
||||
location_type == LocationType.OffshoreStrikeTarget
|
||||
)
|
||||
if position is not None:
|
||||
return position
|
||||
|
||||
logging.debug(f"No mizdata location for %s at %s", location_type.value,
|
||||
self.control_point)
|
||||
logging.debug(
|
||||
f"No mizdata location for %s at %s", location_type.value, self.control_point
|
||||
)
|
||||
position = self.random_position(location_type)
|
||||
if position is not None:
|
||||
return position
|
||||
|
||||
logging.error(f"Could not find position for %s at %s",
|
||||
location_type.value, self.control_point)
|
||||
logging.error(
|
||||
f"Could not find position for %s at %s",
|
||||
location_type.value,
|
||||
self.control_point,
|
||||
)
|
||||
return None
|
||||
|
||||
def random_from_miz_data(self, offshore: bool) -> Optional[Point]:
|
||||
@ -177,8 +191,11 @@ class LocationFinder:
|
||||
|
||||
def random_position(self, location_type: LocationType) -> Optional[Point]:
|
||||
# TODO: Flesh out preset locations so we never hit this case.
|
||||
logging.warning("Falling back to random location for %s at %s",
|
||||
location_type.value, self.control_point)
|
||||
logging.warning(
|
||||
"Falling back to random location for %s at %s",
|
||||
location_type.value,
|
||||
self.control_point,
|
||||
)
|
||||
|
||||
is_base_defense = location_type in {
|
||||
LocationType.BaseAirDefense,
|
||||
@ -212,23 +229,28 @@ class LocationFinder:
|
||||
min_range = 10000
|
||||
max_range = 40000
|
||||
|
||||
position = self._find_random_position(min_range, max_range,
|
||||
on_land, is_base_defense,
|
||||
avoid_others)
|
||||
position = self._find_random_position(
|
||||
min_range, max_range, on_land, is_base_defense, avoid_others
|
||||
)
|
||||
|
||||
# Retry once, searching a bit further (On some big airbases, 3200 is too
|
||||
# short (Ex : Incirlik)), but searching farther on every base would be
|
||||
# problematic, as some base defense units would end up very far away
|
||||
# from small airfields.
|
||||
if position is None and is_base_defense:
|
||||
position = self._find_random_position(3200, 4800,
|
||||
on_land, is_base_defense,
|
||||
avoid_others)
|
||||
position = self._find_random_position(
|
||||
3200, 4800, on_land, is_base_defense, avoid_others
|
||||
)
|
||||
return position
|
||||
|
||||
def _find_random_position(self, min_range: int, max_range: int,
|
||||
on_ground: bool, is_base_defense: bool,
|
||||
avoid_others: bool) -> Optional[Point]:
|
||||
def _find_random_position(
|
||||
self,
|
||||
min_range: int,
|
||||
max_range: int,
|
||||
on_ground: bool,
|
||||
is_base_defense: bool,
|
||||
avoid_others: bool,
|
||||
) -> Optional[Point]:
|
||||
"""
|
||||
Find a valid ground object location
|
||||
:param on_ground: Whether it should be on ground or on sea (True = on
|
||||
@ -279,8 +301,12 @@ class LocationFinder:
|
||||
|
||||
|
||||
class ControlPointGroundObjectGenerator:
|
||||
def __init__(self, game: Game, generator_settings: GeneratorSettings,
|
||||
control_point: ControlPoint) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
game: Game,
|
||||
generator_settings: GeneratorSettings,
|
||||
control_point: ControlPoint,
|
||||
) -> None:
|
||||
self.game = game
|
||||
self.generator_settings = generator_settings
|
||||
self.control_point = control_point
|
||||
@ -321,15 +347,15 @@ class ControlPointGroundObjectGenerator:
|
||||
self.generate_ship()
|
||||
|
||||
def generate_ship(self) -> None:
|
||||
point = self.location_finder.location_for(
|
||||
LocationType.OffshoreStrikeTarget)
|
||||
point = self.location_finder.location_for(LocationType.OffshoreStrikeTarget)
|
||||
if point is None:
|
||||
return
|
||||
|
||||
group_id = self.game.next_group_id()
|
||||
|
||||
g = ShipGroundObject(namegen.random_objective_name(), group_id, point,
|
||||
self.control_point)
|
||||
g = ShipGroundObject(
|
||||
namegen.random_objective_name(), group_id, point, self.control_point
|
||||
)
|
||||
|
||||
group = generate_ship_group(self.game, g, self.faction_name)
|
||||
g.groups = []
|
||||
@ -352,13 +378,15 @@ class CarrierGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
if not carrier_names:
|
||||
logging.info(
|
||||
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
|
||||
|
||||
# Create ground object group
|
||||
group_id = self.game.next_group_id()
|
||||
g = CarrierGroundObject(namegen.random_objective_name(), group_id,
|
||||
self.control_point)
|
||||
g = CarrierGroundObject(
|
||||
namegen.random_objective_name(), group_id, self.control_point
|
||||
)
|
||||
group = generate_carrier_group(self.faction_name, self.game, g)
|
||||
g.groups = []
|
||||
if group is not None:
|
||||
@ -377,13 +405,15 @@ class LhaGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
if not lha_names:
|
||||
logging.info(
|
||||
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
|
||||
|
||||
# Create ground object group
|
||||
group_id = self.game.next_group_id()
|
||||
g = LhaGroundObject(namegen.random_objective_name(), group_id,
|
||||
self.control_point)
|
||||
g = LhaGroundObject(
|
||||
namegen.random_objective_name(), group_id, self.control_point
|
||||
)
|
||||
group = generate_lha_group(self.faction_name, self.game, g)
|
||||
g.groups = []
|
||||
if group is not None:
|
||||
@ -422,8 +452,9 @@ class BaseDefenseGenerator:
|
||||
|
||||
group_id = self.game.next_group_id()
|
||||
|
||||
g = EwrGroundObject(namegen.random_objective_name(), group_id,
|
||||
position, self.control_point)
|
||||
g = EwrGroundObject(
|
||||
namegen.random_objective_name(), group_id, position, self.control_point
|
||||
)
|
||||
|
||||
group = generate_ewr_group(self.game, g, self.faction)
|
||||
if group is None:
|
||||
@ -454,28 +485,35 @@ class BaseDefenseGenerator:
|
||||
|
||||
group_id = self.game.next_group_id()
|
||||
|
||||
g = VehicleGroupGroundObject(namegen.random_objective_name(), group_id,
|
||||
position, self.control_point,
|
||||
for_airbase=True)
|
||||
g = VehicleGroupGroundObject(
|
||||
namegen.random_objective_name(),
|
||||
group_id,
|
||||
position,
|
||||
self.control_point,
|
||||
for_airbase=True,
|
||||
)
|
||||
|
||||
group = generate_armor_group(self.faction_name, self.game, g)
|
||||
if group is None:
|
||||
logging.error(
|
||||
f"Could not generate garrison at {self.control_point}")
|
||||
logging.error(f"Could not generate garrison at {self.control_point}")
|
||||
return
|
||||
g.groups.append(group)
|
||||
self.control_point.base_defenses.append(g)
|
||||
|
||||
def generate_sam(self) -> None:
|
||||
position = self.location_finder.location_for(
|
||||
LocationType.BaseAirDefense)
|
||||
position = self.location_finder.location_for(LocationType.BaseAirDefense)
|
||||
if position is None:
|
||||
return
|
||||
|
||||
group_id = self.game.next_group_id()
|
||||
|
||||
g = SamGroundObject(namegen.random_objective_name(), group_id,
|
||||
position, self.control_point, for_airbase=True)
|
||||
g = SamGroundObject(
|
||||
namegen.random_objective_name(),
|
||||
group_id,
|
||||
position,
|
||||
self.control_point,
|
||||
for_airbase=True,
|
||||
)
|
||||
|
||||
groups = generate_anti_air_group(self.game, g, self.faction)
|
||||
if not groups:
|
||||
@ -485,21 +523,25 @@ class BaseDefenseGenerator:
|
||||
self.control_point.base_defenses.append(g)
|
||||
|
||||
def generate_shorad(self) -> None:
|
||||
position = self.location_finder.location_for(
|
||||
LocationType.BaseAirDefense)
|
||||
position = self.location_finder.location_for(LocationType.BaseAirDefense)
|
||||
if position is None:
|
||||
return
|
||||
|
||||
group_id = self.game.next_group_id()
|
||||
|
||||
g = SamGroundObject(namegen.random_objective_name(), group_id,
|
||||
position, self.control_point, for_airbase=True)
|
||||
g = SamGroundObject(
|
||||
namegen.random_objective_name(),
|
||||
group_id,
|
||||
position,
|
||||
self.control_point,
|
||||
for_airbase=True,
|
||||
)
|
||||
|
||||
groups = generate_anti_air_group(self.game, g, self.faction,
|
||||
ranges=[{AirDefenseRange.Short}])
|
||||
groups = generate_anti_air_group(
|
||||
self.game, g, self.faction, ranges=[{AirDefenseRange.Short}]
|
||||
)
|
||||
if not groups:
|
||||
logging.error(
|
||||
f"Could not generate SHORAD group at {self.control_point}")
|
||||
logging.error(f"Could not generate SHORAD group at {self.control_point}")
|
||||
return
|
||||
g.groups = groups
|
||||
self.control_point.base_defenses.append(g)
|
||||
@ -509,7 +551,7 @@ class FobDefenseGenerator(BaseDefenseGenerator):
|
||||
def generate(self) -> None:
|
||||
self.generate_garrison()
|
||||
self.generate_fob_defenses()
|
||||
|
||||
|
||||
def generate_fob_defenses(self):
|
||||
# First group has a 1/2 chance of being a SHORAD,
|
||||
# and a 1/2 chance of a garrison.
|
||||
@ -528,9 +570,13 @@ class FobDefenseGenerator(BaseDefenseGenerator):
|
||||
|
||||
|
||||
class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
def __init__(self, game: Game, generator_settings: GeneratorSettings,
|
||||
control_point: ControlPoint,
|
||||
templates: GroundObjectTemplates) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
game: Game,
|
||||
generator_settings: GeneratorSettings,
|
||||
control_point: ControlPoint,
|
||||
templates: GroundObjectTemplates,
|
||||
) -> None:
|
||||
super().__init__(game, generator_settings, control_point)
|
||||
self.templates = templates
|
||||
|
||||
@ -576,18 +622,25 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
"""
|
||||
presets = self.control_point.preset_locations
|
||||
for position in presets.required_long_range_sams:
|
||||
self.generate_aa_at(position, ranges=[
|
||||
{AirDefenseRange.Long},
|
||||
{AirDefenseRange.Medium},
|
||||
{AirDefenseRange.Short},
|
||||
])
|
||||
self.generate_aa_at(
|
||||
position,
|
||||
ranges=[
|
||||
{AirDefenseRange.Long},
|
||||
{AirDefenseRange.Medium},
|
||||
{AirDefenseRange.Short},
|
||||
],
|
||||
)
|
||||
for position in presets.required_medium_range_sams:
|
||||
self.generate_aa_at(position, ranges=[
|
||||
{AirDefenseRange.Medium},
|
||||
{AirDefenseRange.Short},
|
||||
])
|
||||
return (len(presets.required_long_range_sams) +
|
||||
len(presets.required_medium_range_sams))
|
||||
self.generate_aa_at(
|
||||
position,
|
||||
ranges=[
|
||||
{AirDefenseRange.Medium},
|
||||
{AirDefenseRange.Short},
|
||||
],
|
||||
)
|
||||
return len(presets.required_long_range_sams) + len(
|
||||
presets.required_medium_range_sams
|
||||
)
|
||||
|
||||
def generate_ground_point(self) -> None:
|
||||
try:
|
||||
@ -618,8 +671,15 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
|
||||
template_point = Point(unit["offset"].x, unit["offset"].y)
|
||||
g = BuildingGroundObject(
|
||||
obj_name, category, group_id, object_id, point + template_point,
|
||||
unit["heading"], self.control_point, unit["type"])
|
||||
obj_name,
|
||||
category,
|
||||
group_id,
|
||||
object_id,
|
||||
point + template_point,
|
||||
unit["heading"],
|
||||
self.control_point,
|
||||
unit["type"],
|
||||
)
|
||||
|
||||
self.control_point.connected_objectives.append(g)
|
||||
|
||||
@ -627,23 +687,34 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
position = self.location_finder.location_for(LocationType.Sam)
|
||||
if position is None:
|
||||
return
|
||||
self.generate_aa_at(position, ranges=[
|
||||
# Prefer to use proper SAMs, but fall back to SHORADs if needed.
|
||||
{AirDefenseRange.Long, AirDefenseRange.Medium},
|
||||
{AirDefenseRange.Short},
|
||||
])
|
||||
self.generate_aa_at(
|
||||
position,
|
||||
ranges=[
|
||||
# Prefer to use proper SAMs, but fall back to SHORADs if needed.
|
||||
{AirDefenseRange.Long, AirDefenseRange.Medium},
|
||||
{AirDefenseRange.Short},
|
||||
],
|
||||
)
|
||||
|
||||
def generate_aa_at(
|
||||
self, position: Point,
|
||||
ranges: Iterable[Set[AirDefenseRange]]) -> None:
|
||||
self, position: Point, ranges: Iterable[Set[AirDefenseRange]]
|
||||
) -> None:
|
||||
group_id = self.game.next_group_id()
|
||||
|
||||
g = SamGroundObject(namegen.random_objective_name(), group_id,
|
||||
position, self.control_point, for_airbase=False)
|
||||
g = SamGroundObject(
|
||||
namegen.random_objective_name(),
|
||||
group_id,
|
||||
position,
|
||||
self.control_point,
|
||||
for_airbase=False,
|
||||
)
|
||||
groups = generate_anti_air_group(self.game, g, self.faction, ranges)
|
||||
if not groups:
|
||||
logging.error("Could not generate air defense group for %s at %s",
|
||||
g.name, self.control_point)
|
||||
logging.error(
|
||||
"Could not generate air defense group for %s at %s",
|
||||
g.name,
|
||||
self.control_point,
|
||||
)
|
||||
return
|
||||
g.groups = groups
|
||||
self.control_point.connected_objectives.append(g)
|
||||
@ -659,8 +730,9 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
|
||||
group_id = self.game.next_group_id()
|
||||
|
||||
g = MissileSiteGroundObject(namegen.random_objective_name(), group_id,
|
||||
position, self.control_point)
|
||||
g = MissileSiteGroundObject(
|
||||
namegen.random_objective_name(), group_id, position, self.control_point
|
||||
)
|
||||
group = generate_missile_group(self.game, g, self.faction_name)
|
||||
g.groups = []
|
||||
if group is not None:
|
||||
@ -678,7 +750,7 @@ class FobGroundObjectGenerator(AirbaseGroundObjectGenerator):
|
||||
|
||||
def generate_fob(self) -> None:
|
||||
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:
|
||||
logging.exception("Faction has no fob buildings defined")
|
||||
return
|
||||
@ -696,14 +768,21 @@ class FobGroundObjectGenerator(AirbaseGroundObjectGenerator):
|
||||
|
||||
template_point = Point(unit["offset"].x, unit["offset"].y)
|
||||
g = BuildingGroundObject(
|
||||
obj_name, category, group_id, object_id, point + template_point,
|
||||
unit["heading"], self.control_point, unit["type"], airbase_group=True)
|
||||
obj_name,
|
||||
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)
|
||||
|
||||
|
||||
class GroundObjectGenerator:
|
||||
def __init__(self, game: Game,
|
||||
generator_settings: GeneratorSettings) -> None:
|
||||
def __init__(self, game: Game, generator_settings: GeneratorSettings) -> None:
|
||||
self.game = game
|
||||
self.generator_settings = generator_settings
|
||||
with open("resources/groundobject_templates.p", "rb") as f:
|
||||
@ -721,19 +800,22 @@ class GroundObjectGenerator:
|
||||
generator: ControlPointGroundObjectGenerator
|
||||
if control_point.cptype == ControlPointType.AIRCRAFT_CARRIER_GROUP:
|
||||
generator = CarrierGroundObjectGenerator(
|
||||
self.game, self.generator_settings, control_point)
|
||||
self.game, self.generator_settings, control_point
|
||||
)
|
||||
elif control_point.cptype == ControlPointType.LHA_GROUP:
|
||||
generator = LhaGroundObjectGenerator(
|
||||
self.game, self.generator_settings, control_point)
|
||||
self.game, self.generator_settings, control_point
|
||||
)
|
||||
elif isinstance(control_point, OffMapSpawn):
|
||||
generator = NoOpGroundObjectGenerator(
|
||||
self.game, self.generator_settings, control_point)
|
||||
self.game, self.generator_settings, control_point
|
||||
)
|
||||
elif isinstance(control_point, Fob):
|
||||
generator = FobGroundObjectGenerator(
|
||||
self.game, self.generator_settings, control_point,
|
||||
self.templates)
|
||||
self.game, self.generator_settings, control_point, self.templates
|
||||
)
|
||||
else:
|
||||
generator = AirbaseGroundObjectGenerator(
|
||||
self.game, self.generator_settings, control_point,
|
||||
self.templates)
|
||||
self.game, self.generator_settings, control_point, self.templates
|
||||
)
|
||||
return generator.generate()
|
||||
|
||||
@ -33,7 +33,7 @@ NAME_BY_CATEGORY = {
|
||||
"ww2bunker": "Bunker",
|
||||
"village": "Village",
|
||||
"allycamp": "Camp",
|
||||
"EWR":"EWR",
|
||||
"EWR": "EWR",
|
||||
}
|
||||
|
||||
ABBREV_NAME = {
|
||||
@ -54,34 +54,59 @@ ABBREV_NAME = {
|
||||
}
|
||||
|
||||
CATEGORY_MAP = {
|
||||
|
||||
# Special cases
|
||||
"CARRIER": ["CARRIER"],
|
||||
"LHA": ["LHA"],
|
||||
"aa": ["AA"],
|
||||
|
||||
# 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"],
|
||||
"fuel": ["Tank", "Tank 2", "Tank 3", "Fuel tank"],
|
||||
"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"],
|
||||
"factory": ["Tech combine", "Tech hangar A"],
|
||||
"comms": ["TV tower", "Comms tower M"],
|
||||
"oil": ["Oil platform"],
|
||||
"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"],
|
||||
"allycamp": [],
|
||||
}
|
||||
|
||||
|
||||
class TheaterGroundObject(MissionTarget):
|
||||
|
||||
def __init__(self, name: str, category: str, group_id: int, position: Point,
|
||||
heading: int, control_point: ControlPoint, dcs_identifier: str,
|
||||
airbase_group: bool, sea_object: bool) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
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)
|
||||
self.category = category
|
||||
self.group_id = group_id
|
||||
@ -131,6 +156,7 @@ class TheaterGroundObject(MissionTarget):
|
||||
|
||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||
from gen.flights.flight import FlightType
|
||||
|
||||
if self.is_friendly(for_player):
|
||||
yield from [
|
||||
# TODO: FlightType.LOGISTICS
|
||||
@ -193,9 +219,18 @@ class TheaterGroundObject(MissionTarget):
|
||||
|
||||
|
||||
class BuildingGroundObject(TheaterGroundObject):
|
||||
def __init__(self, name: str, category: str, group_id: int, object_id: int,
|
||||
position: Point, heading: int, control_point: ControlPoint,
|
||||
dcs_identifier: str, airbase_group=False) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
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__(
|
||||
name=name,
|
||||
category=category,
|
||||
@ -205,7 +240,7 @@ class BuildingGroundObject(TheaterGroundObject):
|
||||
control_point=control_point,
|
||||
dcs_identifier=dcs_identifier,
|
||||
airbase_group=airbase_group,
|
||||
sea_object=False
|
||||
sea_object=False,
|
||||
)
|
||||
self.object_id = object_id
|
||||
# Other TGOs track deadness based on the number of alive units, but
|
||||
@ -234,6 +269,7 @@ class BuildingGroundObject(TheaterGroundObject):
|
||||
class NavalGroundObject(TheaterGroundObject):
|
||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||
from gen.flights.flight import FlightType
|
||||
|
||||
if not self.is_friendly(for_player):
|
||||
yield FlightType.ANTISHIP
|
||||
yield from super().mission_types(for_player)
|
||||
@ -249,8 +285,7 @@ class GenericCarrierGroundObject(NavalGroundObject):
|
||||
|
||||
# TODO: Why is this both a CP and a TGO?
|
||||
class CarrierGroundObject(GenericCarrierGroundObject):
|
||||
def __init__(self, name: str, group_id: int,
|
||||
control_point: ControlPoint) -> None:
|
||||
def __init__(self, name: str, group_id: int, control_point: ControlPoint) -> None:
|
||||
super().__init__(
|
||||
name=name,
|
||||
category="CARRIER",
|
||||
@ -260,7 +295,7 @@ class CarrierGroundObject(GenericCarrierGroundObject):
|
||||
control_point=control_point,
|
||||
dcs_identifier="CARRIER",
|
||||
airbase_group=True,
|
||||
sea_object=True
|
||||
sea_object=True,
|
||||
)
|
||||
|
||||
@property
|
||||
@ -272,8 +307,7 @@ class CarrierGroundObject(GenericCarrierGroundObject):
|
||||
|
||||
# TODO: Why is this both a CP and a TGO?
|
||||
class LhaGroundObject(GenericCarrierGroundObject):
|
||||
def __init__(self, name: str, group_id: int,
|
||||
control_point: ControlPoint) -> None:
|
||||
def __init__(self, name: str, group_id: int, control_point: ControlPoint) -> None:
|
||||
super().__init__(
|
||||
name=name,
|
||||
category="LHA",
|
||||
@ -283,7 +317,7 @@ class LhaGroundObject(GenericCarrierGroundObject):
|
||||
control_point=control_point,
|
||||
dcs_identifier="LHA",
|
||||
airbase_group=True,
|
||||
sea_object=True
|
||||
sea_object=True,
|
||||
)
|
||||
|
||||
@property
|
||||
@ -294,8 +328,9 @@ class LhaGroundObject(GenericCarrierGroundObject):
|
||||
|
||||
|
||||
class MissileSiteGroundObject(TheaterGroundObject):
|
||||
def __init__(self, name: str, group_id: int, position: Point,
|
||||
control_point: ControlPoint) -> None:
|
||||
def __init__(
|
||||
self, name: str, group_id: int, position: Point, control_point: ControlPoint
|
||||
) -> None:
|
||||
super().__init__(
|
||||
name=name,
|
||||
category="aa",
|
||||
@ -305,7 +340,7 @@ class MissileSiteGroundObject(TheaterGroundObject):
|
||||
control_point=control_point,
|
||||
dcs_identifier="AA",
|
||||
airbase_group=False,
|
||||
sea_object=False
|
||||
sea_object=False,
|
||||
)
|
||||
|
||||
|
||||
@ -317,8 +352,14 @@ class BaseDefenseGroundObject(TheaterGroundObject):
|
||||
# This type gets used both for AA sites (SAM, AAA, or SHORAD). These should each
|
||||
# be split into their own types.
|
||||
class SamGroundObject(BaseDefenseGroundObject):
|
||||
def __init__(self, name: str, group_id: int, position: Point,
|
||||
control_point: ControlPoint, for_airbase: bool) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
group_id: int,
|
||||
position: Point,
|
||||
control_point: ControlPoint,
|
||||
for_airbase: bool,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
name=name,
|
||||
category="aa",
|
||||
@ -328,7 +369,7 @@ class SamGroundObject(BaseDefenseGroundObject):
|
||||
control_point=control_point,
|
||||
dcs_identifier="AA",
|
||||
airbase_group=for_airbase,
|
||||
sea_object=False
|
||||
sea_object=False,
|
||||
)
|
||||
# Set by the SAM unit generator if the generated group is compatible
|
||||
# with Skynet.
|
||||
@ -345,6 +386,7 @@ class SamGroundObject(BaseDefenseGroundObject):
|
||||
|
||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||
from gen.flights.flight import FlightType
|
||||
|
||||
if not self.is_friendly(for_player):
|
||||
yield FlightType.DEAD
|
||||
yield from super().mission_types(for_player)
|
||||
@ -355,8 +397,14 @@ class SamGroundObject(BaseDefenseGroundObject):
|
||||
|
||||
|
||||
class VehicleGroupGroundObject(BaseDefenseGroundObject):
|
||||
def __init__(self, name: str, group_id: int, position: Point,
|
||||
control_point: ControlPoint, for_airbase: bool) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
group_id: int,
|
||||
position: Point,
|
||||
control_point: ControlPoint,
|
||||
for_airbase: bool,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
name=name,
|
||||
category="aa",
|
||||
@ -366,13 +414,14 @@ class VehicleGroupGroundObject(BaseDefenseGroundObject):
|
||||
control_point=control_point,
|
||||
dcs_identifier="AA",
|
||||
airbase_group=for_airbase,
|
||||
sea_object=False
|
||||
sea_object=False,
|
||||
)
|
||||
|
||||
|
||||
class EwrGroundObject(BaseDefenseGroundObject):
|
||||
def __init__(self, name: str, group_id: int, position: Point,
|
||||
control_point: ControlPoint) -> None:
|
||||
def __init__(
|
||||
self, name: str, group_id: int, position: Point, control_point: ControlPoint
|
||||
) -> None:
|
||||
super().__init__(
|
||||
name=name,
|
||||
category="EWR",
|
||||
@ -382,7 +431,7 @@ class EwrGroundObject(BaseDefenseGroundObject):
|
||||
control_point=control_point,
|
||||
dcs_identifier="EWR",
|
||||
airbase_group=True,
|
||||
sea_object=False
|
||||
sea_object=False,
|
||||
)
|
||||
|
||||
@property
|
||||
@ -392,6 +441,7 @@ class EwrGroundObject(BaseDefenseGroundObject):
|
||||
|
||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||
from gen.flights.flight import FlightType
|
||||
|
||||
if not self.is_friendly(for_player):
|
||||
yield FlightType.DEAD
|
||||
yield from super().mission_types(for_player)
|
||||
@ -402,8 +452,9 @@ class EwrGroundObject(BaseDefenseGroundObject):
|
||||
|
||||
|
||||
class ShipGroundObject(NavalGroundObject):
|
||||
def __init__(self, name: str, group_id: int, position: Point,
|
||||
control_point: ControlPoint) -> None:
|
||||
def __init__(
|
||||
self, name: str, group_id: int, position: Point, control_point: ControlPoint
|
||||
) -> None:
|
||||
super().__init__(
|
||||
name=name,
|
||||
category="aa",
|
||||
@ -413,7 +464,7 @@ class ShipGroundObject(NavalGroundObject):
|
||||
control_point=control_point,
|
||||
dcs_identifier="AA",
|
||||
airbase_group=False,
|
||||
sea_object=True
|
||||
sea_object=True,
|
||||
)
|
||||
|
||||
@property
|
||||
|
||||
@ -32,8 +32,9 @@ class ThreatZones:
|
||||
self.all = unary_union([airbases, air_defenses])
|
||||
|
||||
def closest_boundary(self, point: DcsPoint) -> DcsPoint:
|
||||
boundary, _ = nearest_points(self.all.boundary,
|
||||
self.dcs_to_shapely_point(point))
|
||||
boundary, _ = nearest_points(
|
||||
self.all.boundary, self.dcs_to_shapely_point(point)
|
||||
)
|
||||
return DcsPoint(boundary.x, boundary.y)
|
||||
|
||||
@singledispatchmethod
|
||||
@ -49,8 +50,9 @@ class ThreatZones:
|
||||
return self.all.intersects(self.dcs_to_shapely_point(position))
|
||||
|
||||
def path_threatened(self, a: DcsPoint, b: DcsPoint) -> bool:
|
||||
return self.threatened(LineString(
|
||||
[self.dcs_to_shapely_point(a), self.dcs_to_shapely_point(b)]))
|
||||
return self.threatened(
|
||||
LineString([self.dcs_to_shapely_point(a), self.dcs_to_shapely_point(b)])
|
||||
)
|
||||
|
||||
@singledispatchmethod
|
||||
def threatened_by_aircraft(self, target) -> bool:
|
||||
@ -62,9 +64,9 @@ class ThreatZones:
|
||||
|
||||
@threatened_by_aircraft.register
|
||||
def _threatened_by_aircraft_flight(self, flight: Flight) -> bool:
|
||||
return self.threatened_by_aircraft(LineString((
|
||||
self.dcs_to_shapely_point(p.position) for p in flight.points
|
||||
)))
|
||||
return self.threatened_by_aircraft(
|
||||
LineString((self.dcs_to_shapely_point(p.position) for p in flight.points))
|
||||
)
|
||||
|
||||
@singledispatchmethod
|
||||
def threatened_by_air_defense(self, target) -> bool:
|
||||
@ -76,13 +78,14 @@ class ThreatZones:
|
||||
|
||||
@threatened_by_air_defense.register
|
||||
def _threatened_by_air_defense_flight(self, flight: Flight) -> bool:
|
||||
return self.threatened_by_air_defense(LineString((
|
||||
self.dcs_to_shapely_point(p.position) for p in flight.points
|
||||
)))
|
||||
return self.threatened_by_air_defense(
|
||||
LineString((self.dcs_to_shapely_point(p.position) for p in flight.points))
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def closest_enemy_airbase(cls, location: ControlPoint,
|
||||
max_distance: Distance) -> Optional[ControlPoint]:
|
||||
def closest_enemy_airbase(
|
||||
cls, location: ControlPoint, max_distance: Distance
|
||||
) -> Optional[ControlPoint]:
|
||||
airfields = ObjectiveDistanceCache.get_closest_airfields(location)
|
||||
for airfield in airfields.airfields_within(max_distance):
|
||||
if airfield.captured != location.captured:
|
||||
@ -90,13 +93,14 @@ class ThreatZones:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def barcap_threat_range(cls, game: Game,
|
||||
control_point: ControlPoint) -> Distance:
|
||||
def barcap_threat_range(cls, game: Game, control_point: ControlPoint) -> Distance:
|
||||
doctrine = game.faction_for(control_point.captured).doctrine
|
||||
cap_threat_range = (doctrine.cap_max_distance_from_cp +
|
||||
doctrine.cap_engagement_range)
|
||||
opposing_airfield = cls.closest_enemy_airbase(control_point,
|
||||
cap_threat_range * 2)
|
||||
cap_threat_range = (
|
||||
doctrine.cap_max_distance_from_cp + doctrine.cap_engagement_range
|
||||
)
|
||||
opposing_airfield = cls.closest_enemy_airbase(
|
||||
control_point, cap_threat_range * 2
|
||||
)
|
||||
if opposing_airfield is None:
|
||||
return cap_threat_range
|
||||
|
||||
@ -133,8 +137,7 @@ class ThreatZones:
|
||||
if control_point.captured != player:
|
||||
continue
|
||||
if control_point.runway_is_operational():
|
||||
point = ShapelyPoint(control_point.position.x,
|
||||
control_point.position.y)
|
||||
point = ShapelyPoint(control_point.position.x, control_point.position.y)
|
||||
cap_threat_range = cls.barcap_threat_range(game, control_point)
|
||||
airbases.append(point.buffer(cap_threat_range.meters))
|
||||
|
||||
@ -149,10 +152,9 @@ class ThreatZones:
|
||||
air_defenses.append(threat_zone)
|
||||
|
||||
return cls(
|
||||
airbases=unary_union(airbases),
|
||||
air_defenses=unary_union(air_defenses)
|
||||
airbases=unary_union(airbases), air_defenses=unary_union(air_defenses)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def dcs_to_shapely_point(point: DcsPoint) -> ShapelyPoint:
|
||||
return ShapelyPoint(point.x, point.y)
|
||||
return ShapelyPoint(point.x, point.y)
|
||||
|
||||
@ -70,15 +70,19 @@ class UnitMap:
|
||||
raise RuntimeError(f"Unknown unit type: {unit.type}")
|
||||
if not issubclass(unit_type, VehicleType):
|
||||
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)
|
||||
|
||||
def front_line_unit(self, name: str) -> Optional[FrontLineUnit]:
|
||||
return self.front_line_units.get(name, None)
|
||||
|
||||
def add_ground_object_units(self, ground_object: TheaterGroundObject,
|
||||
persistence_group: Group,
|
||||
miz_group: Group) -> None:
|
||||
def add_ground_object_units(
|
||||
self,
|
||||
ground_object: TheaterGroundObject,
|
||||
persistence_group: Group,
|
||||
miz_group: Group,
|
||||
) -> None:
|
||||
"""Adds a group associated with a TGO to the unit map.
|
||||
|
||||
Args:
|
||||
@ -103,13 +107,13 @@ class UnitMap:
|
||||
if name in self.ground_object_units:
|
||||
raise RuntimeError(f"Duplicate TGO unit: {name}")
|
||||
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]:
|
||||
return self.ground_object_units.get(name, None)
|
||||
|
||||
def add_building(self, ground_object: BuildingGroundObject,
|
||||
group: Group) -> None:
|
||||
def add_building(self, ground_object: BuildingGroundObject, group: Group) -> None:
|
||||
# The actual name is a String (the pydcs translatable string), which
|
||||
# doesn't define __eq__.
|
||||
# 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}")
|
||||
self.buildings[name] = Building(ground_object)
|
||||
|
||||
def add_fortification(self, ground_object: BuildingGroundObject,
|
||||
group: VehicleGroup) -> None:
|
||||
def add_fortification(
|
||||
self, ground_object: BuildingGroundObject, group: VehicleGroup
|
||||
) -> None:
|
||||
if len(group.units) != 1:
|
||||
raise ValueError("Fortification groups must have exactly one unit.")
|
||||
unit = group.units[0]
|
||||
|
||||
@ -58,7 +58,7 @@ class Weather:
|
||||
return None
|
||||
return Fog(
|
||||
visibility=meters(random.randint(2500, 5000)),
|
||||
thickness=random.randint(100, 500)
|
||||
thickness=random.randint(100, 500),
|
||||
)
|
||||
|
||||
def generate_wind(self) -> WindConditions:
|
||||
@ -76,7 +76,7 @@ class Weather:
|
||||
# Always some wind to make the smoke move a bit.
|
||||
at_0m=Wind(wind_direction, max(1, base_wind * at_0m_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
|
||||
@ -105,7 +105,7 @@ class Cloudy(Weather):
|
||||
base=self.random_cloud_base(),
|
||||
density=random.randint(1, 8),
|
||||
thickness=self.random_cloud_thickness(),
|
||||
precipitation=PydcsWeather.Preceptions.None_
|
||||
precipitation=PydcsWeather.Preceptions.None_,
|
||||
)
|
||||
|
||||
def generate_wind(self) -> WindConditions:
|
||||
@ -118,7 +118,7 @@ class Raining(Weather):
|
||||
base=self.random_cloud_base(),
|
||||
density=random.randint(5, 8),
|
||||
thickness=self.random_cloud_thickness(),
|
||||
precipitation=PydcsWeather.Preceptions.Rain
|
||||
precipitation=PydcsWeather.Preceptions.Rain,
|
||||
)
|
||||
|
||||
def generate_wind(self) -> WindConditions:
|
||||
@ -131,7 +131,7 @@ class Thunderstorm(Weather):
|
||||
base=self.random_cloud_base(),
|
||||
density=random.randint(9, 10),
|
||||
thickness=self.random_cloud_thickness(),
|
||||
precipitation=PydcsWeather.Preceptions.Thunderstorm
|
||||
precipitation=PydcsWeather.Preceptions.Thunderstorm,
|
||||
)
|
||||
|
||||
def generate_wind(self) -> WindConditions:
|
||||
@ -145,20 +145,29 @@ class Conditions:
|
||||
weather: Weather
|
||||
|
||||
@classmethod
|
||||
def generate(cls, theater: ConflictTheater, day: datetime.date,
|
||||
time_of_day: TimeOfDay, settings: Settings) -> Conditions:
|
||||
def generate(
|
||||
cls,
|
||||
theater: ConflictTheater,
|
||||
day: datetime.date,
|
||||
time_of_day: TimeOfDay,
|
||||
settings: Settings,
|
||||
) -> Conditions:
|
||||
return cls(
|
||||
time_of_day=time_of_day,
|
||||
start_time=cls.generate_start_time(
|
||||
theater, day, time_of_day, settings.night_disabled
|
||||
),
|
||||
weather=cls.generate_weather()
|
||||
weather=cls.generate_weather(),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def generate_start_time(cls, theater: ConflictTheater, day: datetime.date,
|
||||
time_of_day: TimeOfDay,
|
||||
night_disabled: bool) -> datetime.datetime:
|
||||
def generate_start_time(
|
||||
cls,
|
||||
theater: ConflictTheater,
|
||||
day: datetime.date,
|
||||
time_of_day: TimeOfDay,
|
||||
night_disabled: bool,
|
||||
) -> datetime.datetime:
|
||||
if night_disabled:
|
||||
logging.info("Skip Night mission due to user settings")
|
||||
time_range = {
|
||||
@ -181,6 +190,7 @@ class Conditions:
|
||||
Cloudy: 60,
|
||||
ClearSkies: 20,
|
||||
}
|
||||
weather_type = random.choices(list(chances.keys()),
|
||||
weights=list(chances.values()))[0]
|
||||
weather_type = random.choices(
|
||||
list(chances.keys()), weights=list(chances.values())
|
||||
)[0]
|
||||
return weather_type()
|
||||
|
||||
784
gen/aircraft.py
784
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
@ -32,6 +32,7 @@ AWACS_ALT = 13000
|
||||
@dataclass
|
||||
class AwacsInfo:
|
||||
"""AWACS information for the kneeboard."""
|
||||
|
||||
dcsGroupName: str
|
||||
callsign: str
|
||||
freq: RadioFrequency
|
||||
@ -40,6 +41,7 @@ class AwacsInfo:
|
||||
@dataclass
|
||||
class TankerInfo:
|
||||
"""Tanker information for the kneeboard."""
|
||||
|
||||
dcsGroupName: str
|
||||
callsign: str
|
||||
variant: str
|
||||
@ -54,10 +56,14 @@ class AirSupport:
|
||||
|
||||
|
||||
class AirSupportConflictGenerator:
|
||||
|
||||
def __init__(self, mission: Mission, conflict: Conflict, game,
|
||||
radio_registry: RadioRegistry,
|
||||
tacan_registry: TacanRegistry) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
mission: Mission,
|
||||
conflict: Conflict,
|
||||
game,
|
||||
radio_registry: RadioRegistry,
|
||||
tacan_registry: TacanRegistry,
|
||||
) -> None:
|
||||
self.mission = mission
|
||||
self.conflict = conflict
|
||||
self.game = game
|
||||
@ -78,22 +84,37 @@ class AirSupportConflictGenerator:
|
||||
elif unit_type is KC135MPRS:
|
||||
return (TANKER_ALT + 500, 596)
|
||||
return (TANKER_ALT, 574)
|
||||
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
variant = db.unit_type_name(tanker_unit_type)
|
||||
variant = db.unit_type_name(tanker_unit_type)
|
||||
freq = self.radio_registry.alloc_uhf()
|
||||
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_position = player_cp.position.point_from_heading(tanker_heading, TANKER_DISTANCE)
|
||||
tanker_heading = (
|
||||
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(
|
||||
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,
|
||||
plane_type=tanker_unit_type,
|
||||
position=tanker_position,
|
||||
@ -124,28 +145,41 @@ class AirSupportConflictGenerator:
|
||||
if tanker_unit_type != IL_78M:
|
||||
# Override PyDCS tacan channel.
|
||||
tanker_group.points[0].tasks.pop()
|
||||
tanker_group.points[0].tasks.append(ActivateBeaconCommand(
|
||||
tacan.number, tacan.band.value, tacan_callsign, True,
|
||||
tanker_group.units[0].id, True))
|
||||
tanker_group.points[0].tasks.append(
|
||||
ActivateBeaconCommand(
|
||||
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(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)
|
||||
)
|
||||
|
||||
possible_awacs = db.find_unittype(AWACS, self.conflict.attackers_side)
|
||||
|
||||
if len(possible_awacs) > 0:
|
||||
awacs_unit = possible_awacs[0]
|
||||
freq = self.radio_registry.alloc_uhf()
|
||||
|
||||
|
||||
awacs_flight = self.mission.awacs_flight(
|
||||
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,
|
||||
altitude=AWACS_ALT,
|
||||
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,
|
||||
start_type=StartType.Warm,
|
||||
)
|
||||
@ -154,7 +188,12 @@ class AirSupportConflictGenerator:
|
||||
awacs_flight.points[0].tasks.append(SetInvisibleCommand(True))
|
||||
awacs_flight.points[0].tasks.append(SetImmortalCommand(True))
|
||||
|
||||
self.air_support.awacs.append(AwacsInfo(
|
||||
str(awacs_flight.name), callsign_for_support_unit(awacs_flight), freq))
|
||||
self.air_support.awacs.append(
|
||||
AwacsInfo(
|
||||
str(awacs_flight.name),
|
||||
callsign_for_support_unit(awacs_flight),
|
||||
freq,
|
||||
)
|
||||
)
|
||||
else:
|
||||
logging.warning("No AWACS for faction")
|
||||
logging.warning("No AWACS for faction")
|
||||
|
||||
362
gen/armor.py
362
gen/armor.py
@ -12,9 +12,17 @@ from dcs.country import Country
|
||||
from dcs.mapping import Point
|
||||
from dcs.planes import MQ_9_Reaper
|
||||
from dcs.point import PointAction
|
||||
from dcs.task import (EPLRS, AttackGroup, ControlledTask, FireAtPoint,
|
||||
GoToWaypoint, Hold, OrbitAction, SetImmortalCommand,
|
||||
SetInvisibleCommand)
|
||||
from dcs.task import (
|
||||
EPLRS,
|
||||
AttackGroup,
|
||||
ControlledTask,
|
||||
FireAtPoint,
|
||||
GoToWaypoint,
|
||||
Hold,
|
||||
OrbitAction,
|
||||
SetImmortalCommand,
|
||||
SetInvisibleCommand,
|
||||
)
|
||||
from dcs.triggers import Event, TriggerOnce
|
||||
from dcs.unit import Vehicle
|
||||
from dcs.unitgroup import VehicleGroup
|
||||
@ -24,8 +32,11 @@ from game.unitmap import UnitMap
|
||||
from game.utils import heading_sum, opposite_heading
|
||||
from game.theater.controlpoint import ControlPoint
|
||||
|
||||
from gen.ground_forces.ai_ground_planner import (DISTANCE_FROM_FRONTLINE,
|
||||
CombatGroup, CombatGroupRole)
|
||||
from gen.ground_forces.ai_ground_planner import (
|
||||
DISTANCE_FROM_FRONTLINE,
|
||||
CombatGroup,
|
||||
CombatGroupRole,
|
||||
)
|
||||
|
||||
from .callsigns import callsign_for_support_unit
|
||||
from .conflictgen import Conflict
|
||||
@ -56,6 +67,7 @@ INFANTRY_GROUP_SIZE = 5
|
||||
@dataclass(frozen=True)
|
||||
class JtacInfo:
|
||||
"""JTAC information."""
|
||||
|
||||
dcsGroupName: str
|
||||
unit_name: str
|
||||
callsign: str
|
||||
@ -65,16 +77,16 @@ class JtacInfo:
|
||||
|
||||
|
||||
class GroundConflictGenerator:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mission: Mission,
|
||||
conflict: Conflict,
|
||||
game: Game,
|
||||
player_planned_combat_groups: List[CombatGroup],
|
||||
enemy_planned_combat_groups: List[CombatGroup],
|
||||
player_stance: CombatStance,
|
||||
unit_map: UnitMap) -> None:
|
||||
self,
|
||||
mission: Mission,
|
||||
conflict: Conflict,
|
||||
game: Game,
|
||||
player_planned_combat_groups: List[CombatGroup],
|
||||
enemy_planned_combat_groups: List[CombatGroup],
|
||||
player_stance: CombatStance,
|
||||
unit_map: UnitMap,
|
||||
) -> None:
|
||||
self.mission = mission
|
||||
self.conflict = conflict
|
||||
self.enemy_planned_combat_groups = enemy_planned_combat_groups
|
||||
@ -87,14 +99,16 @@ class GroundConflictGenerator:
|
||||
|
||||
def _enemy_stance(self):
|
||||
"""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(
|
||||
[
|
||||
CombatStance.AGGRESSIVE,
|
||||
CombatStance.AGGRESSIVE,
|
||||
CombatStance.AGGRESSIVE,
|
||||
CombatStance.ELIMINATION,
|
||||
CombatStance.BREAKTHROUGH
|
||||
CombatStance.BREAKTHROUGH,
|
||||
]
|
||||
)
|
||||
else:
|
||||
@ -104,31 +118,37 @@ class GroundConflictGenerator:
|
||||
CombatStance.DEFENSIVE,
|
||||
CombatStance.DEFENSIVE,
|
||||
CombatStance.AMBUSH,
|
||||
CombatStance.AGGRESSIVE
|
||||
CombatStance.AGGRESSIVE,
|
||||
]
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _group_point(point: Point, base_distance) -> Point:
|
||||
distance = random.randint(
|
||||
int(base_distance * SPREAD_DISTANCE_FACTOR[0]),
|
||||
int(base_distance * SPREAD_DISTANCE_FACTOR[1]),
|
||||
)
|
||||
return point.random_point_within(distance, base_distance * SPREAD_DISTANCE_SIZE_FACTOR)
|
||||
int(base_distance * SPREAD_DISTANCE_FACTOR[0]),
|
||||
int(base_distance * SPREAD_DISTANCE_FACTOR[1]),
|
||||
)
|
||||
return point.random_point_within(
|
||||
distance, base_distance * SPREAD_DISTANCE_SIZE_FACTOR
|
||||
)
|
||||
|
||||
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(
|
||||
self.conflict.from_cp,
|
||||
self.conflict.to_cp,
|
||||
self.game.theater
|
||||
)
|
||||
self.conflict.from_cp, self.conflict.to_cp, self.game.theater
|
||||
)
|
||||
|
||||
# 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
|
||||
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
|
||||
self.plan_action_for_groups(
|
||||
@ -137,7 +157,7 @@ class GroundConflictGenerator:
|
||||
enemy_groups,
|
||||
self.conflict.heading + 90,
|
||||
self.conflict.from_cp,
|
||||
self.conflict.to_cp
|
||||
self.conflict.to_cp,
|
||||
)
|
||||
self.plan_action_for_groups(
|
||||
self.enemy_stance,
|
||||
@ -145,7 +165,7 @@ class GroundConflictGenerator:
|
||||
player_groups,
|
||||
self.conflict.heading - 90,
|
||||
self.conflict.to_cp,
|
||||
self.conflict.from_cp
|
||||
self.conflict.from_cp,
|
||||
)
|
||||
|
||||
# Add JTAC
|
||||
@ -157,34 +177,38 @@ class GroundConflictGenerator:
|
||||
if self.game.player_faction.jtac_unit is not None:
|
||||
utype = self.game.player_faction.jtac_unit
|
||||
|
||||
jtac = self.mission.flight_group(country=self.mission.country(self.game.player_country),
|
||||
name=n,
|
||||
aircraft_type=utype,
|
||||
position=position[0],
|
||||
airport=None,
|
||||
altitude=5000)
|
||||
jtac = self.mission.flight_group(
|
||||
country=self.mission.country(self.game.player_country),
|
||||
name=n,
|
||||
aircraft_type=utype,
|
||||
position=position[0],
|
||||
airport=None,
|
||||
altitude=5000,
|
||||
)
|
||||
jtac.points[0].tasks.append(SetInvisibleCommand(True))
|
||||
jtac.points[0].tasks.append(SetImmortalCommand(True))
|
||||
jtac.points[0].tasks.append(OrbitAction(5000, 300, OrbitAction.OrbitPattern.Circle))
|
||||
frontline = f"Frontline {self.conflict.from_cp.name}/{self.conflict.to_cp.name}"
|
||||
jtac.points[0].tasks.append(
|
||||
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.
|
||||
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(
|
||||
self,
|
||||
group: VehicleGroup,
|
||||
is_player: bool,
|
||||
side: Country,
|
||||
forward_heading: int
|
||||
self, group: VehicleGroup, is_player: bool, side: Country, forward_heading: int
|
||||
) -> None:
|
||||
|
||||
infantry_position = self.conflict.find_ground_position(
|
||||
group.points[0].position.random_point_within(250, 50),
|
||||
500,
|
||||
forward_heading,
|
||||
self.conflict.theater
|
||||
)
|
||||
self.conflict.theater,
|
||||
)
|
||||
if not infantry_position:
|
||||
logging.warning("Could not find infantry position")
|
||||
return
|
||||
@ -208,44 +232,50 @@ class GroundConflictGenerator:
|
||||
u = random.choice(manpads)
|
||||
self.mission.vehicle_group(
|
||||
side,
|
||||
namegen.next_infantry_name(side, cp.id, u), u,
|
||||
namegen.next_infantry_name(side, cp.id, u),
|
||||
u,
|
||||
position=infantry_position,
|
||||
group_size=1,
|
||||
heading=forward_heading,
|
||||
move_formation=PointAction.OffRoad)
|
||||
move_formation=PointAction.OffRoad,
|
||||
)
|
||||
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:
|
||||
return
|
||||
|
||||
u = random.choice(possible_infantry_units)
|
||||
self.mission.vehicle_group(
|
||||
side,
|
||||
namegen.next_infantry_name(side, cp.id, u), u,
|
||||
position=infantry_position,
|
||||
group_size=1,
|
||||
heading=forward_heading,
|
||||
move_formation=PointAction.OffRoad)
|
||||
side,
|
||||
namegen.next_infantry_name(side, cp.id, u),
|
||||
u,
|
||||
position=infantry_position,
|
||||
group_size=1,
|
||||
heading=forward_heading,
|
||||
move_formation=PointAction.OffRoad,
|
||||
)
|
||||
|
||||
for i in range(INFANTRY_GROUP_SIZE):
|
||||
u = random.choice(possible_infantry_units)
|
||||
position = infantry_position.random_point_within(55, 5)
|
||||
self.mission.vehicle_group(
|
||||
side,
|
||||
namegen.next_infantry_name(side, cp.id, u), u,
|
||||
namegen.next_infantry_name(side, cp.id, u),
|
||||
u,
|
||||
position=position,
|
||||
group_size=1,
|
||||
heading=forward_heading,
|
||||
move_formation=PointAction.OffRoad)
|
||||
move_formation=PointAction.OffRoad,
|
||||
)
|
||||
|
||||
def _set_reform_waypoint(
|
||||
self,
|
||||
dcs_group: VehicleGroup,
|
||||
forward_heading: int
|
||||
self, dcs_group: VehicleGroup, forward_heading: int
|
||||
) -> None:
|
||||
"""Setting a waypoint close to the spawn position allows the group to reform gracefully
|
||||
rather than spin
|
||||
rather than spin
|
||||
"""
|
||||
reform_point = dcs_group.position.point_from_heading(forward_heading, 50)
|
||||
dcs_group.add_waypoint(reform_point)
|
||||
@ -256,7 +286,7 @@ class GroundConflictGenerator:
|
||||
gen_group: CombatGroup,
|
||||
dcs_group: VehicleGroup,
|
||||
forward_heading: int,
|
||||
target: Point
|
||||
target: Point,
|
||||
) -> bool:
|
||||
"""
|
||||
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)
|
||||
|
||||
# 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))
|
||||
# TODO: Update to fire at group instead of point
|
||||
fire_task = FireAtPoint(target, len(gen_group.units) * 10, 100)
|
||||
@ -283,12 +315,19 @@ class GroundConflictGenerator:
|
||||
|
||||
# Hold position
|
||||
dcs_group.points[1].tasks.append(Hold())
|
||||
retreat = self.find_retreat_point(dcs_group, forward_heading, (int)(RETREAT_DISTANCE/3))
|
||||
dcs_group.add_waypoint(dcs_group.position.point_from_heading(forward_heading, 1), PointAction.OffRoad)
|
||||
retreat = self.find_retreat_point(
|
||||
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.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):
|
||||
artillery_fallback.add_condition(UnitDamaged(u.id))
|
||||
if i < len(dcs_group.units) - 1:
|
||||
@ -302,7 +341,9 @@ class GroundConflictGenerator:
|
||||
retreat_task.number = 4
|
||||
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)
|
||||
|
||||
for u in dcs_group.units:
|
||||
@ -330,12 +371,8 @@ class GroundConflictGenerator:
|
||||
target = self.find_nearest_enemy_group(dcs_group, enemy_groups)
|
||||
if target is not None:
|
||||
rand_offset = Point(
|
||||
random.randint(
|
||||
-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK
|
||||
),
|
||||
random.randint(
|
||||
-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.points[0].position + rand_offset
|
||||
@ -345,8 +382,7 @@ class GroundConflictGenerator:
|
||||
|
||||
if (
|
||||
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(
|
||||
to_cp.position.random_point_within(500, 0)
|
||||
@ -358,16 +394,16 @@ class GroundConflictGenerator:
|
||||
if offset_heading < 0:
|
||||
offset_heading = 358
|
||||
attack_point = self.find_offensive_point(
|
||||
dcs_group,
|
||||
offset_heading,
|
||||
AGGRESIVE_MOVE_DISTANCE
|
||||
dcs_group, offset_heading, AGGRESIVE_MOVE_DISTANCE
|
||||
)
|
||||
dcs_group.add_waypoint(attack_point, PointAction.OffRoad)
|
||||
elif stance == CombatStance.BREAKTHROUGH:
|
||||
# In breakthrough mode, the units will move forward
|
||||
# If the enemy base is close enough, the units will attack the base
|
||||
if to_cp.position.distance_to_point(
|
||||
dcs_group.points[0].position) <= BREAKTHROUGH_OFFENSIVE_DISTANCE:
|
||||
if (
|
||||
to_cp.position.distance_to_point(dcs_group.points[0].position)
|
||||
<= BREAKTHROUGH_OFFENSIVE_DISTANCE
|
||||
):
|
||||
attack_point = self.conflict.theater.nearest_land_pos(
|
||||
to_cp.position.random_point_within(500, 0)
|
||||
)
|
||||
@ -377,27 +413,27 @@ class GroundConflictGenerator:
|
||||
offset_heading = forward_heading - 1
|
||||
if offset_heading < 0:
|
||||
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)
|
||||
elif stance == CombatStance.ELIMINATION:
|
||||
# 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)
|
||||
for i, target in enumerate(targets, start=1):
|
||||
rand_offset = Point(
|
||||
random.randint(
|
||||
-RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK
|
||||
),
|
||||
random.randint(
|
||||
-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.points[0].position+rand_offset
|
||||
target.points[0].position + rand_offset
|
||||
)
|
||||
dcs_group.add_waypoint(target_point, PointAction.OffRoad)
|
||||
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(
|
||||
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.
|
||||
"""
|
||||
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
|
||||
if 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))
|
||||
if (
|
||||
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:
|
||||
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)
|
||||
|
||||
if stance != CombatStance.RETREAT:
|
||||
@ -434,29 +481,36 @@ class GroundConflictGenerator:
|
||||
return False
|
||||
|
||||
def plan_action_for_groups(
|
||||
self, stance: CombatStance,
|
||||
self,
|
||||
stance: CombatStance,
|
||||
ally_groups: List[Tuple[VehicleGroup, CombatGroup]],
|
||||
enemy_groups: List[Tuple[VehicleGroup, CombatGroup]],
|
||||
forward_heading: int,
|
||||
from_cp: ControlPoint,
|
||||
to_cp: ControlPoint
|
||||
to_cp: ControlPoint,
|
||||
) -> None:
|
||||
|
||||
if not self.game.settings.perf_moving_units:
|
||||
return
|
||||
|
||||
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))
|
||||
|
||||
if group.role == CombatGroupRole.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:
|
||||
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]:
|
||||
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]:
|
||||
self._plan_apc_atgm_action(stance, dcs_group, forward_heading, to_cp)
|
||||
@ -464,11 +518,16 @@ class GroundConflictGenerator:
|
||||
if stance == CombatStance.RETREAT:
|
||||
# In retreat mode, the units will fall back
|
||||
# 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)
|
||||
else:
|
||||
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(reposition_point, PointAction.OffRoad)
|
||||
|
||||
@ -490,8 +549,10 @@ class GroundConflictGenerator:
|
||||
|
||||
# We add a new retreat waypoint
|
||||
dcs_group.add_waypoint(
|
||||
self.find_retreat_point(dcs_group, forward_heading, (int)(RETREAT_DISTANCE / 8)),
|
||||
PointAction.OffRoad
|
||||
self.find_retreat_point(
|
||||
dcs_group, forward_heading, (int)(RETREAT_DISTANCE / 8)
|
||||
),
|
||||
PointAction.OffRoad,
|
||||
)
|
||||
|
||||
# Fallback task
|
||||
@ -515,7 +576,7 @@ class GroundConflictGenerator:
|
||||
self,
|
||||
dcs_group: VehicleGroup,
|
||||
frontline_heading: int,
|
||||
distance: int = RETREAT_DISTANCE
|
||||
distance: int = RETREAT_DISTANCE,
|
||||
) -> Point:
|
||||
"""
|
||||
Find a point to retreat to
|
||||
@ -523,17 +584,15 @@ class GroundConflictGenerator:
|
||||
:param frontline_heading: Heading of the frontline
|
||||
: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):
|
||||
return desired_point
|
||||
return self.conflict.theater.nearest_land_pos(desired_point)
|
||||
|
||||
|
||||
def find_offensive_point(
|
||||
self,
|
||||
dcs_group: VehicleGroup,
|
||||
frontline_heading: int,
|
||||
distance: int
|
||||
self, dcs_group: VehicleGroup, frontline_heading: int, distance: int
|
||||
) -> Point:
|
||||
"""
|
||||
Find a point to attack
|
||||
@ -542,7 +601,9 @@ class GroundConflictGenerator:
|
||||
:param distance: Distance of the offensive (how far unit should move)
|
||||
: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):
|
||||
return desired_point
|
||||
return self.conflict.theater.nearest_land_pos(desired_point)
|
||||
@ -551,7 +612,7 @@ class GroundConflictGenerator:
|
||||
def find_n_nearest_enemy_groups(
|
||||
player_group: VehicleGroup,
|
||||
enemy_groups: List[Tuple[VehicleGroup, CombatGroup]],
|
||||
n: int
|
||||
n: int,
|
||||
) -> List[VehicleGroup]:
|
||||
"""
|
||||
Return the nearest enemy group for the player group
|
||||
@ -562,7 +623,9 @@ class GroundConflictGenerator:
|
||||
targets = [] # type: List[Optional[VehicleGroup]]
|
||||
sorted_list = sorted(
|
||||
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):
|
||||
# TODO: Is this supposed to return no groups if enemy_groups is less than n?
|
||||
@ -574,8 +637,7 @@ class GroundConflictGenerator:
|
||||
|
||||
@staticmethod
|
||||
def find_nearest_enemy_group(
|
||||
player_group: VehicleGroup,
|
||||
enemy_groups: List[Tuple[VehicleGroup, CombatGroup]]
|
||||
player_group: VehicleGroup, enemy_groups: List[Tuple[VehicleGroup, CombatGroup]]
|
||||
) -> Optional[VehicleGroup]:
|
||||
"""
|
||||
Search the enemy groups for a potential target suitable to armored assault
|
||||
@ -585,7 +647,9 @@ class GroundConflictGenerator:
|
||||
min_distance = 99999999
|
||||
target = None
|
||||
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:
|
||||
min_distance = dist
|
||||
target = dcs_group
|
||||
@ -595,7 +659,7 @@ class GroundConflictGenerator:
|
||||
def get_artillery_target_in_range(
|
||||
dcs_group: VehicleGroup,
|
||||
group: CombatGroup,
|
||||
enemy_groups: List[Tuple[VehicleGroup, CombatGroup]]
|
||||
enemy_groups: List[Tuple[VehicleGroup, CombatGroup]],
|
||||
) -> Optional[Point]:
|
||||
"""
|
||||
Search the enemy groups for a potential target suitable to an artillery unit
|
||||
@ -606,7 +670,9 @@ class GroundConflictGenerator:
|
||||
return None
|
||||
for _ in range(10):
|
||||
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:
|
||||
return potential_target.points[0].position
|
||||
return None
|
||||
@ -620,12 +686,12 @@ class GroundConflictGenerator:
|
||||
if rg > DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][1]:
|
||||
rg = random.randint(
|
||||
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]:
|
||||
rg = random.randint(
|
||||
DISTANCE_FROM_FRONTLINE[CombatGroupRole.TANK][0],
|
||||
DISTANCE_FROM_FRONTLINE[CombatGroupRole.TANK][1]
|
||||
DISTANCE_FROM_FRONTLINE[CombatGroupRole.TANK][1],
|
||||
)
|
||||
return rg
|
||||
|
||||
@ -635,42 +701,46 @@ class GroundConflictGenerator:
|
||||
combat_width: int,
|
||||
distance_from_frontline: int,
|
||||
heading: int,
|
||||
spawn_heading: int
|
||||
spawn_heading: int,
|
||||
):
|
||||
shifted = conflict_position.point_from_heading(heading, random.randint(0, combat_width))
|
||||
desired_point = shifted.point_from_heading(
|
||||
spawn_heading,
|
||||
distance_from_frontline
|
||||
shifted = conflict_position.point_from_heading(
|
||||
heading, random.randint(0, combat_width)
|
||||
)
|
||||
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(
|
||||
self,
|
||||
groups: List[CombatGroup],
|
||||
frontline_vector: Tuple[Point, int, int],
|
||||
is_player: bool
|
||||
is_player: bool,
|
||||
) -> List[Tuple[VehicleGroup, CombatGroup]]:
|
||||
"""Finds valid positions for planned groups and generates a pydcs group for them"""
|
||||
positioned_groups = []
|
||||
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
|
||||
for group in groups:
|
||||
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:
|
||||
distance_from_frontline = random.randint(
|
||||
DISTANCE_FROM_FRONTLINE[group.role][0],
|
||||
DISTANCE_FROM_FRONTLINE[group.role][1]
|
||||
DISTANCE_FROM_FRONTLINE[group.role][0],
|
||||
DISTANCE_FROM_FRONTLINE[group.role][1],
|
||||
)
|
||||
|
||||
final_position = self.get_valid_position_for_group(
|
||||
position,
|
||||
combat_width,
|
||||
distance_from_frontline,
|
||||
heading,
|
||||
spawn_heading
|
||||
position, combat_width, distance_from_frontline, heading, spawn_heading
|
||||
)
|
||||
|
||||
if final_position is not None:
|
||||
@ -693,7 +763,7 @@ class GroundConflictGenerator:
|
||||
g,
|
||||
is_player,
|
||||
self.mission.country(country),
|
||||
opposite_heading(spawn_heading)
|
||||
opposite_heading(spawn_heading),
|
||||
)
|
||||
else:
|
||||
logging.warning(f"Unable to get valid position for {group}")
|
||||
@ -718,12 +788,14 @@ class GroundConflictGenerator:
|
||||
|
||||
logging.info("armorgen: {} for {}".format(unit, side.id))
|
||||
group = self.mission.vehicle_group(
|
||||
side,
|
||||
namegen.next_unit_name(side, cp.id, unit), unit,
|
||||
position=at,
|
||||
group_size=count,
|
||||
heading=heading,
|
||||
move_formation=move_formation)
|
||||
side,
|
||||
namegen.next_unit_name(side, cp.id, unit),
|
||||
unit,
|
||||
position=at,
|
||||
group_size=count,
|
||||
heading=heading,
|
||||
move_formation=move_formation,
|
||||
)
|
||||
|
||||
self.unit_map.add_front_line_units(group, cp)
|
||||
|
||||
|
||||
@ -96,7 +96,8 @@ class Package:
|
||||
if tot is None:
|
||||
logging.error(
|
||||
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
|
||||
times.append(tot)
|
||||
if times:
|
||||
@ -117,7 +118,8 @@ class Package:
|
||||
logging.error(
|
||||
f"{flight} dismissed escort at {waypoint} but that "
|
||||
"waypoint has no TOT or departure time. It may not be "
|
||||
"escorted.")
|
||||
"escorted."
|
||||
)
|
||||
continue
|
||||
times.append(tot)
|
||||
if times:
|
||||
|
||||
@ -23,9 +23,11 @@ from .runways import RunwayData
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
|
||||
|
||||
@dataclass
|
||||
class CommInfo:
|
||||
"""Communications information for the kneeboard."""
|
||||
|
||||
name: str
|
||||
freq: RadioFrequency
|
||||
|
||||
@ -37,10 +39,13 @@ class FrontLineInfo:
|
||||
self.enemy_base: ControlPoint = front_line.control_point_b
|
||||
self.player_zero: bool = self.player_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.combat_stances = CombatStance
|
||||
|
||||
|
||||
class MissionInfoGenerator:
|
||||
"""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):
|
||||
|
||||
def __init__(self, mission: Mission, game: Game):
|
||||
super().__init__(mission, game)
|
||||
self.allied_flights_by_departure: Dict[str, List[FlightData]] = {}
|
||||
@ -141,36 +145,36 @@ class BriefingGenerator(MissionInfoGenerator):
|
||||
disabled_extensions=("",),
|
||||
default_for_string=True,
|
||||
default=True,
|
||||
),
|
||||
),
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True,
|
||||
)
|
||||
)
|
||||
env.filters["waypoint_timing"] = format_waypoint_time
|
||||
self.template = env.get_template("briefingtemplate_EN.j2")
|
||||
|
||||
def generate(self) -> None:
|
||||
"""Generate the mission briefing
|
||||
"""
|
||||
"""Generate the mission briefing"""
|
||||
self._generate_frontline_info()
|
||||
self.generate_allied_flights_by_departure()
|
||||
self.mission.set_description_text(self.template.render(vars(self)))
|
||||
self.mission.add_picture_blue(os.path.abspath(
|
||||
"./resources/ui/splash_screen.png"))
|
||||
self.mission.add_picture_blue(
|
||||
os.path.abspath("./resources/ui/splash_screen.png")
|
||||
)
|
||||
|
||||
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):
|
||||
self.add_frontline(FrontLineInfo(front_line))
|
||||
|
||||
# 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:
|
||||
"""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:
|
||||
if not flight.client_units and flight.friendly:
|
||||
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)
|
||||
else:
|
||||
self.allied_flights_by_departure[name] = [flight]
|
||||
|
||||
@ -12,19 +12,21 @@ from game.utils import heading_sum, opposite_heading
|
||||
|
||||
FRONTLINE_LENGTH = 80000
|
||||
|
||||
|
||||
class Conflict:
|
||||
def __init__(self,
|
||||
theater: ConflictTheater,
|
||||
from_cp: ControlPoint,
|
||||
to_cp: ControlPoint,
|
||||
attackers_side: str,
|
||||
defenders_side: str,
|
||||
attackers_country: Country,
|
||||
defenders_country: Country,
|
||||
position: Point,
|
||||
heading: Optional[int] = None,
|
||||
size: Optional[int] = None
|
||||
):
|
||||
def __init__(
|
||||
self,
|
||||
theater: ConflictTheater,
|
||||
from_cp: ControlPoint,
|
||||
to_cp: ControlPoint,
|
||||
attackers_side: str,
|
||||
defenders_side: str,
|
||||
attackers_country: Country,
|
||||
defenders_country: Country,
|
||||
position: Point,
|
||||
heading: Optional[int] = None,
|
||||
size: Optional[int] = None,
|
||||
):
|
||||
|
||||
self.attackers_side = attackers_side
|
||||
self.defenders_side = defenders_side
|
||||
@ -43,27 +45,49 @@ class Conflict:
|
||||
return from_cp.has_frontline and to_cp.has_frontline
|
||||
|
||||
@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)
|
||||
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)
|
||||
|
||||
@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.
|
||||
"""
|
||||
center_position, heading = cls.frontline_position(from_cp, to_cp, theater)
|
||||
left_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)
|
||||
right_position = cls.extend_ground_position(center_position, int(FRONTLINE_LENGTH / 2), right_heading, theater)
|
||||
left_position = cls.extend_ground_position(
|
||||
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))
|
||||
return left_position, right_heading, distance
|
||||
|
||||
@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)
|
||||
position, heading, distance = cls.frontline_vector(from_cp, to_cp, theater)
|
||||
conflict = cls(
|
||||
@ -76,12 +100,14 @@ class Conflict:
|
||||
defenders_side=defender_name,
|
||||
attackers_country=attacker,
|
||||
defenders_country=defender,
|
||||
size=distance
|
||||
size=distance,
|
||||
)
|
||||
return conflict
|
||||
|
||||
@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"""
|
||||
extended = initial.point_from_heading(heading, max_distance)
|
||||
if theater.landmap is None:
|
||||
@ -92,8 +118,7 @@ class Conflict:
|
||||
p1 = ShapelyPoint(extended.x, extended.y)
|
||||
line = LineString([p0, p1])
|
||||
|
||||
intersection = line.intersection(
|
||||
theater.landmap.inclusion_zone_only.boundary)
|
||||
intersection = line.intersection(theater.landmap.inclusion_zone_only.boundary)
|
||||
if intersection.is_empty:
|
||||
# Max extent does not intersect with the boundary of the inclusion
|
||||
# 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))
|
||||
|
||||
@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.
|
||||
`coerce=True` will return the closest land position to `initial` regardless of heading or distance
|
||||
@ -123,4 +155,3 @@ class Conflict:
|
||||
return pos
|
||||
logging.error("Didn't find ground position ({})!".format(initial))
|
||||
return None
|
||||
|
||||
|
||||
@ -3,15 +3,20 @@ import random
|
||||
from dcs.vehicles import Armor
|
||||
|
||||
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
|
||||
: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:
|
||||
unit_type = random.choice(possible_unit)
|
||||
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.generate()
|
||||
return generator.get_generated_group()
|
||||
|
||||
|
||||
@ -4,7 +4,6 @@ from gen.sam.group_generator import GroupGenerator
|
||||
|
||||
|
||||
class ArmoredGroupGenerator(GroupGenerator):
|
||||
|
||||
def __init__(self, game, ground_object, unit_type):
|
||||
super(ArmoredGroupGenerator, self).__init__(game, ground_object)
|
||||
self.unit_type = unit_type
|
||||
@ -20,13 +19,16 @@ class ArmoredGroupGenerator(GroupGenerator):
|
||||
for i in range(grid_x):
|
||||
for j in range(grid_y):
|
||||
index = index + 1
|
||||
self.add_unit(self.unit_type, "Armor#" + str(index),
|
||||
self.position.x + spacing * i,
|
||||
self.position.y + spacing * j, self.heading)
|
||||
self.add_unit(
|
||||
self.unit_type,
|
||||
"Armor#" + str(index),
|
||||
self.position.x + spacing * i,
|
||||
self.position.y + spacing * j,
|
||||
self.heading,
|
||||
)
|
||||
|
||||
|
||||
class FixedSizeArmorGroupGenerator(GroupGenerator):
|
||||
|
||||
def __init__(self, game, ground_object, unit_type, size):
|
||||
super(FixedSizeArmorGroupGenerator, self).__init__(game, ground_object)
|
||||
self.unit_type = unit_type
|
||||
@ -38,7 +40,10 @@ class FixedSizeArmorGroupGenerator(GroupGenerator):
|
||||
index = 0
|
||||
for i in range(self.size):
|
||||
index = index + 1
|
||||
self.add_unit(self.unit_type, "Armor#" + str(index),
|
||||
self.position.x + spacing * i,
|
||||
self.position.y, self.heading)
|
||||
|
||||
self.add_unit(
|
||||
self.unit_type,
|
||||
"Armor#" + str(index),
|
||||
self.position.x + spacing * i,
|
||||
self.position.y,
|
||||
self.heading,
|
||||
)
|
||||
|
||||
@ -4,23 +4,48 @@ from gen.sam.group_generator import ShipGroupGenerator
|
||||
|
||||
|
||||
class CarrierGroupGenerator(ShipGroupGenerator):
|
||||
|
||||
def generate(self):
|
||||
|
||||
# Add carrier
|
||||
if len(self.faction.aircraft_carrier) > 0:
|
||||
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:
|
||||
return
|
||||
|
||||
# Add destroyers escort
|
||||
if len(self.faction.destroyers) > 0:
|
||||
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(dd_type, "DD2", self.position.x + 2500, self.position.y - 4500, self.heading)
|
||||
self.add_unit(
|
||||
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(dd_type, "DD4", self.position.x + 4500, self.position.y - 8500, self.heading)
|
||||
self.add_unit(
|
||||
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
|
||||
|
||||
@ -20,7 +20,6 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class ChineseNavyGroupGenerator(ShipGroupGenerator):
|
||||
|
||||
def generate(self):
|
||||
|
||||
include_frigate = random.choice([True, True, False])
|
||||
@ -30,17 +29,45 @@ class ChineseNavyGroupGenerator(ShipGroupGenerator):
|
||||
include_frigate = True
|
||||
|
||||
if include_frigate:
|
||||
self.add_unit(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)
|
||||
self.add_unit(
|
||||
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:
|
||||
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(dd_type, "DD2", self.position.x + 2400, self.position.y - 900, self.heading)
|
||||
self.add_unit(
|
||||
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
|
||||
|
||||
|
||||
class Type54GroupGenerator(DDGroupGenerator):
|
||||
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
|
||||
super(Type54GroupGenerator, self).__init__(game, ground_object, faction, Type_054A_Frigate)
|
||||
def __init__(
|
||||
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):
|
||||
|
||||
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction, ddtype: ShipType):
|
||||
def __init__(
|
||||
self,
|
||||
game: Game,
|
||||
ground_object: TheaterGroundObject,
|
||||
faction: Faction,
|
||||
ddtype: ShipType,
|
||||
):
|
||||
super(DDGroupGenerator, self).__init__(game, ground_object, faction)
|
||||
self.ddtype = ddtype
|
||||
|
||||
def generate(self):
|
||||
self.add_unit(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.add_unit(
|
||||
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
|
||||
|
||||
|
||||
class OliverHazardPerryGroupGenerator(DDGroupGenerator):
|
||||
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
|
||||
super(OliverHazardPerryGroupGenerator, self).__init__(game, ground_object, faction, Oliver_Hazzard_Perry_class)
|
||||
def __init__(
|
||||
self, game: Game, ground_object: TheaterGroundObject, faction: Faction
|
||||
):
|
||||
super(OliverHazardPerryGroupGenerator, self).__init__(
|
||||
game, ground_object, faction, Oliver_Hazzard_Perry_class
|
||||
)
|
||||
|
||||
|
||||
class ArleighBurkeGroupGenerator(DDGroupGenerator):
|
||||
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
|
||||
super(ArleighBurkeGroupGenerator, self).__init__(game, ground_object, faction, USS_Arleigh_Burke_IIa)
|
||||
def __init__(
|
||||
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):
|
||||
|
||||
def generate(self):
|
||||
|
||||
# Add carrier
|
||||
if len(self.faction.helicopter_carrier) > 0:
|
||||
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
|
||||
if len(self.faction.destroyers) > 0:
|
||||
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(dd_type, "DD2", self.position.x + 1250, self.position.y - 1450, self.heading)
|
||||
self.add_unit(
|
||||
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
|
||||
|
||||
@ -9,7 +9,7 @@ from dcs.ships import (
|
||||
FF_1135M_Rezky,
|
||||
CG_1164_Moskva,
|
||||
SSK_877,
|
||||
SSK_641B
|
||||
SSK_641B,
|
||||
)
|
||||
|
||||
from gen.fleet.dd_group import DDGroupGenerator
|
||||
@ -23,7 +23,6 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class RussianNavyGroupGenerator(ShipGroupGenerator):
|
||||
|
||||
def generate(self):
|
||||
|
||||
include_frigate = random.choice([True, True, False])
|
||||
@ -39,37 +38,79 @@ class RussianNavyGroupGenerator(ShipGroupGenerator):
|
||||
|
||||
if include_frigate:
|
||||
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(frigate_type, "FF2", self.position.x + 1200, self.position.y - 900, self.heading)
|
||||
self.add_unit(
|
||||
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:
|
||||
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(dd_type, "DD2", self.position.x + 2400, self.position.y - 900, self.heading)
|
||||
self.add_unit(
|
||||
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:
|
||||
# Only include the Moskva for now, the Pyotry Velikiy is an unkillable monster.
|
||||
# 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
|
||||
|
||||
|
||||
class GrishaGroupGenerator(DDGroupGenerator):
|
||||
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
|
||||
super(GrishaGroupGenerator, self).__init__(game, ground_object, faction, FFL_1124_4_Grisha)
|
||||
def __init__(
|
||||
self, game: Game, ground_object: TheaterGroundObject, faction: Faction
|
||||
):
|
||||
super(GrishaGroupGenerator, self).__init__(
|
||||
game, ground_object, faction, FFL_1124_4_Grisha
|
||||
)
|
||||
|
||||
|
||||
class MolniyaGroupGenerator(DDGroupGenerator):
|
||||
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
|
||||
super(MolniyaGroupGenerator, self).__init__(game, ground_object, faction, FSG_1241_1MP_Molniya)
|
||||
def __init__(
|
||||
self, game: Game, ground_object: TheaterGroundObject, faction: Faction
|
||||
):
|
||||
super(MolniyaGroupGenerator, self).__init__(
|
||||
game, ground_object, faction, FSG_1241_1MP_Molniya
|
||||
)
|
||||
|
||||
|
||||
class KiloSubGroupGenerator(DDGroupGenerator):
|
||||
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
|
||||
super(KiloSubGroupGenerator, self).__init__(game, ground_object, faction, SSK_877)
|
||||
def __init__(
|
||||
self, game: Game, ground_object: TheaterGroundObject, faction: Faction
|
||||
):
|
||||
super(KiloSubGroupGenerator, self).__init__(
|
||||
game, ground_object, faction, SSK_877
|
||||
)
|
||||
|
||||
|
||||
class TangoSubGroupGenerator(DDGroupGenerator):
|
||||
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Faction):
|
||||
super(TangoSubGroupGenerator, self).__init__(game, ground_object, faction, SSK_641B)
|
||||
def __init__(
|
||||
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):
|
||||
|
||||
def generate(self):
|
||||
|
||||
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
|
||||
|
||||
@ -4,10 +4,18 @@ import random
|
||||
from game import db
|
||||
from gen.fleet.carrier_group import CarrierGroupGenerator
|
||||
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.ru_dd_group import RussianNavyGroupGenerator, GrishaGroupGenerator, MolniyaGroupGenerator, \
|
||||
KiloSubGroupGenerator, TangoSubGroupGenerator
|
||||
from gen.fleet.ru_dd_group import (
|
||||
RussianNavyGroupGenerator,
|
||||
GrishaGroupGenerator,
|
||||
MolniyaGroupGenerator,
|
||||
KiloSubGroupGenerator,
|
||||
TangoSubGroupGenerator,
|
||||
)
|
||||
from gen.fleet.schnellboot import SchnellbootGroupGenerator
|
||||
from gen.fleet.uboat import UBoatGroupGenerator
|
||||
from gen.fleet.ww2lst import WW2LSTGroupGenerator
|
||||
@ -25,7 +33,7 @@ SHIP_MAP = {
|
||||
"MolniyaGroupGenerator": MolniyaGroupGenerator,
|
||||
"KiloSubGroupGenerator": KiloSubGroupGenerator,
|
||||
"TangoSubGroupGenerator": TangoSubGroupGenerator,
|
||||
"Type54GroupGenerator": Type54GroupGenerator
|
||||
"Type54GroupGenerator": Type54GroupGenerator,
|
||||
}
|
||||
|
||||
|
||||
@ -42,7 +50,11 @@ def generate_ship_group(game, ground_object, faction_name: str):
|
||||
generator.generate()
|
||||
return generator.get_generated_group()
|
||||
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
|
||||
|
||||
|
||||
|
||||
@ -6,10 +6,15 @@ from gen.sam.group_generator import ShipGroupGenerator
|
||||
|
||||
|
||||
class UBoatGroupGenerator(ShipGroupGenerator):
|
||||
|
||||
def generate(self):
|
||||
|
||||
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):
|
||||
|
||||
def generate(self):
|
||||
|
||||
# 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)):
|
||||
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,22 +109,25 @@ class ProposedMission:
|
||||
flights: List[ProposedFlight]
|
||||
|
||||
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}"
|
||||
|
||||
|
||||
class AircraftAllocator:
|
||||
"""Finds suitable aircraft for proposed missions."""
|
||||
|
||||
def __init__(self, closest_airfields: ClosestAirfields,
|
||||
global_inventory: GlobalAircraftInventory,
|
||||
is_player: bool) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
closest_airfields: ClosestAirfields,
|
||||
global_inventory: GlobalAircraftInventory,
|
||||
is_player: bool,
|
||||
) -> None:
|
||||
self.closest_airfields = closest_airfields
|
||||
self.global_inventory = global_inventory
|
||||
self.is_player = is_player
|
||||
|
||||
def find_aircraft_for_flight(
|
||||
self, flight: ProposedFlight
|
||||
self, flight: ProposedFlight
|
||||
) -> Optional[Tuple[ControlPoint, Type[FlyingType]]]:
|
||||
"""Finds aircraft suitable for the given mission.
|
||||
|
||||
@ -144,12 +147,12 @@ class AircraftAllocator:
|
||||
on subsequent calls. If the found aircraft are not used, the caller is
|
||||
responsible for returning them to the inventory.
|
||||
"""
|
||||
return self.find_aircraft_of_type(
|
||||
flight, aircraft_for_task(flight.task)
|
||||
)
|
||||
return self.find_aircraft_of_type(flight, aircraft_for_task(flight.task))
|
||||
|
||||
def find_aircraft_of_type(
|
||||
self, flight: ProposedFlight, types: List[Type[FlyingType]],
|
||||
self,
|
||||
flight: ProposedFlight,
|
||||
types: List[Type[FlyingType]],
|
||||
) -> Optional[Tuple[ControlPoint, Type[FlyingType]]]:
|
||||
airfields_in_range = self.closest_airfields.airfields_within(
|
||||
flight.max_distance
|
||||
@ -171,18 +174,22 @@ class AircraftAllocator:
|
||||
class PackageBuilder:
|
||||
"""Builds a Package for the flights it receives."""
|
||||
|
||||
def __init__(self, location: MissionTarget,
|
||||
closest_airfields: ClosestAirfields,
|
||||
global_inventory: GlobalAircraftInventory,
|
||||
is_player: bool,
|
||||
package_country: str,
|
||||
start_type: str) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
location: MissionTarget,
|
||||
closest_airfields: ClosestAirfields,
|
||||
global_inventory: GlobalAircraftInventory,
|
||||
is_player: bool,
|
||||
package_country: str,
|
||||
start_type: str,
|
||||
) -> None:
|
||||
self.closest_airfields = closest_airfields
|
||||
self.is_player = is_player
|
||||
self.package_country = package_country
|
||||
self.package = Package(location)
|
||||
self.allocator = AircraftAllocator(closest_airfields, global_inventory,
|
||||
is_player)
|
||||
self.allocator = AircraftAllocator(
|
||||
closest_airfields, global_inventory, is_player
|
||||
)
|
||||
self.global_inventory = global_inventory
|
||||
self.start_type = start_type
|
||||
|
||||
@ -203,14 +210,23 @@ class PackageBuilder:
|
||||
else:
|
||||
start_type = self.start_type
|
||||
|
||||
flight = Flight(self.package, self.package_country, aircraft, plan.num_aircraft, plan.task,
|
||||
start_type, departure=airfield, arrival=airfield,
|
||||
divert=self.find_divert_field(aircraft, airfield))
|
||||
flight = Flight(
|
||||
self.package,
|
||||
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)
|
||||
return True
|
||||
|
||||
def find_divert_field(self, aircraft: Type[FlyingType],
|
||||
arrival: ControlPoint) -> Optional[ControlPoint]:
|
||||
def find_divert_field(
|
||||
self, aircraft: Type[FlyingType], arrival: ControlPoint
|
||||
) -> Optional[ControlPoint]:
|
||||
divert_limit = nautical_miles(150)
|
||||
for airfield in self.closest_airfields.airfields_within(divert_limit):
|
||||
if airfield.captured != self.is_player:
|
||||
@ -323,8 +339,8 @@ class ObjectiveFinder:
|
||||
return self._targets_by_range(self.enemy_ships())
|
||||
|
||||
def _targets_by_range(
|
||||
self,
|
||||
targets: Iterable[MissionTarget]) -> Iterator[MissionTarget]:
|
||||
self, targets: Iterable[MissionTarget]
|
||||
) -> Iterator[MissionTarget]:
|
||||
target_ranges: List[Tuple[MissionTarget, int]] = []
|
||||
for target in targets:
|
||||
ranges: List[int] = []
|
||||
@ -430,13 +446,17 @@ class ObjectiveFinder:
|
||||
|
||||
def friendly_control_points(self) -> Iterator[ControlPoint]:
|
||||
"""Iterates over all friendly control points."""
|
||||
return (c for c in self.game.theater.controlpoints if
|
||||
c.is_friendly(self.is_player))
|
||||
return (
|
||||
c for c in self.game.theater.controlpoints if c.is_friendly(self.is_player)
|
||||
)
|
||||
|
||||
def enemy_control_points(self) -> Iterator[ControlPoint]:
|
||||
"""Iterates over all enemy control points."""
|
||||
return (c for c in self.game.theater.controlpoints if
|
||||
not c.is_friendly(self.is_player))
|
||||
return (
|
||||
c
|
||||
for c in self.game.theater.controlpoints
|
||||
if not c.is_friendly(self.is_player)
|
||||
)
|
||||
|
||||
def all_possible_targets(self) -> Iterator[MissionTarget]:
|
||||
"""Iterates over all possible mission targets in the theater.
|
||||
@ -510,23 +530,36 @@ class CoalitionMissionPlanner:
|
||||
for cp in self.objective_finder.vulnerable_control_points():
|
||||
# Plan three rounds of CAP to give ~90 minutes coverage. Spacing
|
||||
# these out appropriately is done in stagger_missions.
|
||||
yield ProposedMission(cp, [
|
||||
ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE),
|
||||
])
|
||||
yield ProposedMission(cp, [
|
||||
ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE),
|
||||
])
|
||||
yield ProposedMission(cp, [
|
||||
ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE),
|
||||
])
|
||||
yield ProposedMission(
|
||||
cp,
|
||||
[
|
||||
ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE),
|
||||
],
|
||||
)
|
||||
yield ProposedMission(
|
||||
cp,
|
||||
[
|
||||
ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE),
|
||||
],
|
||||
)
|
||||
yield ProposedMission(
|
||||
cp,
|
||||
[
|
||||
ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE),
|
||||
],
|
||||
)
|
||||
|
||||
# Find front lines, plan CAS.
|
||||
for front_line in self.objective_finder.front_lines():
|
||||
yield ProposedMission(front_line, [
|
||||
ProposedFlight(FlightType.CAS, 2, self.MAX_CAS_RANGE),
|
||||
ProposedFlight(FlightType.TARCAP, 2, self.MAX_CAP_RANGE,
|
||||
EscortType.AirToAir),
|
||||
])
|
||||
yield ProposedMission(
|
||||
front_line,
|
||||
[
|
||||
ProposedFlight(FlightType.CAS, 2, self.MAX_CAS_RANGE),
|
||||
ProposedFlight(
|
||||
FlightType.TARCAP, 2, self.MAX_CAP_RANGE, EscortType.AirToAir
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
def propose_missions(self) -> Iterator[ProposedMission]:
|
||||
"""Identifies and iterates over potential mission in priority order."""
|
||||
@ -537,30 +570,46 @@ class CoalitionMissionPlanner:
|
||||
# Find enemy SAM sites with ranges that extend to within 50 nmi of
|
||||
# friendly CPs, front, lines, or objects, plan DEAD.
|
||||
for sam in self.objective_finder.threatening_sams():
|
||||
yield ProposedMission(sam, [
|
||||
ProposedFlight(FlightType.DEAD, 2, self.MAX_SEAD_RANGE),
|
||||
# TODO: Max escort range.
|
||||
ProposedFlight(FlightType.ESCORT, 2, self.MAX_SEAD_RANGE,
|
||||
EscortType.AirToAir),
|
||||
])
|
||||
yield ProposedMission(
|
||||
sam,
|
||||
[
|
||||
ProposedFlight(FlightType.DEAD, 2, self.MAX_SEAD_RANGE),
|
||||
# TODO: Max escort range.
|
||||
ProposedFlight(
|
||||
FlightType.ESCORT, 2, self.MAX_SEAD_RANGE, EscortType.AirToAir
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
for group in self.objective_finder.threatening_ships():
|
||||
yield ProposedMission(group, [
|
||||
ProposedFlight(FlightType.ANTISHIP, 2, self.MAX_ANTISHIP_RANGE),
|
||||
# TODO: Max escort range.
|
||||
ProposedFlight(FlightType.ESCORT, 2, self.MAX_ANTISHIP_RANGE,
|
||||
EscortType.AirToAir),
|
||||
])
|
||||
yield ProposedMission(
|
||||
group,
|
||||
[
|
||||
ProposedFlight(FlightType.ANTISHIP, 2, self.MAX_ANTISHIP_RANGE),
|
||||
# TODO: Max escort range.
|
||||
ProposedFlight(
|
||||
FlightType.ESCORT,
|
||||
2,
|
||||
self.MAX_ANTISHIP_RANGE,
|
||||
EscortType.AirToAir,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
for group in self.objective_finder.threatening_vehicle_groups():
|
||||
yield ProposedMission(group, [
|
||||
ProposedFlight(FlightType.BAI, 2, self.MAX_BAI_RANGE),
|
||||
# TODO: Max escort range.
|
||||
ProposedFlight(FlightType.ESCORT, 2, self.MAX_BAI_RANGE,
|
||||
EscortType.AirToAir),
|
||||
ProposedFlight(FlightType.SEAD, 2, self.MAX_OCA_RANGE,
|
||||
EscortType.Sead),
|
||||
])
|
||||
yield ProposedMission(
|
||||
group,
|
||||
[
|
||||
ProposedFlight(FlightType.BAI, 2, self.MAX_BAI_RANGE),
|
||||
# TODO: Max escort range.
|
||||
ProposedFlight(
|
||||
FlightType.ESCORT, 2, self.MAX_BAI_RANGE, EscortType.AirToAir
|
||||
),
|
||||
ProposedFlight(
|
||||
FlightType.SEAD, 2, self.MAX_OCA_RANGE, EscortType.Sead
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
for target in self.objective_finder.oca_targets(min_aircraft=20):
|
||||
flights = [
|
||||
@ -569,27 +618,37 @@ class CoalitionMissionPlanner:
|
||||
if self.game.settings.default_start_type == "Cold":
|
||||
# Only schedule if the default start type is Cold. If the player
|
||||
# has set anything else there are no targets to hit.
|
||||
flights.append(ProposedFlight(FlightType.OCA_AIRCRAFT, 2,
|
||||
self.MAX_OCA_RANGE))
|
||||
flights.extend([
|
||||
# TODO: Max escort range.
|
||||
ProposedFlight(FlightType.ESCORT, 2, self.MAX_OCA_RANGE,
|
||||
EscortType.AirToAir),
|
||||
ProposedFlight(FlightType.SEAD, 2, self.MAX_OCA_RANGE,
|
||||
EscortType.Sead),
|
||||
])
|
||||
flights.append(
|
||||
ProposedFlight(FlightType.OCA_AIRCRAFT, 2, self.MAX_OCA_RANGE)
|
||||
)
|
||||
flights.extend(
|
||||
[
|
||||
# TODO: Max escort range.
|
||||
ProposedFlight(
|
||||
FlightType.ESCORT, 2, self.MAX_OCA_RANGE, EscortType.AirToAir
|
||||
),
|
||||
ProposedFlight(
|
||||
FlightType.SEAD, 2, self.MAX_OCA_RANGE, EscortType.Sead
|
||||
),
|
||||
]
|
||||
)
|
||||
yield ProposedMission(target, flights)
|
||||
|
||||
# Plan strike missions.
|
||||
for target in self.objective_finder.strike_targets():
|
||||
yield ProposedMission(target, [
|
||||
ProposedFlight(FlightType.STRIKE, 2, self.MAX_STRIKE_RANGE),
|
||||
# TODO: Max escort range.
|
||||
ProposedFlight(FlightType.ESCORT, 2, self.MAX_STRIKE_RANGE,
|
||||
EscortType.AirToAir),
|
||||
ProposedFlight(FlightType.SEAD, 2, self.MAX_STRIKE_RANGE,
|
||||
EscortType.Sead),
|
||||
])
|
||||
yield ProposedMission(
|
||||
target,
|
||||
[
|
||||
ProposedFlight(FlightType.STRIKE, 2, self.MAX_STRIKE_RANGE),
|
||||
# TODO: Max escort range.
|
||||
ProposedFlight(
|
||||
FlightType.ESCORT, 2, self.MAX_STRIKE_RANGE, EscortType.AirToAir
|
||||
),
|
||||
ProposedFlight(
|
||||
FlightType.SEAD, 2, self.MAX_STRIKE_RANGE, EscortType.Sead
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
def plan_missions(self) -> None:
|
||||
"""Identifies and plans mission for the turn."""
|
||||
@ -604,19 +663,23 @@ class CoalitionMissionPlanner:
|
||||
for cp in self.objective_finder.friendly_control_points():
|
||||
inventory = self.game.aircraft_inventory.for_control_point(cp)
|
||||
for aircraft, available in inventory.all_aircraft:
|
||||
self.message("Unused aircraft",
|
||||
f"{available} {aircraft.id} from {cp}")
|
||||
self.message("Unused aircraft", f"{available} {aircraft.id} from {cp}")
|
||||
|
||||
def plan_flight(self, mission: ProposedMission, flight: ProposedFlight,
|
||||
builder: PackageBuilder, missing_types: Set[FlightType],
|
||||
for_reserves: bool) -> None:
|
||||
def plan_flight(
|
||||
self,
|
||||
mission: ProposedMission,
|
||||
flight: ProposedFlight,
|
||||
builder: PackageBuilder,
|
||||
missing_types: Set[FlightType],
|
||||
for_reserves: bool,
|
||||
) -> None:
|
||||
if not builder.plan_flight(flight):
|
||||
missing_types.add(flight.task)
|
||||
purchase_order = AircraftProcurementRequest(
|
||||
near=mission.location,
|
||||
range=flight.max_distance,
|
||||
task_capability=flight.task,
|
||||
number=flight.num_aircraft
|
||||
number=flight.num_aircraft,
|
||||
)
|
||||
if for_reserves:
|
||||
# Reserves are planned for critical missions, so prioritize
|
||||
@ -626,26 +689,28 @@ class CoalitionMissionPlanner:
|
||||
self.procurement_requests.append(purchase_order)
|
||||
|
||||
def scrub_mission_missing_aircraft(
|
||||
self, mission: ProposedMission, builder: PackageBuilder,
|
||||
missing_types: Set[FlightType],
|
||||
not_attempted: Iterable[ProposedFlight],
|
||||
reserves: bool) -> None:
|
||||
self,
|
||||
mission: ProposedMission,
|
||||
builder: PackageBuilder,
|
||||
missing_types: Set[FlightType],
|
||||
not_attempted: Iterable[ProposedFlight],
|
||||
reserves: bool,
|
||||
) -> None:
|
||||
# Try to plan the rest of the mission just so we can count the missing
|
||||
# types to buy.
|
||||
for flight in not_attempted:
|
||||
self.plan_flight(mission, flight, builder, missing_types, reserves)
|
||||
|
||||
missing_types_str = ", ".join(
|
||||
sorted([t.name for t in missing_types]))
|
||||
missing_types_str = ", ".join(sorted([t.name for t in missing_types]))
|
||||
builder.release_planned_aircraft()
|
||||
desc = "reserve aircraft" if reserves else "aircraft"
|
||||
self.message(
|
||||
"Insufficient aircraft",
|
||||
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(
|
||||
self, builder: PackageBuilder) -> Dict[EscortType, bool]:
|
||||
def check_needed_escorts(self, builder: PackageBuilder) -> Dict[EscortType, bool]:
|
||||
threats = defaultdict(bool)
|
||||
for flight in builder.package.flights:
|
||||
if self.threat_zones.threatened_by_aircraft(flight):
|
||||
@ -654,8 +719,7 @@ class CoalitionMissionPlanner:
|
||||
threats[EscortType.Sead] = True
|
||||
return threats
|
||||
|
||||
def plan_mission(self, mission: ProposedMission,
|
||||
reserves: bool = False) -> None:
|
||||
def plan_mission(self, mission: ProposedMission, reserves: bool = False) -> None:
|
||||
"""Allocates aircraft for a proposed mission and adds it to the ATO."""
|
||||
|
||||
if self.is_player:
|
||||
@ -669,7 +733,7 @@ class CoalitionMissionPlanner:
|
||||
self.game.aircraft_inventory,
|
||||
self.is_player,
|
||||
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
|
||||
@ -683,12 +747,12 @@ class CoalitionMissionPlanner:
|
||||
# If the package does not need escorts they may be pruned.
|
||||
escorts.append(proposed_flight)
|
||||
continue
|
||||
self.plan_flight(mission, proposed_flight, builder, missing_types,
|
||||
reserves)
|
||||
self.plan_flight(mission, proposed_flight, builder, missing_types, reserves)
|
||||
|
||||
if missing_types:
|
||||
self.scrub_mission_missing_aircraft(mission, builder, missing_types,
|
||||
escorts, reserves)
|
||||
self.scrub_mission_missing_aircraft(
|
||||
mission, builder, missing_types, escorts, reserves
|
||||
)
|
||||
return
|
||||
|
||||
# Create flight plans for the main flights of the package so we can
|
||||
@ -697,8 +761,9 @@ class CoalitionMissionPlanner:
|
||||
# flights that will rendezvous with their package will be affected by
|
||||
# the other flights in the package. Escorts will not be able to
|
||||
# contribute to this.
|
||||
flight_plan_builder = FlightPlanBuilder(self.game, builder.package,
|
||||
self.is_player)
|
||||
flight_plan_builder = FlightPlanBuilder(
|
||||
self.game, builder.package, self.is_player
|
||||
)
|
||||
for flight in builder.package.flights:
|
||||
flight_plan_builder.populate_flight_plan(flight)
|
||||
|
||||
@ -708,14 +773,14 @@ class CoalitionMissionPlanner:
|
||||
# impossible.
|
||||
assert escort.escort_type is not None
|
||||
if needed_escorts[escort.escort_type]:
|
||||
self.plan_flight(mission, escort, builder, missing_types,
|
||||
reserves)
|
||||
self.plan_flight(mission, escort, builder, missing_types, reserves)
|
||||
|
||||
# Check again for unavailable aircraft. If the escort was required and
|
||||
# none were found, scrub the mission.
|
||||
if missing_types:
|
||||
self.scrub_mission_missing_aircraft(mission, builder, missing_types,
|
||||
escorts, reserves)
|
||||
self.scrub_mission_missing_aircraft(
|
||||
mission, builder, missing_types, escorts, reserves
|
||||
)
|
||||
return
|
||||
|
||||
if reserves:
|
||||
@ -732,8 +797,9 @@ class CoalitionMissionPlanner:
|
||||
self.ato.add_package(package)
|
||||
|
||||
def stagger_missions(self) -> None:
|
||||
def start_time_generator(count: int, earliest: int, latest: int,
|
||||
margin: int) -> Iterator[timedelta]:
|
||||
def start_time_generator(
|
||||
count: int, earliest: int, latest: int, margin: int
|
||||
) -> Iterator[timedelta]:
|
||||
interval = (latest - earliest) // count
|
||||
for time in range(earliest, latest, interval):
|
||||
error = random.randint(-margin, margin)
|
||||
@ -744,17 +810,13 @@ class CoalitionMissionPlanner:
|
||||
FlightType.TARCAP,
|
||||
}
|
||||
|
||||
previous_cap_end_time: Dict[MissionTarget, timedelta] = defaultdict(
|
||||
timedelta
|
||||
)
|
||||
non_dca_packages = [p for p in self.ato.packages if
|
||||
p.primary_task not in dca_types]
|
||||
previous_cap_end_time: Dict[MissionTarget, timedelta] = defaultdict(timedelta)
|
||||
non_dca_packages = [
|
||||
p for p in self.ato.packages if p.primary_task not in dca_types
|
||||
]
|
||||
|
||||
start_time = start_time_generator(
|
||||
count=len(non_dca_packages),
|
||||
earliest=5,
|
||||
latest=90,
|
||||
margin=5
|
||||
count=len(non_dca_packages), earliest=5, latest=90, margin=5
|
||||
)
|
||||
for package in self.ato.packages:
|
||||
tot = TotEstimator(package).earliest_tot()
|
||||
@ -771,8 +833,7 @@ class CoalitionMissionPlanner:
|
||||
departure_time = package.mission_departure_time
|
||||
# Should be impossible for CAPs
|
||||
if departure_time is None:
|
||||
logging.error(
|
||||
f"Could not determine mission end time for {package}")
|
||||
logging.error(f"Could not determine mission end time for {package}")
|
||||
continue
|
||||
previous_cap_end_time[package.target] = departure_time
|
||||
else:
|
||||
@ -791,8 +852,6 @@ class CoalitionMissionPlanner:
|
||||
message to the info panel.
|
||||
"""
|
||||
if self.is_player:
|
||||
self.game.informations.append(
|
||||
Information(title, text, self.game.turn)
|
||||
)
|
||||
self.game.informations.append(Information(title, text, self.game.turn))
|
||||
else:
|
||||
logging.info(f"{title}: {text}")
|
||||
|
||||
@ -13,7 +13,7 @@ from dcs.helicopters import (
|
||||
SA342L,
|
||||
SA342M,
|
||||
UH_1H,
|
||||
SH_60B
|
||||
SH_60B,
|
||||
)
|
||||
from dcs.planes import (
|
||||
AJS37,
|
||||
@ -83,7 +83,7 @@ from dcs.planes import (
|
||||
Tu_22M3,
|
||||
Tu_95MS,
|
||||
WingLoong_I,
|
||||
I_16
|
||||
I_16,
|
||||
)
|
||||
from dcs.unittype import FlyingType
|
||||
|
||||
@ -367,11 +367,7 @@ TRANSPORT_CAPABLE = [
|
||||
UH_1H,
|
||||
]
|
||||
|
||||
DRONES = [
|
||||
MQ_9_Reaper,
|
||||
RQ_1A_Predator,
|
||||
WingLoong_I
|
||||
]
|
||||
DRONES = [MQ_9_Reaper, RQ_1A_Predator, WingLoong_I]
|
||||
|
||||
|
||||
def aircraft_for_task(task: FlightType) -> List[Type[FlyingType]]:
|
||||
|
||||
@ -12,8 +12,9 @@ if TYPE_CHECKING:
|
||||
class ClosestAirfields:
|
||||
"""Precalculates which control points are closes to the given target."""
|
||||
|
||||
def __init__(self, target: MissionTarget,
|
||||
all_control_points: List[ControlPoint]) -> None:
|
||||
def __init__(
|
||||
self, target: MissionTarget, all_control_points: List[ControlPoint]
|
||||
) -> None:
|
||||
self.target = target
|
||||
# This cache is configured once on load, so it's important that it is
|
||||
# complete and deterministic to avoid different behaviors across loads.
|
||||
@ -52,9 +53,7 @@ class ObjectiveDistanceCache:
|
||||
@classmethod
|
||||
def get_closest_airfields(cls, location: MissionTarget) -> ClosestAirfields:
|
||||
if cls.theater is None:
|
||||
raise RuntimeError(
|
||||
"Call ObjectiveDistanceCache.set_theater before using"
|
||||
)
|
||||
raise RuntimeError("Call ObjectiveDistanceCache.set_theater before using")
|
||||
|
||||
if location.name not in cls.closest_airfields:
|
||||
cls.closest_airfields[location.name] = ClosestAirfields(
|
||||
|
||||
@ -39,22 +39,22 @@ class FlightType(Enum):
|
||||
|
||||
|
||||
class FlightWaypointType(Enum):
|
||||
TAKEOFF = 0 # Take off point
|
||||
ASCEND_POINT = 1 # Ascension point after take off
|
||||
PATROL = 2 # Patrol point
|
||||
PATROL_TRACK = 3 # Patrol race track
|
||||
NAV = 4 # Nav point
|
||||
INGRESS_STRIKE = 5 # Ingress strike (For generator, means that this should have bombing on next TARGET_POINT points)
|
||||
INGRESS_SEAD = 6 # Ingress sead (For generator, means that this should attack groups on TARGET_GROUP_LOC points)
|
||||
INGRESS_CAS = 7 # Ingress cas (should start CAS task)
|
||||
CAS = 8 # Should do CAS there
|
||||
EGRESS = 9 # Should stop attack
|
||||
DESCENT_POINT = 10 # Should start descending to pattern alt
|
||||
LANDING_POINT = 11 # Should land there
|
||||
TARGET_POINT = 12 # A target building or static object, position
|
||||
TARGET_GROUP_LOC = 13 # A target group approximate location
|
||||
TARGET_SHIP = 14 # A target ship known location
|
||||
CUSTOM = 15 # User waypoint (no specific behaviour)
|
||||
TAKEOFF = 0 # Take off point
|
||||
ASCEND_POINT = 1 # Ascension point after take off
|
||||
PATROL = 2 # Patrol point
|
||||
PATROL_TRACK = 3 # Patrol race track
|
||||
NAV = 4 # Nav point
|
||||
INGRESS_STRIKE = 5 # Ingress strike (For generator, means that this should have bombing on next TARGET_POINT points)
|
||||
INGRESS_SEAD = 6 # Ingress sead (For generator, means that this should attack groups on TARGET_GROUP_LOC points)
|
||||
INGRESS_CAS = 7 # Ingress cas (should start CAS task)
|
||||
CAS = 8 # Should do CAS there
|
||||
EGRESS = 9 # Should stop attack
|
||||
DESCENT_POINT = 10 # Should start descending to pattern alt
|
||||
LANDING_POINT = 11 # Should land there
|
||||
TARGET_POINT = 12 # A target building or static object, position
|
||||
TARGET_GROUP_LOC = 13 # A target group approximate location
|
||||
TARGET_SHIP = 14 # A target ship known location
|
||||
CUSTOM = 15 # User waypoint (no specific behaviour)
|
||||
JOIN = 16
|
||||
SPLIT = 17
|
||||
LOITER = 18
|
||||
@ -68,9 +68,13 @@ class FlightWaypointType(Enum):
|
||||
|
||||
|
||||
class FlightWaypoint:
|
||||
|
||||
def __init__(self, waypoint_type: FlightWaypointType, x: float, y: float,
|
||||
alt: Distance = meters(0)) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
waypoint_type: FlightWaypointType,
|
||||
x: float,
|
||||
y: float,
|
||||
alt: Distance = meters(0),
|
||||
) -> None:
|
||||
"""Creates a flight waypoint.
|
||||
|
||||
Args:
|
||||
@ -108,10 +112,13 @@ class FlightWaypoint:
|
||||
return Point(self.x, self.y)
|
||||
|
||||
@classmethod
|
||||
def from_pydcs(cls, point: MovingPoint,
|
||||
from_cp: ControlPoint) -> "FlightWaypoint":
|
||||
waypoint = FlightWaypoint(FlightWaypointType.NAV, point.position.x,
|
||||
point.position.y, meters(point.alt))
|
||||
def from_pydcs(cls, point: MovingPoint, from_cp: ControlPoint) -> "FlightWaypoint":
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.NAV,
|
||||
point.position.x,
|
||||
point.position.y,
|
||||
meters(point.alt),
|
||||
)
|
||||
waypoint.alt_type = point.alt_type
|
||||
# Other actions exist... but none of them *should* be the first
|
||||
# waypoint for a flight.
|
||||
@ -135,12 +142,19 @@ class FlightWaypoint:
|
||||
|
||||
|
||||
class Flight:
|
||||
|
||||
def __init__(self, package: Package, country: str, unit_type: Type[FlyingType],
|
||||
count: int, flight_type: FlightType, start_type: str,
|
||||
departure: ControlPoint, arrival: ControlPoint,
|
||||
divert: Optional[ControlPoint],
|
||||
custom_name: Optional[str] = None) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
package: Package,
|
||||
country: str,
|
||||
unit_type: Type[FlyingType],
|
||||
count: int,
|
||||
flight_type: FlightType,
|
||||
start_type: str,
|
||||
departure: ControlPoint,
|
||||
arrival: ControlPoint,
|
||||
divert: Optional[ControlPoint],
|
||||
custom_name: Optional[str] = None,
|
||||
) -> None:
|
||||
self.package = package
|
||||
self.country = country
|
||||
self.unit_type = unit_type
|
||||
@ -161,10 +175,9 @@ class Flight:
|
||||
# FlightPlanBuilder, but an empty flight plan the flight begins with an
|
||||
# empty flight plan.
|
||||
from gen.flights.flightplan import CustomFlightPlan
|
||||
|
||||
self.flight_plan: FlightPlan = CustomFlightPlan(
|
||||
package=package,
|
||||
flight=self,
|
||||
custom_waypoints=[]
|
||||
package=package, flight=self, custom_waypoints=[]
|
||||
)
|
||||
|
||||
@property
|
||||
@ -182,7 +195,7 @@ class Flight:
|
||||
return f"[{self.flight_type}] {self.count} x {name}"
|
||||
|
||||
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:
|
||||
return f"{self.custom_name} {self.count} x {name}"
|
||||
return f"[{self.flight_type}] {self.count} x {name}"
|
||||
|
||||
@ -75,7 +75,7 @@ class FlightPlan:
|
||||
raise NotImplementedError
|
||||
|
||||
def edges(
|
||||
self, until: Optional[FlightWaypoint] = None
|
||||
self, until: Optional[FlightWaypoint] = None
|
||||
) -> Iterator[Tuple[FlightWaypoint, FlightWaypoint]]:
|
||||
"""A list of all paths between waypoints, in order."""
|
||||
waypoints = self.waypoints
|
||||
@ -86,8 +86,9 @@ class FlightPlan:
|
||||
|
||||
return zip(self.waypoints[:last_index], self.waypoints[1:last_index])
|
||||
|
||||
def best_speed_between_waypoints(self, a: FlightWaypoint,
|
||||
b: FlightWaypoint) -> Speed:
|
||||
def best_speed_between_waypoints(
|
||||
self, a: FlightWaypoint, b: FlightWaypoint
|
||||
) -> Speed:
|
||||
"""Desired ground speed between points a and b."""
|
||||
factor = 1.0
|
||||
if b.waypoint_type == FlightWaypointType.ASCEND_POINT:
|
||||
@ -108,8 +109,7 @@ class FlightPlan:
|
||||
# near 2000 ft MSL.
|
||||
return GroundSpeed.for_flight(self.flight, min(a.alt, b.alt)) * factor
|
||||
|
||||
def speed_between_waypoints(self, a: FlightWaypoint,
|
||||
b: FlightWaypoint) -> Speed:
|
||||
def speed_between_waypoints(self, a: FlightWaypoint, b: FlightWaypoint) -> Speed:
|
||||
return self.best_speed_between_waypoints(a, b)
|
||||
|
||||
@property
|
||||
@ -121,11 +121,10 @@ class FlightPlan:
|
||||
failed to generate. Nevertheless, we have to defend against it.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@cached_property
|
||||
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)
|
||||
|
||||
bingo = 1000.0 # Minimum Emergency Fuel
|
||||
@ -142,18 +141,18 @@ class FlightPlan:
|
||||
|
||||
@cached_property
|
||||
def joker_fuel(self) -> int:
|
||||
"""Joker fuel value for the FlightPlan
|
||||
"""
|
||||
"""Joker fuel value for the FlightPlan"""
|
||||
return self.bingo_fuel + 1000
|
||||
|
||||
|
||||
def max_distance_from(self, cp: ControlPoint) -> Distance:
|
||||
"""Returns the farthest waypoint of the flight plan from a ControlPoint.
|
||||
:arg cp The ControlPoint to measure distance from.
|
||||
"""
|
||||
if not self.waypoints:
|
||||
return meters(0)
|
||||
return max([meters(cp.position.distance_to_point(w.position)) for w in
|
||||
self.waypoints])
|
||||
return max(
|
||||
[meters(cp.position.distance_to_point(w.position)) for w in self.waypoints]
|
||||
)
|
||||
|
||||
@property
|
||||
def tot_offset(self) -> timedelta:
|
||||
@ -164,30 +163,30 @@ class FlightPlan:
|
||||
"""
|
||||
return timedelta()
|
||||
|
||||
def _travel_time_to_waypoint(
|
||||
self, destination: FlightWaypoint) -> timedelta:
|
||||
def _travel_time_to_waypoint(self, destination: FlightWaypoint) -> timedelta:
|
||||
total = timedelta()
|
||||
|
||||
if destination not in self.waypoints:
|
||||
raise PlanningError(
|
||||
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):
|
||||
total += self.travel_time_between_waypoints(previous_waypoint,
|
||||
waypoint)
|
||||
total += self.travel_time_between_waypoints(previous_waypoint, waypoint)
|
||||
return total
|
||||
|
||||
def travel_time_between_waypoints(self, a: FlightWaypoint,
|
||||
b: FlightWaypoint) -> timedelta:
|
||||
return TravelTime.between_points(a.position, b.position,
|
||||
self.speed_between_waypoints(a, b))
|
||||
def travel_time_between_waypoints(
|
||||
self, a: FlightWaypoint, b: FlightWaypoint
|
||||
) -> timedelta:
|
||||
return TravelTime.between_points(
|
||||
a.position, b.position, self.speed_between_waypoints(a, b)
|
||||
)
|
||||
|
||||
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||
raise NotImplementedError
|
||||
|
||||
def depart_time_for_waypoint(
|
||||
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||
raise NotImplementedError
|
||||
|
||||
def request_escort_at(self) -> Optional[FlightWaypoint]:
|
||||
@ -212,8 +211,7 @@ class FlightPlan:
|
||||
if takeoff_time is None:
|
||||
return None
|
||||
|
||||
start_time = (takeoff_time - self.estimate_startup() -
|
||||
self.estimate_ground_ops())
|
||||
start_time = takeoff_time - self.estimate_startup() - self.estimate_ground_ops()
|
||||
|
||||
# In case FP math has given us some barely below zero time, round to
|
||||
# zero.
|
||||
@ -269,14 +267,14 @@ class LoiterFlightPlan(FlightPlan):
|
||||
def push_time(self) -> timedelta:
|
||||
raise NotImplementedError
|
||||
|
||||
def depart_time_for_waypoint(
|
||||
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||
if waypoint == self.hold:
|
||||
return self.push_time
|
||||
return None
|
||||
|
||||
def travel_time_between_waypoints(self, a: FlightWaypoint,
|
||||
b: FlightWaypoint) -> timedelta:
|
||||
def travel_time_between_waypoints(
|
||||
self, a: FlightWaypoint, b: FlightWaypoint
|
||||
) -> timedelta:
|
||||
travel_time = super().travel_time_between_waypoints(a, b)
|
||||
if a != self.hold:
|
||||
return travel_time
|
||||
@ -321,12 +319,12 @@ class FormationFlightPlan(LoiterFlightPlan):
|
||||
speeds = []
|
||||
for previous_waypoint, waypoint in self.edges():
|
||||
if waypoint in self.package_speed_waypoints:
|
||||
speeds.append(self.best_speed_between_waypoints(
|
||||
previous_waypoint, waypoint))
|
||||
speeds.append(
|
||||
self.best_speed_between_waypoints(previous_waypoint, waypoint)
|
||||
)
|
||||
return min(speeds)
|
||||
|
||||
def speed_between_waypoints(self, a: FlightWaypoint,
|
||||
b: FlightWaypoint) -> Speed:
|
||||
def speed_between_waypoints(self, a: FlightWaypoint, b: FlightWaypoint) -> Speed:
|
||||
if b in self.package_speed_waypoints:
|
||||
# Should be impossible, as any package with at least one
|
||||
# FormationFlightPlan flight needs a formation speed.
|
||||
@ -359,7 +357,7 @@ class FormationFlightPlan(LoiterFlightPlan):
|
||||
return self.join_time - TravelTime.between_points(
|
||||
self.hold.position,
|
||||
self.join.position,
|
||||
GroundSpeed.for_flight(self.flight, self.hold.alt)
|
||||
GroundSpeed.for_flight(self.flight, self.hold.alt),
|
||||
)
|
||||
|
||||
@property
|
||||
@ -399,8 +397,7 @@ class PatrollingFlightPlan(FlightPlan):
|
||||
return self.patrol_start_time
|
||||
return None
|
||||
|
||||
def depart_time_for_waypoint(
|
||||
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||
if waypoint == self.patrol_end:
|
||||
return self.patrol_end_time
|
||||
return None
|
||||
@ -490,8 +487,7 @@ class TarCapFlightPlan(PatrollingFlightPlan):
|
||||
def tot_offset(self) -> timedelta:
|
||||
return -self.lead_time
|
||||
|
||||
def depart_time_for_waypoint(
|
||||
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||
if waypoint == self.patrol_end:
|
||||
return self.patrol_end_time
|
||||
return super().depart_time_for_waypoint(waypoint)
|
||||
@ -542,13 +538,12 @@ class StrikeFlightPlan(FormationFlightPlan):
|
||||
@property
|
||||
def package_speed_waypoints(self) -> Set[FlightWaypoint]:
|
||||
return {
|
||||
self.ingress,
|
||||
self.egress,
|
||||
self.split,
|
||||
self.ingress,
|
||||
self.egress,
|
||||
self.split,
|
||||
} | set(self.targets)
|
||||
|
||||
def speed_between_waypoints(self, a: FlightWaypoint,
|
||||
b: FlightWaypoint) -> Speed:
|
||||
def speed_between_waypoints(self, a: FlightWaypoint, b: FlightWaypoint) -> Speed:
|
||||
# FlightWaypoint is only comparable by identity, so adding
|
||||
# target_area_waypoint to package_speed_waypoints is useless.
|
||||
if b.waypoint_type == FlightWaypointType.TARGET_GROUP_LOC:
|
||||
@ -564,10 +559,12 @@ class StrikeFlightPlan(FormationFlightPlan):
|
||||
|
||||
@property
|
||||
def target_area_waypoint(self) -> FlightWaypoint:
|
||||
return FlightWaypoint(FlightWaypointType.TARGET_GROUP_LOC,
|
||||
self.package.target.position.x,
|
||||
self.package.target.position.y,
|
||||
meters(0))
|
||||
return FlightWaypoint(
|
||||
FlightWaypointType.TARGET_GROUP_LOC,
|
||||
self.package.target.position.x,
|
||||
self.package.target.position.y,
|
||||
meters(0),
|
||||
)
|
||||
|
||||
@property
|
||||
def travel_time_to_target(self) -> timedelta:
|
||||
@ -581,14 +578,15 @@ class StrikeFlightPlan(FormationFlightPlan):
|
||||
# package we need to use the travel time to the same position as
|
||||
# the others.
|
||||
total += self.travel_time_between_waypoints(
|
||||
previous_waypoint, self.target_area_waypoint)
|
||||
previous_waypoint, self.target_area_waypoint
|
||||
)
|
||||
break
|
||||
total += self.travel_time_between_waypoints(previous_waypoint,
|
||||
waypoint)
|
||||
total += self.travel_time_between_waypoints(previous_waypoint, waypoint)
|
||||
else:
|
||||
raise PlanningError(
|
||||
f"Did not find destination waypoint {destination} in "
|
||||
f"waypoints for {self.flight}")
|
||||
f"waypoints for {self.flight}"
|
||||
)
|
||||
return total
|
||||
|
||||
@property
|
||||
@ -597,28 +595,28 @@ class StrikeFlightPlan(FormationFlightPlan):
|
||||
|
||||
@property
|
||||
def join_time(self) -> timedelta:
|
||||
travel_time = self.travel_time_between_waypoints(
|
||||
self.join, self.ingress)
|
||||
travel_time = self.travel_time_between_waypoints(self.join, self.ingress)
|
||||
return self.ingress_time - travel_time
|
||||
|
||||
@property
|
||||
def split_time(self) -> timedelta:
|
||||
travel_time = self.travel_time_between_waypoints(
|
||||
self.egress, self.split)
|
||||
travel_time = self.travel_time_between_waypoints(self.egress, self.split)
|
||||
return self.egress_time + travel_time
|
||||
|
||||
@property
|
||||
def ingress_time(self) -> timedelta:
|
||||
tot = self.package.time_over_target
|
||||
travel_time = self.travel_time_between_waypoints(
|
||||
self.ingress, self.target_area_waypoint)
|
||||
self.ingress, self.target_area_waypoint
|
||||
)
|
||||
return tot - travel_time
|
||||
|
||||
@property
|
||||
def egress_time(self) -> timedelta:
|
||||
tot = self.package.time_over_target
|
||||
travel_time = self.travel_time_between_waypoints(
|
||||
self.target_area_waypoint, self.egress)
|
||||
self.target_area_waypoint, self.egress
|
||||
)
|
||||
return tot + travel_time
|
||||
|
||||
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||
@ -664,7 +662,8 @@ class SweepFlightPlan(LoiterFlightPlan):
|
||||
@property
|
||||
def sweep_start_time(self) -> timedelta:
|
||||
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
|
||||
|
||||
@property
|
||||
@ -678,8 +677,7 @@ class SweepFlightPlan(LoiterFlightPlan):
|
||||
return self.sweep_end_time
|
||||
return None
|
||||
|
||||
def depart_time_for_waypoint(
|
||||
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||
if waypoint == self.hold:
|
||||
return self.push_time
|
||||
return None
|
||||
@ -689,7 +687,7 @@ class SweepFlightPlan(LoiterFlightPlan):
|
||||
return self.sweep_end_time - TravelTime.between_points(
|
||||
self.hold.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:
|
||||
@ -721,8 +719,7 @@ class CustomFlightPlan(FlightPlan):
|
||||
return self.package.time_over_target
|
||||
return None
|
||||
|
||||
def depart_time_for_waypoint(
|
||||
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||
return None
|
||||
|
||||
@property
|
||||
@ -752,9 +749,11 @@ class FlightPlanBuilder:
|
||||
self.threat_zones = self.game.threat_zone_for(not self.is_player)
|
||||
|
||||
def populate_flight_plan(
|
||||
self, flight: Flight,
|
||||
# TODO: Custom targets should be an attribute of the flight.
|
||||
custom_targets: Optional[List[Unit]] = None) -> None:
|
||||
self,
|
||||
flight: Flight,
|
||||
# TODO: Custom targets should be an attribute of the flight.
|
||||
custom_targets: Optional[List[Unit]] = None,
|
||||
) -> None:
|
||||
"""Creates a default flight plan for the given mission."""
|
||||
if flight not in self.package.flights:
|
||||
raise RuntimeError("Flight must be a part of the package")
|
||||
@ -763,8 +762,8 @@ class FlightPlanBuilder:
|
||||
flight.flight_plan = self.generate_flight_plan(flight, custom_targets)
|
||||
|
||||
def generate_flight_plan(
|
||||
self, flight: Flight,
|
||||
custom_targets: Optional[List[Unit]]) -> FlightPlan:
|
||||
self, flight: Flight, custom_targets: Optional[List[Unit]]
|
||||
) -> FlightPlan:
|
||||
# TODO: Flesh out mission types.
|
||||
task = flight.flight_type
|
||||
if task == FlightType.ANTISHIP:
|
||||
@ -791,8 +790,7 @@ class FlightPlanBuilder:
|
||||
return self.generate_sweep(flight)
|
||||
elif task == FlightType.TARCAP:
|
||||
return self.generate_tarcap(flight)
|
||||
raise PlanningError(
|
||||
f"{task} flight plan generation not implemented")
|
||||
raise PlanningError(f"{task} flight plan generation not implemented")
|
||||
|
||||
def regenerate_package_waypoints(self) -> None:
|
||||
# The simple case is where the target is greater than the ingress
|
||||
@ -829,6 +827,7 @@ class FlightPlanBuilder:
|
||||
# | | | |
|
||||
# +--------------+ +---------------+
|
||||
from gen.ato import PackageWaypoints
|
||||
|
||||
target = self.package.target.position
|
||||
|
||||
join_point = self.preferred_join_point()
|
||||
@ -862,10 +861,9 @@ class FlightPlanBuilder:
|
||||
|
||||
def legacy_package_waypoints_impl(self) -> None:
|
||||
from gen.ato import PackageWaypoints
|
||||
ingress_point = self._ingress_point(
|
||||
self._target_heading_to_package_airfield())
|
||||
egress_point = self._egress_point(
|
||||
self._target_heading_to_package_airfield())
|
||||
|
||||
ingress_point = self._ingress_point(self._target_heading_to_package_airfield())
|
||||
egress_point = self._egress_point(self._target_heading_to_package_airfield())
|
||||
join_point = self._rendezvous_point(ingress_point)
|
||||
split_point = self._rendezvous_point(egress_point)
|
||||
self.package.waypoints = PackageWaypoints(
|
||||
@ -877,7 +875,8 @@ class FlightPlanBuilder:
|
||||
|
||||
def preferred_join_point(self) -> Optional[Point]:
|
||||
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):
|
||||
if not self.threat_zones.threatened(point):
|
||||
return point
|
||||
@ -917,9 +916,9 @@ class FlightPlanBuilder:
|
||||
|
||||
targets.append(StrikeTarget(building.category, building))
|
||||
|
||||
return self.strike_flightplan(flight, location,
|
||||
FlightWaypointType.INGRESS_STRIKE,
|
||||
targets)
|
||||
return self.strike_flightplan(
|
||||
flight, location, FlightWaypointType.INGRESS_STRIKE, targets
|
||||
)
|
||||
|
||||
def generate_bai(self, flight: Flight) -> StrikeFlightPlan:
|
||||
"""Generates a BAI flight plan.
|
||||
@ -934,11 +933,11 @@ class FlightPlanBuilder:
|
||||
|
||||
targets: List[StrikeTarget] = []
|
||||
for group in location.groups:
|
||||
targets.append(
|
||||
StrikeTarget(f"{group.name} at {location.name}", group))
|
||||
targets.append(StrikeTarget(f"{group.name} at {location.name}", group))
|
||||
|
||||
return self.strike_flightplan(flight, location,
|
||||
FlightWaypointType.INGRESS_BAI, targets)
|
||||
return self.strike_flightplan(
|
||||
flight, location, FlightWaypointType.INGRESS_BAI, targets
|
||||
)
|
||||
|
||||
def generate_anti_ship(self, flight: Flight) -> StrikeFlightPlan:
|
||||
"""Generates an anti-ship flight plan.
|
||||
@ -960,11 +959,11 @@ class FlightPlanBuilder:
|
||||
|
||||
targets: List[StrikeTarget] = []
|
||||
for group in location.groups:
|
||||
targets.append(
|
||||
StrikeTarget(f"{group.name} at {location.name}", group))
|
||||
targets.append(StrikeTarget(f"{group.name} at {location.name}", group))
|
||||
|
||||
return self.strike_flightplan(flight, location,
|
||||
FlightWaypointType.INGRESS_BAI, targets)
|
||||
return self.strike_flightplan(
|
||||
flight, location, FlightWaypointType.INGRESS_BAI, targets
|
||||
)
|
||||
|
||||
def generate_barcap(self, flight: Flight) -> BarCapFlightPlan:
|
||||
"""Generate a BARCAP flight at a given location.
|
||||
@ -978,10 +977,12 @@ class FlightPlanBuilder:
|
||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||
|
||||
start, end = self.racetrack_for_objective(location, barcap=True)
|
||||
patrol_alt = meters(random.randint(
|
||||
int(self.doctrine.min_patrol_altitude.meters),
|
||||
int(self.doctrine.max_patrol_altitude.meters)
|
||||
))
|
||||
patrol_alt = meters(
|
||||
random.randint(
|
||||
int(self.doctrine.min_patrol_altitude.meters),
|
||||
int(self.doctrine.max_patrol_altitude.meters),
|
||||
)
|
||||
)
|
||||
|
||||
builder = WaypointBuilder(flight, self.game, self.is_player)
|
||||
start, end = builder.race_track(start, end, patrol_alt)
|
||||
@ -992,14 +993,16 @@ class FlightPlanBuilder:
|
||||
patrol_duration=self.doctrine.cap_duration,
|
||||
engagement_distance=self.doctrine.cap_engagement_range,
|
||||
takeoff=builder.takeoff(flight.departure),
|
||||
nav_to=builder.nav_path(flight.departure.position, start.position,
|
||||
patrol_alt),
|
||||
nav_from=builder.nav_path(end.position, flight.arrival.position,
|
||||
patrol_alt),
|
||||
nav_to=builder.nav_path(
|
||||
flight.departure.position, start.position, patrol_alt
|
||||
),
|
||||
nav_from=builder.nav_path(
|
||||
end.position, flight.arrival.position, patrol_alt
|
||||
),
|
||||
patrol_start=start,
|
||||
patrol_end=end,
|
||||
land=builder.land(flight.arrival),
|
||||
divert=builder.divert(flight.divert)
|
||||
divert=builder.divert(flight.divert),
|
||||
)
|
||||
|
||||
def generate_sweep(self, flight: Flight) -> SweepFlightPlan:
|
||||
@ -1012,12 +1015,10 @@ class FlightPlanBuilder:
|
||||
target = self.package.target.position
|
||||
|
||||
heading = self.package.waypoints.join.heading_between_point(target)
|
||||
start = target.point_from_heading(heading,
|
||||
-self.doctrine.sweep_distance.meters)
|
||||
start = target.point_from_heading(heading, -self.doctrine.sweep_distance.meters)
|
||||
|
||||
builder = WaypointBuilder(flight, self.game, self.is_player)
|
||||
start, end = builder.sweep(start, target,
|
||||
self.doctrine.ingress_altitude)
|
||||
start, end = builder.sweep(start, target, self.doctrine.ingress_altitude)
|
||||
|
||||
hold = builder.hold(self._hold_point(flight))
|
||||
|
||||
@ -1028,18 +1029,21 @@ class FlightPlanBuilder:
|
||||
takeoff=builder.takeoff(flight.departure),
|
||||
hold=hold,
|
||||
hold_duration=timedelta(minutes=5),
|
||||
nav_to=builder.nav_path(hold.position, start.position,
|
||||
self.doctrine.ingress_altitude),
|
||||
nav_from=builder.nav_path(end.position, flight.arrival.position,
|
||||
self.doctrine.ingress_altitude),
|
||||
nav_to=builder.nav_path(
|
||||
hold.position, start.position, self.doctrine.ingress_altitude
|
||||
),
|
||||
nav_from=builder.nav_path(
|
||||
end.position, flight.arrival.position, self.doctrine.ingress_altitude
|
||||
),
|
||||
sweep_start=start,
|
||||
sweep_end=end,
|
||||
land=builder.land(flight.arrival),
|
||||
divert=builder.divert(flight.divert)
|
||||
divert=builder.divert(flight.divert),
|
||||
)
|
||||
|
||||
def racetrack_for_objective(self, location: MissionTarget,
|
||||
barcap: bool) -> Tuple[Point, Point]:
|
||||
def racetrack_for_objective(
|
||||
self, location: MissionTarget, barcap: bool
|
||||
) -> Tuple[Point, Point]:
|
||||
closest_cache = ObjectiveDistanceCache.get_closest_airfields(location)
|
||||
for airfield in closest_cache.operational_airfields:
|
||||
# If the mission is a BARCAP of an enemy airfield, find the *next*
|
||||
@ -1052,20 +1056,21 @@ class FlightPlanBuilder:
|
||||
else:
|
||||
raise PlanningError("Could not find any enemy airfields")
|
||||
|
||||
heading = location.position.heading_between_point(
|
||||
closest_airfield.position
|
||||
)
|
||||
heading = location.position.heading_between_point(closest_airfield.position)
|
||||
|
||||
position = ShapelyPoint(self.package.target.position.x,
|
||||
self.package.target.position.y)
|
||||
position = ShapelyPoint(
|
||||
self.package.target.position.x, self.package.target.position.y
|
||||
)
|
||||
|
||||
if barcap:
|
||||
# BARCAPs should remain far enough back from the enemy that their
|
||||
# commit range does not enter the enemy's threat zone. Include a 5nm
|
||||
# buffer.
|
||||
distance_to_no_fly = meters(
|
||||
position.distance(self.threat_zones.all)
|
||||
) - self.doctrine.cap_engagement_range - nautical_miles(5)
|
||||
distance_to_no_fly = (
|
||||
meters(position.distance(self.threat_zones.all))
|
||||
- self.doctrine.cap_engagement_range
|
||||
- nautical_miles(5)
|
||||
)
|
||||
else:
|
||||
# Other race tracks (TARCAPs, currently) just try to keep some
|
||||
# distance from the nearest enemy airbase, but since they are by
|
||||
@ -1075,28 +1080,31 @@ class FlightPlanBuilder:
|
||||
distance_to_airfield = meters(
|
||||
closest_airfield.position.distance_to_point(
|
||||
self.package.target.position
|
||||
))
|
||||
)
|
||||
)
|
||||
distance_to_no_fly = distance_to_airfield - min_distance_from_enemy
|
||||
|
||||
min_cap_distance = min(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)
|
||||
min_cap_distance = min(
|
||||
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
|
||||
)
|
||||
|
||||
end = location.position.point_from_heading(
|
||||
heading,
|
||||
random.randint(int(min_cap_distance.meters),
|
||||
int(max_cap_distance.meters))
|
||||
random.randint(int(min_cap_distance.meters), int(max_cap_distance.meters)),
|
||||
)
|
||||
diameter = random.randint(
|
||||
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)
|
||||
return start, end
|
||||
|
||||
def racetrack_for_frontline(self, origin: Point,
|
||||
front_line: FrontLine) -> Tuple[Point, Point]:
|
||||
def racetrack_for_frontline(
|
||||
self, origin: Point, front_line: FrontLine
|
||||
) -> Tuple[Point, Point]:
|
||||
ally_cp, enemy_cp = front_line.control_points
|
||||
|
||||
# Find targets waypoints
|
||||
@ -1105,8 +1113,10 @@ class FlightPlanBuilder:
|
||||
)
|
||||
center = ingress.point_from_heading(heading, distance / 2)
|
||||
orbit_center = center.point_from_heading(
|
||||
heading - 90, random.randint(int(nautical_miles(6).meters),
|
||||
int(nautical_miles(15).meters))
|
||||
heading - 90,
|
||||
random.randint(
|
||||
int(nautical_miles(6).meters), int(nautical_miles(15).meters)
|
||||
),
|
||||
)
|
||||
|
||||
combat_width = distance / 2
|
||||
@ -1132,18 +1142,21 @@ class FlightPlanBuilder:
|
||||
location = self.package.target
|
||||
|
||||
patrol_alt = meters(
|
||||
random.randint(int(self.doctrine.min_patrol_altitude.meters),
|
||||
int(self.doctrine.max_patrol_altitude.meters)))
|
||||
random.randint(
|
||||
int(self.doctrine.min_patrol_altitude.meters),
|
||||
int(self.doctrine.max_patrol_altitude.meters),
|
||||
)
|
||||
)
|
||||
|
||||
# Create points
|
||||
builder = WaypointBuilder(flight, self.game, self.is_player)
|
||||
|
||||
if isinstance(location, FrontLine):
|
||||
orbit0p, orbit1p = self.racetrack_for_frontline(
|
||||
flight.departure.position, location)
|
||||
flight.departure.position, location
|
||||
)
|
||||
else:
|
||||
orbit0p, orbit1p = self.racetrack_for_objective(location,
|
||||
barcap=False)
|
||||
orbit0p, orbit1p = self.racetrack_for_objective(location, barcap=False)
|
||||
|
||||
start, end = builder.race_track(orbit0p, orbit1p, patrol_alt)
|
||||
return TarCapFlightPlan(
|
||||
@ -1157,18 +1170,17 @@ class FlightPlanBuilder:
|
||||
patrol_duration=self.doctrine.cap_duration,
|
||||
engagement_distance=self.doctrine.cap_engagement_range,
|
||||
takeoff=builder.takeoff(flight.departure),
|
||||
nav_to=builder.nav_path(flight.departure.position, orbit0p,
|
||||
patrol_alt),
|
||||
nav_from=builder.nav_path(orbit1p, flight.arrival.position,
|
||||
patrol_alt),
|
||||
nav_to=builder.nav_path(flight.departure.position, orbit0p, patrol_alt),
|
||||
nav_from=builder.nav_path(orbit1p, flight.arrival.position, patrol_alt),
|
||||
patrol_start=start,
|
||||
patrol_end=end,
|
||||
land=builder.land(flight.arrival),
|
||||
divert=builder.divert(flight.divert)
|
||||
divert=builder.divert(flight.divert),
|
||||
)
|
||||
|
||||
def generate_dead(self, flight: Flight,
|
||||
custom_targets: Optional[List[Unit]]) -> StrikeFlightPlan:
|
||||
def generate_dead(
|
||||
self, flight: Flight, custom_targets: Optional[List[Unit]]
|
||||
) -> StrikeFlightPlan:
|
||||
"""Generate a DEAD flight at a given location.
|
||||
|
||||
Args:
|
||||
@ -1181,7 +1193,8 @@ class FlightPlanBuilder:
|
||||
is_sam = isinstance(location, SamGroundObject)
|
||||
if not is_ewr and not is_sam:
|
||||
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)
|
||||
|
||||
# TODO: Unify these.
|
||||
@ -1193,8 +1206,9 @@ class FlightPlanBuilder:
|
||||
for target in custom_targets:
|
||||
targets.append(StrikeTarget(location.name, target))
|
||||
|
||||
return self.strike_flightplan(flight, location,
|
||||
FlightWaypointType.INGRESS_DEAD, targets)
|
||||
return self.strike_flightplan(
|
||||
flight, location, FlightWaypointType.INGRESS_DEAD, targets
|
||||
)
|
||||
|
||||
def generate_oca_strike(self, flight: Flight) -> StrikeFlightPlan:
|
||||
"""Generate an OCA Strike flight plan at a given location.
|
||||
@ -1207,11 +1221,13 @@ class FlightPlanBuilder:
|
||||
if not isinstance(location, Airfield):
|
||||
logging.exception(
|
||||
f"Invalid Objective Location for OCA Strike flight "
|
||||
f"{flight=} at {location=}.")
|
||||
f"{flight=} at {location=}."
|
||||
)
|
||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||
|
||||
return self.strike_flightplan(flight, location,
|
||||
FlightWaypointType.INGRESS_OCA_AIRCRAFT)
|
||||
return self.strike_flightplan(
|
||||
flight, location, FlightWaypointType.INGRESS_OCA_AIRCRAFT
|
||||
)
|
||||
|
||||
def generate_runway_attack(self, flight: Flight) -> StrikeFlightPlan:
|
||||
"""Generate a runway attack flight plan at a given location.
|
||||
@ -1224,14 +1240,17 @@ class FlightPlanBuilder:
|
||||
if not isinstance(location, Airfield):
|
||||
logging.exception(
|
||||
f"Invalid Objective Location for runway bombing flight "
|
||||
f"{flight=} at {location=}.")
|
||||
f"{flight=} at {location=}."
|
||||
)
|
||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||
|
||||
return self.strike_flightplan(flight, location,
|
||||
FlightWaypointType.INGRESS_OCA_RUNWAY)
|
||||
return self.strike_flightplan(
|
||||
flight, location, FlightWaypointType.INGRESS_OCA_RUNWAY
|
||||
)
|
||||
|
||||
def generate_sead(self, flight: Flight,
|
||||
custom_targets: Optional[List[Unit]]) -> StrikeFlightPlan:
|
||||
def generate_sead(
|
||||
self, flight: Flight, custom_targets: Optional[List[Unit]]
|
||||
) -> StrikeFlightPlan:
|
||||
"""Generate a SEAD flight at a given location.
|
||||
|
||||
Args:
|
||||
@ -1249,16 +1268,19 @@ class FlightPlanBuilder:
|
||||
for target in custom_targets:
|
||||
targets.append(StrikeTarget(location.name, target))
|
||||
|
||||
return self.strike_flightplan(flight, location,
|
||||
FlightWaypointType.INGRESS_SEAD, targets)
|
||||
return self.strike_flightplan(
|
||||
flight, location, FlightWaypointType.INGRESS_SEAD, targets
|
||||
)
|
||||
|
||||
def generate_escort(self, flight: Flight) -> StrikeFlightPlan:
|
||||
assert self.package.waypoints is not None
|
||||
|
||||
builder = WaypointBuilder(flight, self.game, self.is_player)
|
||||
ingress, target, egress = builder.escort(
|
||||
self.package.waypoints.ingress, self.package.target,
|
||||
self.package.waypoints.egress)
|
||||
self.package.waypoints.ingress,
|
||||
self.package.target,
|
||||
self.package.waypoints.egress,
|
||||
)
|
||||
hold = builder.hold(self._hold_point(flight))
|
||||
join = builder.join(self.package.waypoints.join)
|
||||
split = builder.split(self.package.waypoints.split)
|
||||
@ -1269,17 +1291,19 @@ class FlightPlanBuilder:
|
||||
takeoff=builder.takeoff(flight.departure),
|
||||
hold=hold,
|
||||
hold_duration=timedelta(minutes=5),
|
||||
nav_to=builder.nav_path(hold.position, join.position,
|
||||
self.doctrine.ingress_altitude),
|
||||
nav_to=builder.nav_path(
|
||||
hold.position, join.position, self.doctrine.ingress_altitude
|
||||
),
|
||||
join=join,
|
||||
ingress=ingress,
|
||||
targets=[target],
|
||||
egress=egress,
|
||||
split=split,
|
||||
nav_from=builder.nav_path(split.position, flight.arrival.position,
|
||||
self.doctrine.ingress_altitude),
|
||||
nav_from=builder.nav_path(
|
||||
split.position, flight.arrival.position, self.doctrine.ingress_altitude
|
||||
),
|
||||
land=builder.land(flight.arrival),
|
||||
divert=builder.divert(flight.divert)
|
||||
divert=builder.divert(flight.divert),
|
||||
)
|
||||
|
||||
def generate_cas(self, flight: Flight) -> CasFlightPlan:
|
||||
@ -1294,8 +1318,7 @@ class FlightPlanBuilder:
|
||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||
|
||||
ingress, heading, distance = Conflict.frontline_vector(
|
||||
location.control_points[0], location.control_points[1],
|
||||
self.game.theater
|
||||
location.control_points[0], location.control_points[1], self.game.theater
|
||||
)
|
||||
center = ingress.point_from_heading(heading, distance / 2)
|
||||
egress = ingress.point_from_heading(heading, distance)
|
||||
@ -1312,22 +1335,26 @@ class FlightPlanBuilder:
|
||||
flight=flight,
|
||||
patrol_duration=self.doctrine.cas_duration,
|
||||
takeoff=builder.takeoff(flight.departure),
|
||||
nav_to=builder.nav_path(flight.departure.position, ingress,
|
||||
self.doctrine.ingress_altitude),
|
||||
nav_from=builder.nav_path(egress, flight.arrival.position,
|
||||
self.doctrine.ingress_altitude),
|
||||
patrol_start=builder.ingress(FlightWaypointType.INGRESS_CAS,
|
||||
ingress, location),
|
||||
nav_to=builder.nav_path(
|
||||
flight.departure.position, ingress, self.doctrine.ingress_altitude
|
||||
),
|
||||
nav_from=builder.nav_path(
|
||||
egress, flight.arrival.position, self.doctrine.ingress_altitude
|
||||
),
|
||||
patrol_start=builder.ingress(
|
||||
FlightWaypointType.INGRESS_CAS, ingress, location
|
||||
),
|
||||
engagement_distance=meters(FRONTLINE_LENGTH) / 2,
|
||||
target=builder.cas(center),
|
||||
patrol_end=builder.egress(egress, location),
|
||||
land=builder.land(flight.arrival),
|
||||
divert=builder.divert(flight.divert)
|
||||
divert=builder.divert(flight.divert),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def target_waypoint(flight: Flight, builder: WaypointBuilder,
|
||||
target: StrikeTarget) -> FlightWaypoint:
|
||||
def target_waypoint(
|
||||
flight: Flight, builder: WaypointBuilder, target: StrikeTarget
|
||||
) -> FlightWaypoint:
|
||||
if flight.flight_type in {FlightType.ANTISHIP, FlightType.BAI}:
|
||||
return builder.bai_group(target)
|
||||
elif flight.flight_type == FlightType.DEAD:
|
||||
@ -1338,8 +1365,9 @@ class FlightPlanBuilder:
|
||||
return builder.strike_point(target)
|
||||
|
||||
@staticmethod
|
||||
def target_area_waypoint(flight: Flight, location: MissionTarget,
|
||||
builder: WaypointBuilder) -> FlightWaypoint:
|
||||
def target_area_waypoint(
|
||||
flight: Flight, location: MissionTarget, builder: WaypointBuilder
|
||||
) -> FlightWaypoint:
|
||||
if flight.flight_type == FlightType.DEAD:
|
||||
return builder.dead_area(location)
|
||||
elif flight.flight_type == FlightType.SEAD:
|
||||
@ -1360,12 +1388,14 @@ class FlightPlanBuilder:
|
||||
# If the origin airfield is closer to the target than the join
|
||||
# point, plan the hold point such that it retreats from the origin
|
||||
# airfield.
|
||||
return join.point_from_heading(target.heading_between_point(origin),
|
||||
self.doctrine.push_distance.meters)
|
||||
return join.point_from_heading(
|
||||
target.heading_between_point(origin), self.doctrine.push_distance.meters
|
||||
)
|
||||
|
||||
heading_to_join = origin.heading_between_point(join)
|
||||
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))
|
||||
if hold_distance >= self.doctrine.push_distance:
|
||||
# Hold point is between the origin airfield and the join point and
|
||||
@ -1379,26 +1409,27 @@ class FlightPlanBuilder:
|
||||
# properly.
|
||||
origin_to_join = origin.distance_to_point(join)
|
||||
cos_theta = (
|
||||
(self.doctrine.hold_distance.meters ** 2 +
|
||||
origin_to_join ** 2 -
|
||||
self.doctrine.join_distance.meters ** 2) /
|
||||
(2 * self.doctrine.hold_distance.meters * origin_to_join)
|
||||
)
|
||||
self.doctrine.hold_distance.meters ** 2
|
||||
+ origin_to_join ** 2
|
||||
- self.doctrine.join_distance.meters ** 2
|
||||
) / (2 * self.doctrine.hold_distance.meters * origin_to_join)
|
||||
try:
|
||||
theta = math.acos(cos_theta)
|
||||
except ValueError:
|
||||
# No solution that maintains hold and join distances. Extend the
|
||||
# hold point away from the target.
|
||||
return origin.point_from_heading(
|
||||
target.heading_between_point(origin),
|
||||
self.doctrine.hold_distance.meters)
|
||||
target.heading_between_point(origin), self.doctrine.hold_distance.meters
|
||||
)
|
||||
|
||||
return origin.point_from_heading(heading_to_join - theta,
|
||||
self.doctrine.hold_distance.meters)
|
||||
return origin.point_from_heading(
|
||||
heading_to_join - theta, self.doctrine.hold_distance.meters
|
||||
)
|
||||
|
||||
# TODO: Make a model for the waypoint builder and use that in the UI.
|
||||
def generate_rtb_waypoint(self, flight: Flight,
|
||||
arrival: ControlPoint) -> FlightWaypoint:
|
||||
def generate_rtb_waypoint(
|
||||
self, flight: Flight, arrival: ControlPoint
|
||||
) -> FlightWaypoint:
|
||||
"""Generate RTB landing point.
|
||||
|
||||
Args:
|
||||
@ -1409,20 +1440,23 @@ class FlightPlanBuilder:
|
||||
return builder.land(arrival)
|
||||
|
||||
def strike_flightplan(
|
||||
self, flight: Flight, location: MissionTarget,
|
||||
ingress_type: FlightWaypointType,
|
||||
targets: Optional[List[StrikeTarget]] = None) -> StrikeFlightPlan:
|
||||
self,
|
||||
flight: Flight,
|
||||
location: MissionTarget,
|
||||
ingress_type: FlightWaypointType,
|
||||
targets: Optional[List[StrikeTarget]] = None,
|
||||
) -> StrikeFlightPlan:
|
||||
assert self.package.waypoints is not None
|
||||
builder = WaypointBuilder(flight, self.game, self.is_player, targets)
|
||||
|
||||
target_waypoints: List[FlightWaypoint] = []
|
||||
if targets is not None:
|
||||
for target in targets:
|
||||
target_waypoints.append(
|
||||
self.target_waypoint(flight, builder, target))
|
||||
target_waypoints.append(self.target_waypoint(flight, builder, target))
|
||||
else:
|
||||
target_waypoints.append(
|
||||
self.target_area_waypoint(flight, location, builder))
|
||||
self.target_area_waypoint(flight, location, builder)
|
||||
)
|
||||
|
||||
hold = builder.hold(self._hold_point(flight))
|
||||
join = builder.join(self.package.waypoints.join)
|
||||
@ -1434,32 +1468,38 @@ class FlightPlanBuilder:
|
||||
takeoff=builder.takeoff(flight.departure),
|
||||
hold=hold,
|
||||
hold_duration=timedelta(minutes=5),
|
||||
nav_to=builder.nav_path(hold.position, join.position,
|
||||
self.doctrine.ingress_altitude),
|
||||
nav_to=builder.nav_path(
|
||||
hold.position, join.position, self.doctrine.ingress_altitude
|
||||
),
|
||||
join=join,
|
||||
ingress=builder.ingress(ingress_type,
|
||||
self.package.waypoints.ingress, location),
|
||||
ingress=builder.ingress(
|
||||
ingress_type, self.package.waypoints.ingress, location
|
||||
),
|
||||
targets=target_waypoints,
|
||||
egress=builder.egress(self.package.waypoints.egress, location),
|
||||
split=split,
|
||||
nav_from=builder.nav_path(split.position, flight.arrival.position,
|
||||
self.doctrine.ingress_altitude),
|
||||
nav_from=builder.nav_path(
|
||||
split.position, flight.arrival.position, self.doctrine.ingress_altitude
|
||||
),
|
||||
land=builder.land(flight.arrival),
|
||||
divert=builder.divert(flight.divert)
|
||||
divert=builder.divert(flight.divert),
|
||||
)
|
||||
|
||||
def _retreating_rendezvous_point(self, attack_transition: Point) -> Point:
|
||||
"""Creates a rendezvous point that retreats from the origin airfield."""
|
||||
return attack_transition.point_from_heading(
|
||||
self.package.target.position.heading_between_point(
|
||||
self.package_airfield().position),
|
||||
self.doctrine.join_distance.meters)
|
||||
self.package_airfield().position
|
||||
),
|
||||
self.doctrine.join_distance.meters,
|
||||
)
|
||||
|
||||
def _advancing_rendezvous_point(self, attack_transition: Point) -> Point:
|
||||
"""Creates a rendezvous point that advances toward the target."""
|
||||
heading = self._heading_to_package_airfield(attack_transition)
|
||||
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:
|
||||
transition_target_distance = attack_transition.distance_to_point(
|
||||
@ -1514,13 +1554,9 @@ class FlightPlanBuilder:
|
||||
# The package airfield is either the flight's airfield (when there is no
|
||||
# package) or the closest airfield to the objective that is the
|
||||
# departure airfield for some flight in the package.
|
||||
cache = ObjectiveDistanceCache.get_closest_airfields(
|
||||
self.package.target
|
||||
)
|
||||
cache = ObjectiveDistanceCache.get_closest_airfields(self.package.target)
|
||||
for airfield in cache.operational_airfields:
|
||||
for flight in self.package.flights:
|
||||
if flight.departure == airfield:
|
||||
return airfield
|
||||
raise RuntimeError(
|
||||
"Could not find any airfield assigned to this package"
|
||||
)
|
||||
raise RuntimeError("Could not find any airfield assigned to this package")
|
||||
|
||||
@ -23,7 +23,6 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class GroundSpeed:
|
||||
|
||||
@classmethod
|
||||
def for_flight(cls, flight: Flight, altitude: Distance) -> Speed:
|
||||
if not issubclass(flight.unit_type, FlyingType):
|
||||
@ -55,13 +54,11 @@ class TravelTime:
|
||||
def between_points(a: Point, b: Point, speed: Speed) -> timedelta:
|
||||
error_factor = 1.1
|
||||
distance = meters(a.distance_to_point(b))
|
||||
return timedelta(
|
||||
hours=distance.nautical_miles / speed.knots * error_factor)
|
||||
return timedelta(hours=distance.nautical_miles / speed.knots * error_factor)
|
||||
|
||||
|
||||
# TODO: Most if not all of this should move into FlightPlan.
|
||||
class TotEstimator:
|
||||
|
||||
def __init__(self, package: Package) -> None:
|
||||
self.package = package
|
||||
|
||||
@ -75,9 +72,9 @@ class TotEstimator:
|
||||
return startup_time
|
||||
|
||||
def earliest_tot(self) -> timedelta:
|
||||
earliest_tot = max((
|
||||
self.earliest_tot_for_flight(f) for f in self.package.flights
|
||||
))
|
||||
earliest_tot = max(
|
||||
(self.earliest_tot_for_flight(f) for f in self.package.flights)
|
||||
)
|
||||
|
||||
# Trim microseconds. DCS doesn't handle sub-second resolution for tasks,
|
||||
# and they're not interesting from a mission planning perspective so we
|
||||
|
||||
@ -36,8 +36,13 @@ class StrikeTarget:
|
||||
|
||||
|
||||
class WaypointBuilder:
|
||||
def __init__(self, flight: Flight, game: Game, player: bool,
|
||||
targets: Optional[List[StrikeTarget]] = None) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
flight: Flight,
|
||||
game: Game,
|
||||
player: bool,
|
||||
targets: Optional[List[StrikeTarget]] = None,
|
||||
) -> None:
|
||||
self.flight = flight
|
||||
self.conditions = game.conditions
|
||||
self.doctrine = game.faction_for(player).doctrine
|
||||
@ -65,9 +70,7 @@ class WaypointBuilder:
|
||||
FlightWaypointType.NAV,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(
|
||||
500
|
||||
) if self.is_helo else self.doctrine.rendezvous_altitude
|
||||
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
|
||||
)
|
||||
waypoint.name = "NAV"
|
||||
waypoint.alt_type = "BARO"
|
||||
@ -75,10 +78,7 @@ class WaypointBuilder:
|
||||
waypoint.pretty_name = "Enter theater"
|
||||
else:
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.TAKEOFF,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(0)
|
||||
FlightWaypointType.TAKEOFF, position.x, position.y, meters(0)
|
||||
)
|
||||
waypoint.name = "TAKEOFF"
|
||||
waypoint.alt_type = "RADIO"
|
||||
@ -98,9 +98,7 @@ class WaypointBuilder:
|
||||
FlightWaypointType.NAV,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(
|
||||
500
|
||||
) if self.is_helo else self.doctrine.rendezvous_altitude
|
||||
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
|
||||
)
|
||||
waypoint.name = "NAV"
|
||||
waypoint.alt_type = "BARO"
|
||||
@ -108,10 +106,7 @@ class WaypointBuilder:
|
||||
waypoint.pretty_name = "Exit theater"
|
||||
else:
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.LANDING_POINT,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(0)
|
||||
FlightWaypointType.LANDING_POINT, position.x, position.y, meters(0)
|
||||
)
|
||||
waypoint.name = "LANDING"
|
||||
waypoint.alt_type = "RADIO"
|
||||
@ -119,8 +114,7 @@ class WaypointBuilder:
|
||||
waypoint.pretty_name = "Land"
|
||||
return waypoint
|
||||
|
||||
def divert(self,
|
||||
divert: Optional[ControlPoint]) -> Optional[FlightWaypoint]:
|
||||
def divert(self, divert: Optional[ControlPoint]) -> Optional[FlightWaypoint]:
|
||||
"""Create divert waypoint for the given arrival airfield or carrier.
|
||||
|
||||
Args:
|
||||
@ -141,10 +135,7 @@ class WaypointBuilder:
|
||||
altitude_type = "RADIO"
|
||||
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.DIVERT,
|
||||
position.x,
|
||||
position.y,
|
||||
altitude
|
||||
FlightWaypointType.DIVERT, position.x, position.y, altitude
|
||||
)
|
||||
waypoint.alt_type = altitude_type
|
||||
waypoint.name = "DIVERT"
|
||||
@ -158,9 +149,7 @@ class WaypointBuilder:
|
||||
FlightWaypointType.LOITER,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(
|
||||
500
|
||||
) if self.is_helo else self.doctrine.rendezvous_altitude
|
||||
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
|
||||
)
|
||||
waypoint.pretty_name = "Hold"
|
||||
waypoint.description = "Wait until push time"
|
||||
@ -172,9 +161,7 @@ class WaypointBuilder:
|
||||
FlightWaypointType.JOIN,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(
|
||||
500
|
||||
) if self.is_helo else self.doctrine.ingress_altitude
|
||||
meters(500) if self.is_helo else self.doctrine.ingress_altitude,
|
||||
)
|
||||
waypoint.pretty_name = "Join"
|
||||
waypoint.description = "Rendezvous with package"
|
||||
@ -186,24 +173,24 @@ class WaypointBuilder:
|
||||
FlightWaypointType.SPLIT,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(
|
||||
500
|
||||
) if self.is_helo else self.doctrine.ingress_altitude
|
||||
meters(500) if self.is_helo else self.doctrine.ingress_altitude,
|
||||
)
|
||||
waypoint.pretty_name = "Split"
|
||||
waypoint.description = "Depart from package"
|
||||
waypoint.name = "SPLIT"
|
||||
return waypoint
|
||||
|
||||
def ingress(self, ingress_type: FlightWaypointType, position: Point,
|
||||
objective: MissionTarget) -> FlightWaypoint:
|
||||
def ingress(
|
||||
self,
|
||||
ingress_type: FlightWaypointType,
|
||||
position: Point,
|
||||
objective: MissionTarget,
|
||||
) -> FlightWaypoint:
|
||||
waypoint = FlightWaypoint(
|
||||
ingress_type,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(
|
||||
500
|
||||
) if self.is_helo else self.doctrine.ingress_altitude
|
||||
meters(500) if self.is_helo else self.doctrine.ingress_altitude,
|
||||
)
|
||||
waypoint.pretty_name = "INGRESS on " + objective.name
|
||||
waypoint.description = "INGRESS on " + objective.name
|
||||
@ -217,9 +204,7 @@ class WaypointBuilder:
|
||||
FlightWaypointType.EGRESS,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(
|
||||
500
|
||||
) if self.is_helo else self.doctrine.ingress_altitude
|
||||
meters(500) if self.is_helo else self.doctrine.ingress_altitude,
|
||||
)
|
||||
waypoint.pretty_name = "EGRESS from " + target.name
|
||||
waypoint.description = "EGRESS from " + target.name
|
||||
@ -244,7 +229,7 @@ class WaypointBuilder:
|
||||
FlightWaypointType.TARGET_POINT,
|
||||
target.target.position.x,
|
||||
target.target.position.y,
|
||||
meters(0)
|
||||
meters(0),
|
||||
)
|
||||
waypoint.description = description
|
||||
waypoint.pretty_name = description
|
||||
@ -269,13 +254,14 @@ class WaypointBuilder:
|
||||
return self._target_area(f"ATTACK {target.name}", target, flyover=True)
|
||||
|
||||
@staticmethod
|
||||
def _target_area(name: str, location: MissionTarget,
|
||||
flyover: bool = False) -> FlightWaypoint:
|
||||
def _target_area(
|
||||
name: str, location: MissionTarget, flyover: bool = False
|
||||
) -> FlightWaypoint:
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.TARGET_GROUP_LOC,
|
||||
location.position.x,
|
||||
location.position.y,
|
||||
meters(0)
|
||||
meters(0),
|
||||
)
|
||||
waypoint.description = name
|
||||
waypoint.pretty_name = name
|
||||
@ -300,7 +286,7 @@ class WaypointBuilder:
|
||||
FlightWaypointType.CAS,
|
||||
position.x,
|
||||
position.y,
|
||||
meters(500) if self.is_helo else meters(1000)
|
||||
meters(500) if self.is_helo else meters(1000),
|
||||
)
|
||||
waypoint.alt_type = "RADIO"
|
||||
waypoint.description = "Provide CAS"
|
||||
@ -317,10 +303,7 @@ class WaypointBuilder:
|
||||
altitude: Altitude of the racetrack.
|
||||
"""
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.PATROL_TRACK,
|
||||
position.x,
|
||||
position.y,
|
||||
altitude
|
||||
FlightWaypointType.PATROL_TRACK, position.x, position.y, altitude
|
||||
)
|
||||
waypoint.name = "RACETRACK START"
|
||||
waypoint.description = "Orbit between this point and the next point"
|
||||
@ -336,18 +319,16 @@ class WaypointBuilder:
|
||||
altitude: Altitude of the racetrack.
|
||||
"""
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.PATROL,
|
||||
position.x,
|
||||
position.y,
|
||||
altitude
|
||||
FlightWaypointType.PATROL, position.x, position.y, altitude
|
||||
)
|
||||
waypoint.name = "RACETRACK END"
|
||||
waypoint.description = "Orbit between this point and the previous point"
|
||||
waypoint.pretty_name = "Race-track end"
|
||||
return waypoint
|
||||
|
||||
def race_track(self, start: Point, end: Point,
|
||||
altitude: Distance) -> Tuple[FlightWaypoint, FlightWaypoint]:
|
||||
def race_track(
|
||||
self, start: Point, end: Point, altitude: Distance
|
||||
) -> Tuple[FlightWaypoint, FlightWaypoint]:
|
||||
"""Creates two waypoint for a racetrack orbit.
|
||||
|
||||
Args:
|
||||
@ -355,8 +336,10 @@ class WaypointBuilder:
|
||||
end: The ending racetrack waypoint.
|
||||
altitude: The racetrack altitude.
|
||||
"""
|
||||
return (self.race_track_start(start, altitude),
|
||||
self.race_track_end(end, altitude))
|
||||
return (
|
||||
self.race_track_start(start, altitude),
|
||||
self.race_track_end(end, altitude),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def sweep_start(position: Point, altitude: Distance) -> FlightWaypoint:
|
||||
@ -367,10 +350,7 @@ class WaypointBuilder:
|
||||
altitude: Altitude of the sweep in meters.
|
||||
"""
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.INGRESS_SWEEP,
|
||||
position.x,
|
||||
position.y,
|
||||
altitude
|
||||
FlightWaypointType.INGRESS_SWEEP, position.x, position.y, altitude
|
||||
)
|
||||
waypoint.name = "SWEEP START"
|
||||
waypoint.description = "Proceed to the target and engage enemy aircraft"
|
||||
@ -386,18 +366,16 @@ class WaypointBuilder:
|
||||
altitude: Altitude of the sweep in meters.
|
||||
"""
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.EGRESS,
|
||||
position.x,
|
||||
position.y,
|
||||
altitude
|
||||
FlightWaypointType.EGRESS, position.x, position.y, altitude
|
||||
)
|
||||
waypoint.name = "SWEEP END"
|
||||
waypoint.description = "End of sweep"
|
||||
waypoint.pretty_name = "Sweep end"
|
||||
return waypoint
|
||||
|
||||
def sweep(self, start: Point, end: Point,
|
||||
altitude: Distance) -> Tuple[FlightWaypoint, FlightWaypoint]:
|
||||
def sweep(
|
||||
self, start: Point, end: Point, altitude: Distance
|
||||
) -> Tuple[FlightWaypoint, FlightWaypoint]:
|
||||
"""Creates two waypoint for a racetrack orbit.
|
||||
|
||||
Args:
|
||||
@ -405,11 +383,11 @@ class WaypointBuilder:
|
||||
end: The end of the sweep.
|
||||
altitude: The sweep altitude.
|
||||
"""
|
||||
return (self.sweep_start(start, altitude),
|
||||
self.sweep_end(end, altitude))
|
||||
return (self.sweep_start(start, altitude), self.sweep_end(end, altitude))
|
||||
|
||||
def escort(self, ingress: Point, target: MissionTarget, egress: Point) -> \
|
||||
Tuple[FlightWaypoint, FlightWaypoint, FlightWaypoint]:
|
||||
def escort(
|
||||
self, ingress: Point, target: MissionTarget, egress: Point
|
||||
) -> Tuple[FlightWaypoint, FlightWaypoint, FlightWaypoint]:
|
||||
"""Creates the waypoints needed to escort the package.
|
||||
|
||||
Args:
|
||||
@ -423,16 +401,13 @@ class WaypointBuilder:
|
||||
# description in gen.aircraft.JoinPointBuilder), so instead we give
|
||||
# the escort flights a flight plan including the ingress point, target
|
||||
# area, and egress point.
|
||||
ingress = self.ingress(FlightWaypointType.INGRESS_ESCORT, ingress,
|
||||
target)
|
||||
ingress = self.ingress(FlightWaypointType.INGRESS_ESCORT, ingress, target)
|
||||
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.TARGET_GROUP_LOC,
|
||||
target.position.x,
|
||||
target.position.y,
|
||||
meters(
|
||||
500
|
||||
) if self.is_helo else self.doctrine.ingress_altitude
|
||||
meters(500) if self.is_helo else self.doctrine.ingress_altitude,
|
||||
)
|
||||
waypoint.name = "TARGET"
|
||||
waypoint.description = "Escort the package"
|
||||
@ -450,18 +425,14 @@ class WaypointBuilder:
|
||||
altitude: Altitude of the waypoint.
|
||||
"""
|
||||
waypoint = FlightWaypoint(
|
||||
FlightWaypointType.NAV,
|
||||
position.x,
|
||||
position.y,
|
||||
altitude
|
||||
FlightWaypointType.NAV, position.x, position.y, altitude
|
||||
)
|
||||
waypoint.name = "NAV"
|
||||
waypoint.description = "NAV"
|
||||
waypoint.pretty_name = "Nav"
|
||||
return waypoint
|
||||
|
||||
def nav_path(self, a: Point, b: Point,
|
||||
altitude: Distance) -> List[FlightWaypoint]:
|
||||
def nav_path(self, a: Point, b: Point, altitude: Distance) -> List[FlightWaypoint]:
|
||||
path = self.clean_nav_points(self.navmesh.shortest_path(a, b))
|
||||
return [self.nav(self.perturb(p), altitude) for p in path]
|
||||
|
||||
@ -488,10 +459,8 @@ class WaypointBuilder:
|
||||
previous = current
|
||||
current = nxt
|
||||
|
||||
def nav_point_prunable(self, previous: Point, current: Point,
|
||||
nxt: Point) -> bool:
|
||||
previous_threatened = self.threat_zones.path_threatened(previous,
|
||||
current)
|
||||
def nav_point_prunable(self, previous: Point, current: Point, nxt: Point) -> bool:
|
||||
previous_threatened = self.threat_zones.path_threatened(previous, current)
|
||||
next_threatened = self.threat_zones.path_threatened(current, nxt)
|
||||
pruned_threatened = self.threat_zones.path_threatened(previous, nxt)
|
||||
previous_distance = meters(previous.distance_to_point(current))
|
||||
|
||||
@ -15,11 +15,15 @@ class ForcedOptionsGenerator:
|
||||
self.game = game
|
||||
|
||||
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:
|
||||
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:
|
||||
# TODO: Fix settings to use the real type.
|
||||
|
||||
@ -39,12 +39,11 @@ GROUP_SIZES_BY_COMBAT_STANCE = {
|
||||
CombatStance.RETREAT: [2, 4, 6, 8],
|
||||
CombatStance.BREAKTHROUGH: [4, 6, 6, 8],
|
||||
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:
|
||||
|
||||
def __init__(self, role: CombatGroupRole):
|
||||
self.units: List[VehicleType] = []
|
||||
self.role = role
|
||||
@ -60,11 +59,12 @@ class CombatGroup:
|
||||
|
||||
|
||||
class GroundPlanner:
|
||||
|
||||
def __init__(self, cp:ControlPoint, game):
|
||||
def __init__(self, cp: ControlPoint, game):
|
||||
self.cp = cp
|
||||
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.apc_group: List[CombatGroup] = []
|
||||
self.ifv_group: List[CombatGroup] = []
|
||||
@ -80,7 +80,7 @@ class GroundPlanner:
|
||||
|
||||
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]
|
||||
else:
|
||||
self.cp.stance = CombatStance.DEFENSIVE
|
||||
@ -152,12 +152,3 @@ class GroundPlanner:
|
||||
print("For : #" + str(key))
|
||||
for group in self.units_per_cp[key]:
|
||||
print(str(group))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -16,7 +16,6 @@ TYPE_TANKS = [
|
||||
Armor.MBT_M60A3_Patton,
|
||||
Armor.MBT_Merkava_Mk__4,
|
||||
Armor.ZTZ_96B,
|
||||
|
||||
# WW2
|
||||
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G,
|
||||
Armor.MT_Pz_Kpfw_IV_Ausf_H,
|
||||
@ -29,36 +28,30 @@ TYPE_TANKS = [
|
||||
Armor.CT_Cromwell_IV,
|
||||
Armor.HIT_Churchill_VII,
|
||||
Armor.LT_Mk_VII_Tetrarch,
|
||||
|
||||
# Mods
|
||||
frenchpack.DIM__TOYOTA_BLUE,
|
||||
frenchpack.DIM__TOYOTA_GREEN,
|
||||
frenchpack.DIM__TOYOTA_DESERT,
|
||||
frenchpack.DIM__KAMIKAZE,
|
||||
|
||||
frenchpack.AMX_10RCR,
|
||||
frenchpack.AMX_10RCR_SEPAR,
|
||||
frenchpack.AMX_30B2,
|
||||
frenchpack.Leclerc_Serie_XXI,
|
||||
|
||||
]
|
||||
|
||||
TYPE_ATGM = [
|
||||
Armor.ATGM_M1045_HMMWV_TOW,
|
||||
Armor.ATGM_M1134_Stryker,
|
||||
Armor.IFV_BMP_2,
|
||||
|
||||
# WW2 (Tank Destroyers)
|
||||
Armor.M30_Cargo_Carrier,
|
||||
Armor.TD_Jagdpanzer_IV,
|
||||
Armor.TD_Jagdpanther_G1,
|
||||
Armor.TD_M10_GMC,
|
||||
|
||||
# Mods
|
||||
frenchpack.VBAE_CRAB_MMP,
|
||||
frenchpack.VAB_MEPHISTO,
|
||||
frenchpack.TRM_2000_PAMELA,
|
||||
|
||||
]
|
||||
|
||||
TYPE_IFV = [
|
||||
@ -73,17 +66,14 @@ TYPE_IFV = [
|
||||
Armor.IFV_M2A2_Bradley,
|
||||
Armor.IFV_BMD_1,
|
||||
Armor.ZBD_04A,
|
||||
|
||||
# WW2
|
||||
Armor.AC_Sd_Kfz_234_2_Puma,
|
||||
Armor.LAC_M8_Greyhound,
|
||||
Armor.Daimler_Armoured_Car,
|
||||
|
||||
# Mods
|
||||
frenchpack.ERC_90,
|
||||
frenchpack.VBAE_CRAB,
|
||||
frenchpack.VAB_T20_13
|
||||
|
||||
frenchpack.VAB_T20_13,
|
||||
]
|
||||
|
||||
TYPE_APC = [
|
||||
@ -101,16 +91,13 @@ TYPE_APC = [
|
||||
Armor.ARV_BRDM_2,
|
||||
Armor.ARV_BTR_RD,
|
||||
Armor.FDDM_Grad,
|
||||
|
||||
# WW2
|
||||
Armor.APC_M2A1,
|
||||
Armor.APC_Sd_Kfz_251,
|
||||
|
||||
# Mods
|
||||
frenchpack.VAB__50,
|
||||
frenchpack.VBL__50,
|
||||
frenchpack.VBL_AANF1,
|
||||
|
||||
]
|
||||
|
||||
TYPE_ARTILLERY = [
|
||||
@ -125,10 +112,9 @@ TYPE_ARTILLERY = [
|
||||
Artillery.SpGH_Dana,
|
||||
Artillery.SPH_2S19_Msta,
|
||||
Artillery.MLRS_FDDM,
|
||||
|
||||
# WW2
|
||||
Artillery.Sturmpanzer_IV_Brummbär,
|
||||
Artillery.M12_GMC
|
||||
Artillery.M12_GMC,
|
||||
]
|
||||
|
||||
TYPE_LOGI = [
|
||||
@ -147,11 +133,9 @@ TYPE_LOGI = [
|
||||
Unarmed.Willys_MB,
|
||||
Unarmed.Land_Rover_109_S3,
|
||||
Unarmed.Land_Rover_101_FC,
|
||||
|
||||
# Mods
|
||||
frenchpack.VBL,
|
||||
frenchpack.VAB,
|
||||
|
||||
]
|
||||
|
||||
TYPE_INFANTRY = [
|
||||
@ -179,7 +163,6 @@ TYPE_SHORAD = [
|
||||
AirDefence.SAM_SA_13_Strela_10M3_9A35M3,
|
||||
AirDefence.SAM_SA_15_Tor_9A331,
|
||||
AirDefence.SAM_SA_19_Tunguska_2S6,
|
||||
|
||||
AirDefence.SPAAA_Gepard,
|
||||
AirDefence.AAA_Vulcan_M163,
|
||||
AirDefence.SAM_Linebacker_M6,
|
||||
@ -187,7 +170,6 @@ TYPE_SHORAD = [
|
||||
AirDefence.SAM_Avenger_M1097,
|
||||
AirDefence.SAM_Roland_ADS,
|
||||
AirDefence.HQ_7_Self_Propelled_LN,
|
||||
|
||||
AirDefence.AAA_8_8cm_Flak_18,
|
||||
AirDefence.AAA_8_8cm_Flak_36,
|
||||
AirDefence.AAA_8_8cm_Flak_37,
|
||||
@ -195,5 +177,4 @@ TYPE_SHORAD = [
|
||||
AirDefence.AAA_Bofors_40mm,
|
||||
AirDefence.AAA_M1_37mm,
|
||||
AirDefence.AA_gun_QF_3_7,
|
||||
|
||||
]
|
||||
|
||||
@ -2,10 +2,11 @@ from enum import Enum
|
||||
|
||||
|
||||
class CombatStance(Enum):
|
||||
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
|
||||
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
|
||||
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
|
||||
)
|
||||
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
|
||||
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,
|
||||
ActivateICLSCommand,
|
||||
EPLRS,
|
||||
OptAlarmState, FireAtPoint,
|
||||
OptAlarmState,
|
||||
FireAtPoint,
|
||||
)
|
||||
from dcs.unit import Ship, Unit, Vehicle
|
||||
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.theater import ControlPoint, TheaterGroundObject
|
||||
from game.theater.theatergroundobject import (
|
||||
BuildingGroundObject, CarrierGroundObject,
|
||||
BuildingGroundObject,
|
||||
CarrierGroundObject,
|
||||
GenericCarrierGroundObject,
|
||||
LhaGroundObject, ShipGroundObject, MissileSiteGroundObject,
|
||||
LhaGroundObject,
|
||||
ShipGroundObject,
|
||||
MissileSiteGroundObject,
|
||||
)
|
||||
from game.unitmap import UnitMap
|
||||
from game.utils import knots, mps
|
||||
@ -53,8 +57,15 @@ class GenericGroundObjectGenerator:
|
||||
|
||||
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.country = country
|
||||
self.game = game
|
||||
@ -72,18 +83,22 @@ class GenericGroundObjectGenerator:
|
||||
|
||||
unit_type = unit_type_from_name(group.units[0].type)
|
||||
if unit_type is None:
|
||||
raise RuntimeError(
|
||||
f"Unrecognized unit type: {group.units[0].type}")
|
||||
raise RuntimeError(f"Unrecognized unit type: {group.units[0].type}")
|
||||
|
||||
vg = self.m.vehicle_group(self.country, group.name, unit_type,
|
||||
position=group.position,
|
||||
heading=group.units[0].heading)
|
||||
vg = self.m.vehicle_group(
|
||||
self.country,
|
||||
group.name,
|
||||
unit_type,
|
||||
position=group.position,
|
||||
heading=group.units[0].heading,
|
||||
)
|
||||
vg.units[0].name = self.m.string(group.units[0].name)
|
||||
vg.units[0].player_can_drive = True
|
||||
for i, u in enumerate(group.units):
|
||||
if i > 0:
|
||||
vehicle = Vehicle(self.m.next_unit_id(),
|
||||
self.m.string(u.name), u.type)
|
||||
vehicle = Vehicle(
|
||||
self.m.next_unit_id(), self.m.string(u.name), u.type
|
||||
)
|
||||
vehicle.position.x = u.position.x
|
||||
vehicle.position.y = u.position.y
|
||||
vehicle.heading = u.heading
|
||||
@ -96,7 +111,7 @@ class GenericGroundObjectGenerator:
|
||||
|
||||
@staticmethod
|
||||
def enable_eplrs(group: Group, unit_type: Type[UnitType]) -> None:
|
||||
if hasattr(unit_type, 'eplrs'):
|
||||
if hasattr(unit_type, "eplrs"):
|
||||
if unit_type.eplrs:
|
||||
group.points[0].tasks.append(EPLRS(group.id))
|
||||
|
||||
@ -106,14 +121,13 @@ class GenericGroundObjectGenerator:
|
||||
else:
|
||||
group.points[0].tasks.append(OptAlarmState(1))
|
||||
|
||||
def _register_unit_group(self, persistence_group: Group,
|
||||
miz_group: Group) -> None:
|
||||
self.unit_map.add_ground_object_units(self.ground_object,
|
||||
persistence_group, miz_group)
|
||||
def _register_unit_group(self, persistence_group: Group, miz_group: Group) -> None:
|
||||
self.unit_map.add_ground_object_units(
|
||||
self.ground_object, persistence_group, miz_group
|
||||
)
|
||||
|
||||
|
||||
class MissileSiteGenerator(GenericGroundObjectGenerator):
|
||||
|
||||
def generate(self) -> None:
|
||||
super(MissileSiteGenerator, self).generate()
|
||||
# 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)
|
||||
if 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))
|
||||
logging.info("Set up fire task for missile group.")
|
||||
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:
|
||||
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]:
|
||||
"""
|
||||
@ -190,7 +210,8 @@ class BuildingSiteGenerator(GenericGroundObjectGenerator):
|
||||
break
|
||||
else:
|
||||
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:
|
||||
if not self.ground_object.is_dead:
|
||||
@ -228,11 +249,20 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator):
|
||||
|
||||
Used by both CV(N) groups and LHA groups.
|
||||
"""
|
||||
def __init__(self, ground_object: GenericCarrierGroundObject,
|
||||
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:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
ground_object: GenericCarrierGroundObject,
|
||||
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)
|
||||
self.ground_object = ground_object
|
||||
self.control_point = control_point
|
||||
@ -245,8 +275,7 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator):
|
||||
# TODO: Require single group?
|
||||
for group in self.ground_object.groups:
|
||||
if not group.units:
|
||||
logging.warning(
|
||||
f"Found empty carrier group in {self.control_point}")
|
||||
logging.warning(f"Found empty carrier group in {self.control_point}")
|
||||
continue
|
||||
|
||||
atc = self.radio_registry.alloc_uhf()
|
||||
@ -270,25 +299,29 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator):
|
||||
def get_carrier_type(self, group: Group) -> Type[UnitType]:
|
||||
unit_type = unit_type_from_name(group.units[0].type)
|
||||
if unit_type is None:
|
||||
raise RuntimeError(
|
||||
f"Unrecognized carrier name: {group.units[0].type}")
|
||||
raise RuntimeError(f"Unrecognized carrier name: {group.units[0].type}")
|
||||
return unit_type
|
||||
|
||||
def configure_carrier(self, group: Group,
|
||||
atc_channel: RadioFrequency) -> ShipGroup:
|
||||
def configure_carrier(self, group: Group, atc_channel: RadioFrequency) -> ShipGroup:
|
||||
unit_type = self.get_carrier_type(group)
|
||||
|
||||
ship_group = self.m.ship_group(self.country, group.name, unit_type,
|
||||
position=group.position,
|
||||
heading=group.units[0].heading)
|
||||
ship_group = self.m.ship_group(
|
||||
self.country,
|
||||
group.name,
|
||||
unit_type,
|
||||
position=group.position,
|
||||
heading=group.units[0].heading,
|
||||
)
|
||||
ship_group.set_frequency(atc_channel.hertz)
|
||||
ship_group.units[0].name = self.m.string(group.units[0].name)
|
||||
return ship_group
|
||||
|
||||
def create_ship(self, unit: Unit, atc_channel: RadioFrequency) -> Ship:
|
||||
ship = Ship(self.m.next_unit_id(),
|
||||
self.m.string(unit.name),
|
||||
unit_type_from_name(unit.type))
|
||||
ship = Ship(
|
||||
self.m.next_unit_id(),
|
||||
self.m.string(unit.name),
|
||||
unit_type_from_name(unit.type),
|
||||
)
|
||||
ship.position.x = unit.position.x
|
||||
ship.position.y = unit.position.y
|
||||
ship.heading = unit.heading
|
||||
@ -303,7 +336,8 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator):
|
||||
carrier_speed = knots(25) - mps(wind.speed)
|
||||
for attempt in range(5):
|
||||
point = group.points[0].position.point_from_heading(
|
||||
brc, 100000 - attempt * 20000)
|
||||
brc, 100000 - attempt * 20000
|
||||
)
|
||||
if self.game.theater.is_in_sea(point):
|
||||
group.points[0].speed = carrier_speed.meters_per_second
|
||||
group.add_waypoint(point, carrier_speed.kph)
|
||||
@ -314,21 +348,30 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator):
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
def activate_beacons(group: ShipGroup, tacan: TacanChannel,
|
||||
callsign: str, icls: int) -> None:
|
||||
group.points[0].tasks.append(ActivateBeaconCommand(
|
||||
channel=tacan.number,
|
||||
modechannel=tacan.band.value,
|
||||
callsign=callsign,
|
||||
unit_id=group.units[0].id,
|
||||
aa=False
|
||||
))
|
||||
group.points[0].tasks.append(ActivateICLSCommand(
|
||||
icls, unit_id=group.units[0].id
|
||||
))
|
||||
def activate_beacons(
|
||||
group: ShipGroup, tacan: TacanChannel, callsign: str, icls: int
|
||||
) -> None:
|
||||
group.points[0].tasks.append(
|
||||
ActivateBeaconCommand(
|
||||
channel=tacan.number,
|
||||
modechannel=tacan.band.value,
|
||||
callsign=callsign,
|
||||
unit_id=group.units[0].id,
|
||||
aa=False,
|
||||
)
|
||||
)
|
||||
group.points[0].tasks.append(
|
||||
ActivateICLSCommand(icls, unit_id=group.units[0].id)
|
||||
)
|
||||
|
||||
def add_runway_data(self, brc: int, atc: RadioFrequency,
|
||||
tacan: TacanChannel, callsign: str, icls: int) -> None:
|
||||
def add_runway_data(
|
||||
self,
|
||||
brc: int,
|
||||
atc: RadioFrequency,
|
||||
tacan: TacanChannel,
|
||||
callsign: str,
|
||||
icls: int,
|
||||
) -> None:
|
||||
# TODO: Make unit name usable.
|
||||
# This relies on one control point mapping exactly
|
||||
# to one LHA, carrier, or other usable "runway".
|
||||
@ -354,24 +397,25 @@ class CarrierGenerator(GenericCarrierGenerator):
|
||||
def get_carrier_type(self, group: Group) -> UnitType:
|
||||
unit_type = super().get_carrier_type(group)
|
||||
if self.game.settings.supercarrier:
|
||||
unit_type = db.upgrade_to_supercarrier(unit_type,
|
||||
self.control_point.name)
|
||||
unit_type = db.upgrade_to_supercarrier(unit_type, self.control_point.name)
|
||||
return unit_type
|
||||
|
||||
def tacan_callsign(self) -> str:
|
||||
# TODO: Assign these properly.
|
||||
return random.choice([
|
||||
"STE",
|
||||
"CVN",
|
||||
"CVH",
|
||||
"CCV",
|
||||
"ACC",
|
||||
"ARC",
|
||||
"GER",
|
||||
"ABR",
|
||||
"LIN",
|
||||
"TRU",
|
||||
])
|
||||
return random.choice(
|
||||
[
|
||||
"STE",
|
||||
"CVN",
|
||||
"CVH",
|
||||
"CCV",
|
||||
"ACC",
|
||||
"ARC",
|
||||
"GER",
|
||||
"ABR",
|
||||
"LIN",
|
||||
"TRU",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class LhaGenerator(GenericCarrierGenerator):
|
||||
@ -379,14 +423,16 @@ class LhaGenerator(GenericCarrierGenerator):
|
||||
|
||||
def tacan_callsign(self) -> str:
|
||||
# TODO: Assign these properly.
|
||||
return random.choice([
|
||||
"LHD",
|
||||
"LHA",
|
||||
"LHB",
|
||||
"LHC",
|
||||
"LHD",
|
||||
"LDS",
|
||||
])
|
||||
return random.choice(
|
||||
[
|
||||
"LHD",
|
||||
"LHA",
|
||||
"LHB",
|
||||
"LHC",
|
||||
"LHD",
|
||||
"LDS",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class ShipObjectGenerator(GenericGroundObjectGenerator):
|
||||
@ -403,22 +449,23 @@ class ShipObjectGenerator(GenericGroundObjectGenerator):
|
||||
|
||||
unit_type = unit_type_from_name(group.units[0].type)
|
||||
if unit_type is None:
|
||||
raise RuntimeError(
|
||||
f"Unrecognized unit type: {group.units[0].type}")
|
||||
raise RuntimeError(f"Unrecognized unit type: {group.units[0].type}")
|
||||
|
||||
self.generate_group(group, unit_type)
|
||||
|
||||
def generate_group(self, group_def: Group,
|
||||
first_unit_type: Type[UnitType]) -> None:
|
||||
group = self.m.ship_group(self.country, group_def.name, first_unit_type,
|
||||
position=group_def.position,
|
||||
heading=group_def.units[0].heading)
|
||||
def generate_group(self, group_def: Group, first_unit_type: Type[UnitType]) -> None:
|
||||
group = self.m.ship_group(
|
||||
self.country,
|
||||
group_def.name,
|
||||
first_unit_type,
|
||||
position=group_def.position,
|
||||
heading=group_def.units[0].heading,
|
||||
)
|
||||
group.units[0].name = self.m.string(group_def.units[0].name)
|
||||
# TODO: Skipping the first unit looks like copy pasta from the carrier.
|
||||
for unit in group_def.units[1:]:
|
||||
unit_type = unit_type_from_name(unit.type)
|
||||
ship = Ship(self.m.next_unit_id(),
|
||||
self.m.string(unit.name), unit_type)
|
||||
ship = Ship(self.m.next_unit_id(), self.m.string(unit.name), unit_type)
|
||||
ship.position.x = unit.position.x
|
||||
ship.position.y = unit.position.y
|
||||
ship.heading = unit.heading
|
||||
@ -436,9 +483,14 @@ class GroundObjectsGenerator:
|
||||
the appropriate generators.
|
||||
"""
|
||||
|
||||
def __init__(self, mission: Mission, game: Game,
|
||||
radio_registry: RadioRegistry, tacan_registry: TacanRegistry,
|
||||
unit_map: UnitMap) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
mission: Mission,
|
||||
game: Game,
|
||||
radio_registry: RadioRegistry,
|
||||
tacan_registry: TacanRegistry,
|
||||
unit_map: UnitMap,
|
||||
) -> None:
|
||||
self.m = mission
|
||||
self.game = game
|
||||
self.radio_registry = radio_registry
|
||||
@ -458,28 +510,44 @@ class GroundObjectsGenerator:
|
||||
for ground_object in cp.ground_objects:
|
||||
if isinstance(ground_object, BuildingGroundObject):
|
||||
generator = BuildingSiteGenerator(
|
||||
ground_object, country, self.game, self.m,
|
||||
self.unit_map)
|
||||
ground_object, country, self.game, self.m, self.unit_map
|
||||
)
|
||||
elif isinstance(ground_object, CarrierGroundObject):
|
||||
generator = CarrierGenerator(
|
||||
ground_object, cp, country, self.game, self.m,
|
||||
self.radio_registry, self.tacan_registry,
|
||||
self.icls_alloc, self.runways, self.unit_map)
|
||||
ground_object,
|
||||
cp,
|
||||
country,
|
||||
self.game,
|
||||
self.m,
|
||||
self.radio_registry,
|
||||
self.tacan_registry,
|
||||
self.icls_alloc,
|
||||
self.runways,
|
||||
self.unit_map,
|
||||
)
|
||||
elif isinstance(ground_object, LhaGroundObject):
|
||||
generator = CarrierGenerator(
|
||||
ground_object, cp, country, self.game, self.m,
|
||||
self.radio_registry, self.tacan_registry,
|
||||
self.icls_alloc, self.runways, self.unit_map)
|
||||
ground_object,
|
||||
cp,
|
||||
country,
|
||||
self.game,
|
||||
self.m,
|
||||
self.radio_registry,
|
||||
self.tacan_registry,
|
||||
self.icls_alloc,
|
||||
self.runways,
|
||||
self.unit_map,
|
||||
)
|
||||
elif isinstance(ground_object, ShipGroundObject):
|
||||
generator = ShipObjectGenerator(
|
||||
ground_object, country, self.game, self.m,
|
||||
self.unit_map)
|
||||
ground_object, country, self.game, self.m, self.unit_map
|
||||
)
|
||||
elif isinstance(ground_object, MissileSiteGroundObject):
|
||||
generator = MissileSiteGenerator(
|
||||
ground_object, country, self.game, self.m,
|
||||
self.unit_map)
|
||||
ground_object, country, self.game, self.m, self.unit_map
|
||||
)
|
||||
else:
|
||||
generator = GenericGroundObjectGenerator(
|
||||
ground_object, country, self.game, self.m,
|
||||
self.unit_map)
|
||||
ground_object, country, self.game, self.m, self.unit_map
|
||||
)
|
||||
generator.generate()
|
||||
|
||||
161
gen/kneeboard.py
161
gen/kneeboard.py
@ -49,7 +49,7 @@ class KneeboardPageWriter:
|
||||
"""Creates kneeboard images."""
|
||||
|
||||
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
|
||||
# we start generating more complicated flight plans, or start including
|
||||
# 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.heading_font = ImageFont.truetype("arial.ttf", 24)
|
||||
self.content_font = ImageFont.truetype("arial.ttf", 20)
|
||||
self.table_font = ImageFont.truetype(
|
||||
"resources/fonts/Inconsolata.otf", 20)
|
||||
self.table_font = ImageFont.truetype("resources/fonts/Inconsolata.otf", 20)
|
||||
self.draw = ImageDraw.Draw(self.image)
|
||||
self.x = page_margin
|
||||
self.y = page_margin
|
||||
@ -69,8 +68,9 @@ class KneeboardPageWriter:
|
||||
def position(self) -> Tuple[int, int]:
|
||||
return self.x, self.y
|
||||
|
||||
def text(self, text: str, font=None,
|
||||
fill: Tuple[int, int, int] = (0, 0, 0)) -> None:
|
||||
def text(
|
||||
self, text: str, font=None, fill: Tuple[int, int, int] = (0, 0, 0)
|
||||
) -> None:
|
||||
if font is None:
|
||||
font = self.content_font
|
||||
|
||||
@ -84,8 +84,9 @@ class KneeboardPageWriter:
|
||||
def heading(self, text: str) -> None:
|
||||
self.text(text, font=self.heading_font)
|
||||
|
||||
def table(self, cells: List[List[str]],
|
||||
headers: Optional[List[str]] = None) -> None:
|
||||
def table(
|
||||
self, cells: List[List[str]], headers: Optional[List[str]] = None
|
||||
) -> None:
|
||||
if headers is None:
|
||||
headers = []
|
||||
table = tabulate(cells, headers=headers, numalign="right")
|
||||
@ -157,29 +158,34 @@ class FlightPlanBuilder:
|
||||
first_waypoint_num = self.target_points[0].number
|
||||
last_waypoint_num = self.target_points[-1].number
|
||||
|
||||
self.rows.append([
|
||||
f"{first_waypoint_num}-{last_waypoint_num}",
|
||||
"Target points",
|
||||
"0",
|
||||
self._waypoint_distance(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.departure_time),
|
||||
])
|
||||
self.rows.append(
|
||||
[
|
||||
f"{first_waypoint_num}-{last_waypoint_num}",
|
||||
"Target points",
|
||||
"0",
|
||||
self._waypoint_distance(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.departure_time),
|
||||
]
|
||||
)
|
||||
self.last_waypoint = self.target_points[-1].waypoint
|
||||
|
||||
def add_waypoint_row(self, waypoint: NumberedWaypoint) -> None:
|
||||
self.rows.append([
|
||||
str(waypoint.number),
|
||||
KneeboardPageWriter.wrap_line(
|
||||
waypoint.waypoint.pretty_name,
|
||||
FlightPlanBuilder.WAYPOINT_DESC_MAX_LEN),
|
||||
str(int(waypoint.waypoint.alt.feet)),
|
||||
self._waypoint_distance(waypoint.waypoint),
|
||||
self._ground_speed(waypoint.waypoint),
|
||||
self._format_time(waypoint.waypoint.tot),
|
||||
self._format_time(waypoint.waypoint.departure_time),
|
||||
])
|
||||
self.rows.append(
|
||||
[
|
||||
str(waypoint.number),
|
||||
KneeboardPageWriter.wrap_line(
|
||||
waypoint.waypoint.pretty_name,
|
||||
FlightPlanBuilder.WAYPOINT_DESC_MAX_LEN,
|
||||
),
|
||||
str(int(waypoint.waypoint.alt.feet)),
|
||||
self._waypoint_distance(waypoint.waypoint),
|
||||
self._ground_speed(waypoint.waypoint),
|
||||
self._format_time(waypoint.waypoint.tot),
|
||||
self._format_time(waypoint.waypoint.departure_time),
|
||||
]
|
||||
)
|
||||
|
||||
def _format_time(self, time: Optional[datetime.timedelta]) -> str:
|
||||
if time is None:
|
||||
@ -191,9 +197,9 @@ class FlightPlanBuilder:
|
||||
if self.last_waypoint is None:
|
||||
return "-"
|
||||
|
||||
distance = meters(self.last_waypoint.position.distance_to_point(
|
||||
waypoint.position
|
||||
))
|
||||
distance = meters(
|
||||
self.last_waypoint.position.distance_to_point(waypoint.position)
|
||||
)
|
||||
return f"{distance.nautical_miles:.1f} NM"
|
||||
|
||||
def _ground_speed(self, waypoint: FlightWaypoint) -> str:
|
||||
@ -210,9 +216,9 @@ class FlightPlanBuilder:
|
||||
else:
|
||||
return "-"
|
||||
|
||||
distance = meters(self.last_waypoint.position.distance_to_point(
|
||||
waypoint.position
|
||||
))
|
||||
distance = meters(
|
||||
self.last_waypoint.position.distance_to_point(waypoint.position)
|
||||
)
|
||||
duration = (waypoint.tot - last_time).total_seconds() / 3600
|
||||
return f"{int(distance.nautical_miles / duration)} kt"
|
||||
|
||||
@ -222,9 +228,16 @@ class FlightPlanBuilder:
|
||||
|
||||
class BriefingPage(KneeboardPage):
|
||||
"""A kneeboard page containing briefing information."""
|
||||
def __init__(self, flight: FlightData, comms: List[CommInfo],
|
||||
awacs: List[AwacsInfo], tankers: List[TankerInfo],
|
||||
jtacs: List[JtacInfo], start_time: datetime.datetime) -> None:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
flight: FlightData,
|
||||
comms: List[CommInfo],
|
||||
awacs: List[AwacsInfo],
|
||||
tankers: List[TankerInfo],
|
||||
jtacs: List[JtacInfo],
|
||||
start_time: datetime.datetime,
|
||||
) -> None:
|
||||
self.flight = flight
|
||||
self.comms = list(comms)
|
||||
self.awacs = awacs
|
||||
@ -239,49 +252,59 @@ class BriefingPage(KneeboardPage):
|
||||
|
||||
# TODO: Handle carriers.
|
||||
writer.heading("Airfield Info")
|
||||
writer.table([
|
||||
self.airfield_info_row("Departure", self.flight.departure),
|
||||
self.airfield_info_row("Arrival", self.flight.arrival),
|
||||
self.airfield_info_row("Divert", self.flight.divert),
|
||||
], headers=["", "Airbase", "ATC", "TCN", "I(C)LS", "RWY"])
|
||||
writer.table(
|
||||
[
|
||||
self.airfield_info_row("Departure", self.flight.departure),
|
||||
self.airfield_info_row("Arrival", self.flight.arrival),
|
||||
self.airfield_info_row("Divert", self.flight.divert),
|
||||
],
|
||||
headers=["", "Airbase", "ATC", "TCN", "I(C)LS", "RWY"],
|
||||
)
|
||||
|
||||
writer.heading("Flight Plan")
|
||||
flight_plan_builder = FlightPlanBuilder(self.start_time)
|
||||
for num, waypoint in enumerate(self.flight.waypoints):
|
||||
flight_plan_builder.add_waypoint(num, waypoint)
|
||||
writer.table(flight_plan_builder.build(), headers=[
|
||||
"#", "Action", "Alt", "Dist", "GSPD", "Time", "Departure"
|
||||
])
|
||||
writer.table(
|
||||
flight_plan_builder.build(),
|
||||
headers=["#", "Action", "Alt", "Dist", "GSPD", "Time", "Departure"],
|
||||
)
|
||||
|
||||
flight_plan_builder
|
||||
writer.table([
|
||||
["{}lbs".format(self.flight.bingo_fuel), "{}lbs".format(self.flight.joker_fuel)]
|
||||
], ['Bingo', 'Joker'])
|
||||
writer.table(
|
||||
[
|
||||
[
|
||||
"{}lbs".format(self.flight.bingo_fuel),
|
||||
"{}lbs".format(self.flight.joker_fuel),
|
||||
]
|
||||
],
|
||||
["Bingo", "Joker"],
|
||||
)
|
||||
|
||||
# Package Section
|
||||
writer.heading("Comm ladder")
|
||||
comm_ladder = []
|
||||
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:
|
||||
comm_ladder.append([
|
||||
a.callsign,
|
||||
'AWACS',
|
||||
'',
|
||||
'',
|
||||
self.format_frequency(a.freq)
|
||||
])
|
||||
comm_ladder.append(
|
||||
[a.callsign, "AWACS", "", "", self.format_frequency(a.freq)]
|
||||
)
|
||||
for tanker in self.tankers:
|
||||
comm_ladder.append([
|
||||
tanker.callsign,
|
||||
"Tanker",
|
||||
tanker.variant,
|
||||
str(tanker.tacan),
|
||||
self.format_frequency(tanker.freq),
|
||||
])
|
||||
|
||||
writer.table(comm_ladder, headers=["Callsign","Task", "Type", "TACAN", "FREQ"])
|
||||
comm_ladder.append(
|
||||
[
|
||||
tanker.callsign,
|
||||
"Tanker",
|
||||
tanker.variant,
|
||||
str(tanker.tacan),
|
||||
self.format_frequency(tanker.freq),
|
||||
]
|
||||
)
|
||||
|
||||
writer.table(comm_ladder, headers=["Callsign", "Task", "Type", "TACAN", "FREQ"])
|
||||
|
||||
writer.heading("JTAC")
|
||||
jtacs = []
|
||||
@ -291,8 +314,9 @@ class BriefingPage(KneeboardPage):
|
||||
|
||||
writer.write(path)
|
||||
|
||||
def airfield_info_row(self, row_title: str,
|
||||
runway: Optional[RunwayData]) -> List[str]:
|
||||
def airfield_info_row(
|
||||
self, row_title: str, runway: Optional[RunwayData]
|
||||
) -> List[str]:
|
||||
"""Creates a table row for a given airfield.
|
||||
|
||||
Args:
|
||||
@ -372,7 +396,8 @@ class KneeboardGenerator(MissionInfoGenerator):
|
||||
if not flight.client_units:
|
||||
continue
|
||||
all_flights[flight.aircraft_type].extend(
|
||||
self.generate_flight_kneeboard(flight))
|
||||
self.generate_flight_kneeboard(flight)
|
||||
)
|
||||
return all_flights
|
||||
|
||||
def generate_flight_kneeboard(self, flight: FlightData) -> List[KneeboardPage]:
|
||||
@ -384,6 +409,6 @@ class KneeboardGenerator(MissionInfoGenerator):
|
||||
self.awacs,
|
||||
self.tankers,
|
||||
self.jtacs,
|
||||
self.mission.start_time
|
||||
self.mission.start_time,
|
||||
),
|
||||
]
|
||||
|
||||
@ -9,9 +9,10 @@ from gen.locations.preset_locations import PresetLocation
|
||||
|
||||
|
||||
class MizDataLocationFinder:
|
||||
|
||||
@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
|
||||
:param terrain_name: Terrain/Map name
|
||||
@ -32,28 +33,54 @@ class MizDataLocationFinder:
|
||||
|
||||
for vehicle_group in m.country("USA").vehicle_group:
|
||||
if len(vehicle_group.units) > 0:
|
||||
ashore_locations.append(PresetLocation(vehicle_group.position,
|
||||
vehicle_group.units[0].heading,
|
||||
vehicle_group.name))
|
||||
ashore_locations.append(
|
||||
PresetLocation(
|
||||
vehicle_group.position,
|
||||
vehicle_group.units[0].heading,
|
||||
vehicle_group.name,
|
||||
)
|
||||
)
|
||||
|
||||
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:
|
||||
offshore_locations.append(PresetLocation(ship_group.position,
|
||||
ship_group.units[0].heading,
|
||||
ship_group.name))
|
||||
if (
|
||||
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.name,
|
||||
)
|
||||
)
|
||||
|
||||
for static_group in m.country("USA").static_group:
|
||||
if len(static_group.units) > 0:
|
||||
powerplants_locations.append(PresetLocation(static_group.position,
|
||||
static_group.units[0].heading,
|
||||
static_group.name))
|
||||
powerplants_locations.append(
|
||||
PresetLocation(
|
||||
static_group.position,
|
||||
static_group.units[0].heading,
|
||||
static_group.name,
|
||||
)
|
||||
)
|
||||
|
||||
if m.country("Iran") is not None:
|
||||
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:
|
||||
antiship_locations.append(PresetLocation(vehicle_group.position,
|
||||
vehicle_group.units[0].heading,
|
||||
vehicle_group.name))
|
||||
if (
|
||||
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.name,
|
||||
)
|
||||
)
|
||||
|
||||
return PresetControlPointLocations(ashore_locations, offshore_locations,
|
||||
antiship_locations, powerplants_locations)
|
||||
return PresetControlPointLocations(
|
||||
ashore_locations,
|
||||
offshore_locations,
|
||||
antiship_locations,
|
||||
powerplants_locations,
|
||||
)
|
||||
|
||||
@ -6,10 +6,16 @@ from dcs import Point
|
||||
@dataclass
|
||||
class PresetLocation:
|
||||
"""A preset location"""
|
||||
|
||||
position: Point
|
||||
heading: int
|
||||
id: str
|
||||
|
||||
def __str__(self):
|
||||
return "-" * 10 + "X: {}\n Y: {}\nHdg: {}°\nId: {}".format(self.position.x, self.position.y, self.heading,
|
||||
self.id) + "-" * 10
|
||||
return (
|
||||
"-" * 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.v1_group import V1GroupGenerator
|
||||
|
||||
MISSILES_MAP = {
|
||||
"V1GroupGenerator": V1GroupGenerator,
|
||||
"ScudGenerator": ScudGenerator
|
||||
}
|
||||
MISSILES_MAP = {"V1GroupGenerator": V1GroupGenerator, "ScudGenerator": ScudGenerator}
|
||||
|
||||
|
||||
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()
|
||||
return generator.get_generated_group()
|
||||
else:
|
||||
logging.info("Unable to generate missile group, generator : " + str(gen) + "does not exists")
|
||||
return None
|
||||
logging.info(
|
||||
"Unable to generate missile group, generator : "
|
||||
+ str(gen)
|
||||
+ "does not exists"
|
||||
)
|
||||
return None
|
||||
|
||||
@ -6,7 +6,6 @@ from gen.sam.group_generator import GroupGenerator
|
||||
|
||||
|
||||
class ScudGenerator(GroupGenerator):
|
||||
|
||||
def __init__(self, game, ground_object, faction):
|
||||
super(ScudGenerator, self).__init__(game, ground_object)
|
||||
self.faction = faction
|
||||
@ -14,17 +13,50 @@ class ScudGenerator(GroupGenerator):
|
||||
def generate(self):
|
||||
|
||||
# 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(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)
|
||||
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(
|
||||
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
|
||||
self.add_unit(Unarmed.Transport_UAZ_469, "Kubel#0", self.position.x - 35, self.position.y - 20,
|
||||
self.heading)
|
||||
self.add_unit(
|
||||
Unarmed.Transport_UAZ_469,
|
||||
"Kubel#0",
|
||||
self.position.x - 35,
|
||||
self.position.y - 20,
|
||||
self.heading,
|
||||
)
|
||||
|
||||
# Shorad
|
||||
self.add_unit(AirDefence.SPAAA_ZSU_23_4_Shilka, "SHILKA#0", self.position.x - 55, self.position.y - 38,
|
||||
self.heading)
|
||||
self.add_unit(
|
||||
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.position.x + 200, self.position.y + 15, 90)
|
||||
self.add_unit(
|
||||
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):
|
||||
|
||||
def __init__(self, game, ground_object, faction):
|
||||
super(V1GroupGenerator, self).__init__(game, ground_object)
|
||||
self.faction = faction
|
||||
@ -14,19 +13,54 @@ class V1GroupGenerator(GroupGenerator):
|
||||
def generate(self):
|
||||
|
||||
# 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(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)
|
||||
self.add_unit(
|
||||
MissilesSS.V_1_ramp,
|
||||
"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
|
||||
self.add_unit(Unarmed.Kübelwagen_82, "Kubel#0", self.position.x - 35, self.position.y - 20,
|
||||
self.heading)
|
||||
self.add_unit(
|
||||
Unarmed.Kübelwagen_82,
|
||||
"Kubel#0",
|
||||
self.position.x - 35,
|
||||
self.position.y - 20,
|
||||
self.heading,
|
||||
)
|
||||
|
||||
# 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.heading)
|
||||
self.add_unit(
|
||||
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.position.x + 200, self.position.y + 15, 90)
|
||||
self.add_unit(
|
||||
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
|
||||
|
||||
ALPHA_MILITARY = ["Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot",
|
||||
"Golf", "Hotel", "India", "Juliet", "Kilo", "Lima", "Mike",
|
||||
"November", "Oscar", "Papa", "Quebec", "Romeo", "Sierra",
|
||||
"Tango", "Uniform", "Victor", "Whisky", "XRay", "Yankee",
|
||||
"Zulu", "Zero"]
|
||||
ALPHA_MILITARY = [
|
||||
"Alpha",
|
||||
"Bravo",
|
||||
"Charlie",
|
||||
"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 = [
|
||||
"SHARK", "TORTOISE", "BAT", "PANGOLIN", "AARDWOLF",
|
||||
"MONKEY", "BUFFALO", "DOG", "BOBCAT", "LYNX", "PANTHER", "TIGER",
|
||||
"LION", "OWL", "BUTTERFLY", "BISON", "DUCK", "COBRA", "MAMBA",
|
||||
"DOLPHIN", "PHEASANT", "ARMADILLLO", "RACOON", "ZEBRA", "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"
|
||||
]
|
||||
"SHARK",
|
||||
"TORTOISE",
|
||||
"BAT",
|
||||
"PANGOLIN",
|
||||
"AARDWOLF",
|
||||
"MONKEY",
|
||||
"BUFFALO",
|
||||
"DOG",
|
||||
"BOBCAT",
|
||||
"LYNX",
|
||||
"PANTHER",
|
||||
"TIGER",
|
||||
"LION",
|
||||
"OWL",
|
||||
"BUTTERFLY",
|
||||
"BISON",
|
||||
"DUCK",
|
||||
"COBRA",
|
||||
"MAMBA",
|
||||
"DOLPHIN",
|
||||
"PHEASANT",
|
||||
"ARMADILLLO",
|
||||
"RACOON",
|
||||
"ZEBRA",
|
||||
"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:
|
||||
number = 0
|
||||
@ -72,21 +275,36 @@ class NameGenerator:
|
||||
name_str = flight.custom_name
|
||||
else:
|
||||
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
|
||||
name_str = "{} {}".format(
|
||||
flight.package.target.name, flight.flight_type)
|
||||
return "{}|{}|{}|{}|{}|".format(name_str, country.id, cls.aircraft_number, parent_base_id, db.unit_type_name(flight.unit_type))
|
||||
name_str = "{} {}".format(flight.package.target.name, flight.flight_type)
|
||||
return "{}|{}|{}|{}|{}|".format(
|
||||
name_str,
|
||||
country.id,
|
||||
cls.aircraft_number,
|
||||
parent_base_id,
|
||||
db.unit_type_name(flight.unit_type),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def next_unit_name(cls, country: Country, parent_base_id: int, unit_type: UnitType):
|
||||
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
|
||||
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
|
||||
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
|
||||
def next_basedefense_name():
|
||||
@ -100,7 +318,9 @@ class NameGenerator:
|
||||
@classmethod
|
||||
def next_tanker_name(cls, country: Country, unit_type: UnitType):
|
||||
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
|
||||
def next_carrier_name(cls, country: Country):
|
||||
@ -112,7 +332,11 @@ class NameGenerator:
|
||||
if len(cls.ANIMALS) == 0:
|
||||
for i in range(10):
|
||||
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:
|
||||
if existing_name == alpha_mil_name:
|
||||
new_name_generated = False
|
||||
|
||||
@ -68,9 +68,10 @@ class Radio:
|
||||
|
||||
def range(self) -> Iterator[RadioFrequency]:
|
||||
"""Returns an iterator over the usable frequencies of this radio."""
|
||||
return (RadioFrequency(x) for x in range(
|
||||
self.minimum.hertz, self.maximum.hertz, self.step.hertz
|
||||
))
|
||||
return (
|
||||
RadioFrequency(x)
|
||||
for x in range(self.minimum.hertz, self.maximum.hertz, self.step.hertz)
|
||||
)
|
||||
|
||||
@property
|
||||
def last_channel(self) -> RadioFrequency:
|
||||
@ -99,14 +100,12 @@ RADIOS: List[Radio] = [
|
||||
Radio("SCR-522", 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)),
|
||||
|
||||
# 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
|
||||
# system doesn't model gaps, so just pretend it ends at 150 MHz for now. We
|
||||
# can model gaps later if needed.
|
||||
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)),
|
||||
|
||||
# Tomcat radios
|
||||
# # 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)),
|
||||
@ -114,31 +113,23 @@ RADIOS: List[Radio] = [
|
||||
# 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
|
||||
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.
|
||||
Radio("FR 22", MHz(225), MHz(400), step=kHz(50)),
|
||||
|
||||
# P-51 / P-47 Radio
|
||||
# 4 preset channels (A/B/C/D)
|
||||
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 UHF", MHz(225), MHz(400), step=MHz(1)),
|
||||
|
||||
# MiG-15bis
|
||||
Radio("RSI-6K HF", MHz(3, 750), MHz(5), step=kHz(25)),
|
||||
|
||||
# MiG-19P
|
||||
Radio("RSIU-4V", MHz(100), MHz(150), step=MHz(1)),
|
||||
|
||||
# MiG-21bis
|
||||
Radio("RSIU-5V", MHz(118), MHz(140), step=MHz(1)),
|
||||
|
||||
# Ka-50
|
||||
# Note: Also capable of 100MHz-150MHz, but we can't model gaps.
|
||||
Radio("R-800L1", MHz(220), MHz(400), step=kHz(25)),
|
||||
Radio("R-828", MHz(20), MHz(60), step=kHz(25)),
|
||||
|
||||
# UH-1H
|
||||
Radio("AN/ARC-51BX", MHz(225), MHz(400), 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
|
||||
channel = radio.last_channel
|
||||
logging.warning(
|
||||
f"No more free channels for {radio.name}. Reusing {channel}.")
|
||||
f"No more free channels for {radio.name}. Reusing {channel}."
|
||||
)
|
||||
return channel
|
||||
|
||||
def alloc_uhf(self) -> RadioFrequency:
|
||||
|
||||
@ -25,8 +25,9 @@ class RunwayData:
|
||||
icls: Optional[int] = None
|
||||
|
||||
@classmethod
|
||||
def for_airfield(cls, airport: Airport, runway_heading: int,
|
||||
runway_name: str) -> RunwayData:
|
||||
def for_airfield(
|
||||
cls, airport: Airport, runway_heading: int, runway_name: str
|
||||
) -> RunwayData:
|
||||
"""Creates RunwayData for the given runway of an airfield.
|
||||
|
||||
Args:
|
||||
@ -56,7 +57,7 @@ class RunwayData:
|
||||
atc=atc,
|
||||
tacan=tacan,
|
||||
tacan_callsign=tacan_callsign,
|
||||
ils=ils
|
||||
ils=ils,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@ -20,15 +20,19 @@ class BoforsGenerator(AirDefenseGroupGenerator):
|
||||
grid_x = random.randint(2, 3)
|
||||
grid_y = random.randint(2, 3)
|
||||
|
||||
spacing = random.randint(10,40)
|
||||
spacing = random.randint(10, 40)
|
||||
|
||||
index = 0
|
||||
for i in range(grid_x):
|
||||
for j in range(grid_y):
|
||||
index = index+1
|
||||
self.add_unit(AirDefence.AAA_Bofors_40mm, "AAA#" + str(index),
|
||||
self.position.x + spacing*i,
|
||||
self.position.y + spacing*j, self.heading)
|
||||
index = index + 1
|
||||
self.add_unit(
|
||||
AirDefence.AAA_Bofors_40mm,
|
||||
"AAA#" + str(index),
|
||||
self.position.x + spacing * i,
|
||||
self.position.y + spacing * j,
|
||||
self.heading,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def range(cls) -> AirDefenseRange:
|
||||
|
||||
@ -37,34 +37,64 @@ class FlakGenerator(AirDefenseGroupGenerator):
|
||||
|
||||
for i in range(grid_x):
|
||||
for j in range(grid_y):
|
||||
index = index+1
|
||||
self.add_unit(unit_type, "AAA#" + str(index),
|
||||
self.position.x + spacing*i + random.randint(1,5),
|
||||
self.position.y + spacing*j + random.randint(1,5), self.heading)
|
||||
index = index + 1
|
||||
self.add_unit(
|
||||
unit_type,
|
||||
"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)
|
||||
|
||||
# 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):
|
||||
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
|
||||
self.add_unit(AirDefence.Maschinensatz_33, "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)
|
||||
self.add_unit(
|
||||
AirDefence.Maschinensatz_33,
|
||||
"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
|
||||
self.add_unit(Unarmed.Kübelwagen_82, "Kubel#", self.position.x - 35, self.position.y - 20,
|
||||
self.heading)
|
||||
self.add_unit(
|
||||
Unarmed.Kübelwagen_82,
|
||||
"Kubel#",
|
||||
self.position.x - 35,
|
||||
self.position.y - 20,
|
||||
self.heading,
|
||||
)
|
||||
|
||||
# Some Opel Blitz trucks
|
||||
for i 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.position.x + 125 + 15*i + random.randint(1,5),
|
||||
self.position.y + 15*j + random.randint(1,5), 75)
|
||||
for i 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.position.x + 125 + 15 * i + random.randint(1, 5),
|
||||
self.position.y + 15 * j + random.randint(1, 5),
|
||||
75,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def range(cls) -> AirDefenseRange:
|
||||
|
||||
@ -24,12 +24,22 @@ class Flak18Generator(AirDefenseGroupGenerator):
|
||||
for i in range(3):
|
||||
for j in range(2):
|
||||
index = index + 1
|
||||
self.add_unit(AirDefence.AAA_8_8cm_Flak_18, "AAA#" + str(index),
|
||||
self.position.x + spacing * i + random.randint(1, 5),
|
||||
self.position.y + spacing * j + random.randint(1, 5), self.heading)
|
||||
self.add_unit(
|
||||
AirDefence.AAA_8_8cm_Flak_18,
|
||||
"AAA#" + str(index),
|
||||
self.position.x + spacing * i + random.randint(1, 5),
|
||||
self.position.y + spacing * j + random.randint(1, 5),
|
||||
self.heading,
|
||||
)
|
||||
|
||||
# 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
|
||||
def range(cls) -> AirDefenseRange:
|
||||
|
||||
@ -19,15 +19,25 @@ class KS19Generator(AirDefenseGroupGenerator):
|
||||
|
||||
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
|
||||
for i in range(3):
|
||||
for j in range(3):
|
||||
index = index + 1
|
||||
self.add_unit(highdigitsams.AAA_100mm_KS_19, "AAA#" + str(index),
|
||||
self.position.x + spacing * i,
|
||||
self.position.y + spacing * j, self.heading)
|
||||
self.add_unit(
|
||||
highdigitsams.AAA_100mm_KS_19,
|
||||
"AAA#" + str(index),
|
||||
self.position.x + spacing * i,
|
||||
self.position.y + spacing * j,
|
||||
self.heading,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def range(cls) -> AirDefenseRange:
|
||||
|
||||
@ -20,21 +20,63 @@ class AllyWW2FlakGenerator(AirDefenseGroupGenerator):
|
||||
|
||||
positions = self.get_circular_position(4, launcher_distance=30, coverage=360)
|
||||
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)
|
||||
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)
|
||||
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
|
||||
self.add_unit(Unarmed.Willys_MB, "CMD#1", 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))
|
||||
self.add_unit(
|
||||
Unarmed.Willys_MB,
|
||||
"CMD#1",
|
||||
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
|
||||
def range(cls) -> AirDefenseRange:
|
||||
|
||||
@ -16,9 +16,17 @@ class ZSU57Generator(AirDefenseGroupGenerator):
|
||||
|
||||
def generate(self):
|
||||
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):
|
||||
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
|
||||
def range(cls) -> AirDefenseRange:
|
||||
|
||||
@ -20,15 +20,19 @@ class ZU23InsurgentGenerator(AirDefenseGroupGenerator):
|
||||
grid_x = random.randint(2, 3)
|
||||
grid_y = random.randint(2, 3)
|
||||
|
||||
spacing = random.randint(10,40)
|
||||
spacing = random.randint(10, 40)
|
||||
|
||||
index = 0
|
||||
for i in range(grid_x):
|
||||
for j in range(grid_y):
|
||||
index = index+1
|
||||
self.add_unit(AirDefence.AAA_ZU_23_Insurgent_Closed, "AAA#" + str(index),
|
||||
self.position.x + spacing*i,
|
||||
self.position.y + spacing*j, self.heading)
|
||||
index = index + 1
|
||||
self.add_unit(
|
||||
AirDefence.AAA_ZU_23_Insurgent_Closed,
|
||||
"AAA#" + str(index),
|
||||
self.position.x + spacing * i,
|
||||
self.position.y + spacing * j,
|
||||
self.heading,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def range(cls) -> AirDefenseRange:
|
||||
|
||||
@ -28,8 +28,9 @@ class AirDefenseGroupGenerator(GroupGenerator, ABC):
|
||||
self.auxiliary_groups: List[VehicleGroup] = []
|
||||
|
||||
def add_auxiliary_group(self, name_suffix: str) -> VehicleGroup:
|
||||
group = VehicleGroup(self.game.next_group_id(),
|
||||
"|".join([self.go.group_name, name_suffix]))
|
||||
group = VehicleGroup(
|
||||
self.game.next_group_id(), "|".join([self.go.group_name, name_suffix])
|
||||
)
|
||||
self.auxiliary_groups.append(group)
|
||||
return group
|
||||
|
||||
@ -37,7 +38,8 @@ class AirDefenseGroupGenerator(GroupGenerator, ABC):
|
||||
raise RuntimeError(
|
||||
"Deprecated call to AirDefenseGroupGenerator.get_generated_group "
|
||||
"misses auxiliary groups. Use AirDefenseGroupGenerator.groups "
|
||||
"instead.")
|
||||
"instead."
|
||||
)
|
||||
|
||||
@property
|
||||
def groups(self) -> Iterator[VehicleGroup]:
|
||||
|
||||
@ -29,18 +29,38 @@ class EarlyColdWarFlakGenerator(AirDefenseGroupGenerator):
|
||||
for i in range(3):
|
||||
for j in range(2):
|
||||
index = index + 1
|
||||
self.add_unit(AirDefence.AAA_8_8cm_Flak_18, "AAA#" + str(index),
|
||||
self.position.x + spacing * i + random.randint(1, 5),
|
||||
self.position.y + spacing * j + random.randint(1, 5), self.heading)
|
||||
self.add_unit(
|
||||
AirDefence.AAA_8_8cm_Flak_18,
|
||||
"AAA#" + str(index),
|
||||
self.position.x + spacing * i + random.randint(1, 5),
|
||||
self.position.y + spacing * j + random.randint(1, 5),
|
||||
self.heading,
|
||||
)
|
||||
|
||||
# Short range guns
|
||||
self.add_unit(AirDefence.AAA_Bofors_40mm, "SHO#1",
|
||||
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),
|
||||
self.add_unit(
|
||||
AirDefence.AAA_Bofors_40mm,
|
||||
"SHO#1",
|
||||
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
|
||||
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
|
||||
def range(cls) -> AirDefenseRange:
|
||||
@ -66,18 +86,38 @@ class ColdWarFlakGenerator(AirDefenseGroupGenerator):
|
||||
for i in range(3):
|
||||
for j in range(2):
|
||||
index = index + 1
|
||||
self.add_unit(AirDefence.AAA_8_8cm_Flak_18, "AAA#" + str(index),
|
||||
self.position.x + spacing * i + random.randint(1, 5),
|
||||
self.position.y + spacing * j + random.randint(1, 5), self.heading)
|
||||
self.add_unit(
|
||||
AirDefence.AAA_8_8cm_Flak_18,
|
||||
"AAA#" + str(index),
|
||||
self.position.x + spacing * i + random.randint(1, 5),
|
||||
self.position.y + spacing * j + random.randint(1, 5),
|
||||
self.heading,
|
||||
)
|
||||
|
||||
# Short range guns
|
||||
self.add_unit(AirDefence.AAA_ZU_23_Closed, "SHO#1",
|
||||
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),
|
||||
self.add_unit(
|
||||
AirDefence.AAA_ZU_23_Closed,
|
||||
"SHO#1",
|
||||
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
|
||||
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
|
||||
def range(cls) -> AirDefenseRange:
|
||||
|
||||
@ -10,8 +10,9 @@ class EwrGenerator(GroupGenerator):
|
||||
raise NotImplementedError
|
||||
|
||||
def generate(self) -> None:
|
||||
self.add_unit(self.unit_type, "EWR", self.position.x, self.position.y,
|
||||
self.heading)
|
||||
self.add_unit(
|
||||
self.unit_type, "EWR", self.position.x, self.position.y, self.heading
|
||||
)
|
||||
|
||||
|
||||
class BoxSpringGenerator(EwrGenerator):
|
||||
|
||||
@ -17,27 +17,93 @@ class FreyaGenerator(AirDefenseGroupGenerator):
|
||||
def generate(self):
|
||||
|
||||
# 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)
|
||||
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)
|
||||
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
|
||||
self.add_unit(Unarmed.Kübelwagen_82, "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)
|
||||
self.add_unit(
|
||||
Unarmed.Kübelwagen_82,
|
||||
"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
|
||||
self.add_unit(AirDefence.Maschinensatz_33, "Energy#1", self.position.x + 20, 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)
|
||||
self.add_unit(
|
||||
AirDefence.Maschinensatz_33,
|
||||
"Energy#1",
|
||||
self.position.x + 20,
|
||||
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
|
||||
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
|
||||
# types rather than pydcs groups.
|
||||
class GroupGenerator:
|
||||
|
||||
def __init__(self, game: Game, ground_object: TheaterGroundObject) -> None:
|
||||
self.game = game
|
||||
self.go = ground_object
|
||||
self.position = ground_object.position
|
||||
self.heading = random.randint(0, 359)
|
||||
self.vg = unitgroup.VehicleGroup(self.game.next_group_id(),
|
||||
self.go.group_name)
|
||||
self.vg = unitgroup.VehicleGroup(self.game.next_group_id(), self.go.group_name)
|
||||
wp = self.vg.add_waypoint(self.position, PointAction.OffRoad, 0)
|
||||
wp.ETA_locked = True
|
||||
|
||||
@ -40,16 +38,27 @@ class GroupGenerator:
|
||||
def get_generated_group(self) -> unitgroup.VehicleGroup:
|
||||
return self.vg
|
||||
|
||||
def add_unit(self, unit_type: Type[VehicleType], 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(
|
||||
self,
|
||||
unit_type: Type[VehicleType],
|
||||
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,
|
||||
unit_type: Type[VehicleType], name: str,
|
||||
position: Point, heading: int) -> Vehicle:
|
||||
unit = Vehicle(self.game.next_unit_id(),
|
||||
f"{group.name}|{name}", unit_type.id)
|
||||
def add_unit_to_group(
|
||||
self,
|
||||
group: unitgroup.VehicleGroup,
|
||||
unit_type: Type[VehicleType],
|
||||
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.heading = heading
|
||||
group.add_unit(unit)
|
||||
@ -82,31 +91,36 @@ class GroupGenerator:
|
||||
current_offset = self.heading
|
||||
current_offset -= outer_offset * (math.ceil(num_units / 2) - 1)
|
||||
for x in range(1, num_units + 1):
|
||||
positions.append((
|
||||
self.position.x + launcher_distance * math.cos(math.radians(current_offset)),
|
||||
self.position.y + launcher_distance * math.sin(math.radians(current_offset)),
|
||||
current_offset,
|
||||
))
|
||||
positions.append(
|
||||
(
|
||||
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 += outer_offset
|
||||
return positions
|
||||
|
||||
|
||||
class ShipGroupGenerator(GroupGenerator):
|
||||
"""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.go = ground_object
|
||||
self.position = ground_object.position
|
||||
self.heading = random.randint(0, 359)
|
||||
self.faction = faction
|
||||
self.vg = unitgroup.ShipGroup(self.game.next_group_id(),
|
||||
self.go.group_name)
|
||||
self.vg = unitgroup.ShipGroup(self.game.next_group_id(), self.go.group_name)
|
||||
wp = self.vg.add_waypoint(self.position, 0)
|
||||
wp.ETA_locked = True
|
||||
|
||||
|
||||
def add_unit(self, unit_type, name, pos_x, pos_y, heading) -> Ship:
|
||||
unit = Ship(self.game.next_unit_id(),
|
||||
f"{self.go.group_name}|{name}", unit_type)
|
||||
unit = Ship(self.game.next_unit_id(), f"{self.go.group_name}|{name}", unit_type)
|
||||
unit.position.x = pos_x
|
||||
unit.position.y = pos_y
|
||||
unit.heading = heading
|
||||
|
||||
@ -19,10 +19,24 @@ class AvengerGenerator(AirDefenseGroupGenerator):
|
||||
def generate(self):
|
||||
num_launchers = random.randint(2, 3)
|
||||
|
||||
self.add_unit(Unarmed.Transport_M818, "TRUCK", self.position.x, self.position.y, self.heading)
|
||||
positions = self.get_circular_position(num_launchers, launcher_distance=110, coverage=180)
|
||||
self.add_unit(
|
||||
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):
|
||||
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
|
||||
def range(cls) -> AirDefenseRange:
|
||||
|
||||
@ -19,10 +19,24 @@ class ChaparralGenerator(AirDefenseGroupGenerator):
|
||||
def generate(self):
|
||||
num_launchers = random.randint(2, 4)
|
||||
|
||||
self.add_unit(Unarmed.Transport_M818, "TRUCK", self.position.x, self.position.y, self.heading)
|
||||
positions = self.get_circular_position(num_launchers, launcher_distance=110, coverage=180)
|
||||
self.add_unit(
|
||||
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):
|
||||
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
|
||||
def range(cls) -> AirDefenseRange:
|
||||
|
||||
@ -17,10 +17,28 @@ class GepardGenerator(AirDefenseGroupGenerator):
|
||||
price = 50
|
||||
|
||||
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:
|
||||
self.add_unit(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)
|
||||
self.add_unit(
|
||||
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
|
||||
def range(cls) -> AirDefenseRange:
|
||||
|
||||
@ -49,7 +49,12 @@ from gen.sam.sam_roland import RolandGenerator
|
||||
from gen.sam.sam_sa10 import (
|
||||
SA10Generator,
|
||||
Tier2SA10Generator,
|
||||
Tier3SA10Generator, SA10BGenerator, SA12Generator, SA20Generator, SA20BGenerator, SA23Generator,
|
||||
Tier3SA10Generator,
|
||||
SA10BGenerator,
|
||||
SA12Generator,
|
||||
SA20Generator,
|
||||
SA20BGenerator,
|
||||
SA23Generator,
|
||||
)
|
||||
from gen.sam.sam_sa11 import SA11Generator
|
||||
from gen.sam.sam_sa13 import SA13Generator
|
||||
@ -103,7 +108,6 @@ SAM_MAP: Dict[str, Type[AirDefenseGroupGenerator]] = {
|
||||
"FreyaGenerator": FreyaGenerator,
|
||||
"AllyWW2FlakGenerator": AllyWW2FlakGenerator,
|
||||
"ZSU57Generator": ZSU57Generator,
|
||||
|
||||
"KS19Generator": KS19Generator,
|
||||
"SA10BGenerator": SA10BGenerator,
|
||||
"SA12Generator": SA12Generator,
|
||||
@ -145,7 +149,7 @@ SAM_PRICES = {
|
||||
AirDefence.SAM_SA_13_Strela_10M3_9A35M3: 30,
|
||||
AirDefence.SAM_SA_15_Tor_9A331: 40,
|
||||
AirDefence.SAM_SA_19_Tunguska_2S6: 35,
|
||||
AirDefence.HQ_7_Self_Propelled_LN: 35
|
||||
AirDefence.HQ_7_Self_Propelled_LN: 35,
|
||||
}
|
||||
|
||||
EWR_MAP = {
|
||||
@ -163,7 +167,8 @@ EWR_MAP = {
|
||||
|
||||
|
||||
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
|
||||
: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(
|
||||
generators: Sequence[Type[AirDefenseGroupGenerator]], game: Game,
|
||||
ground_object: SamGroundObject) -> List[VehicleGroup]:
|
||||
generators: Sequence[Type[AirDefenseGroupGenerator]],
|
||||
game: Game,
|
||||
ground_object: SamGroundObject,
|
||||
) -> List[VehicleGroup]:
|
||||
if not generators:
|
||||
return []
|
||||
sam_generator_class = random.choice(generators)
|
||||
@ -191,8 +198,10 @@ def _generate_anti_air_from(
|
||||
|
||||
|
||||
def generate_anti_air_group(
|
||||
game: Game, ground_object: SamGroundObject, faction: Faction,
|
||||
ranges: Optional[Iterable[Set[AirDefenseRange]]] = None
|
||||
game: Game,
|
||||
ground_object: SamGroundObject,
|
||||
faction: Faction,
|
||||
ranges: Optional[Iterable[Set[AirDefenseRange]]] = None,
|
||||
) -> List[VehicleGroup]:
|
||||
"""
|
||||
This generate a SAM group
|
||||
@ -213,24 +222,25 @@ def generate_anti_air_group(
|
||||
"""
|
||||
generators = get_faction_possible_sams_generator(faction)
|
||||
if ranges is None:
|
||||
ranges = [{
|
||||
AirDefenseRange.Long,
|
||||
AirDefenseRange.Medium,
|
||||
AirDefenseRange.Short,
|
||||
}]
|
||||
ranges = [
|
||||
{
|
||||
AirDefenseRange.Long,
|
||||
AirDefenseRange.Medium,
|
||||
AirDefenseRange.Short,
|
||||
}
|
||||
]
|
||||
|
||||
for range_options in ranges:
|
||||
generators_for_range = [g for g in generators if
|
||||
g.range() in range_options]
|
||||
groups = _generate_anti_air_from(generators_for_range, game,
|
||||
ground_object)
|
||||
generators_for_range = [g for g in generators if g.range() in range_options]
|
||||
groups = _generate_anti_air_from(generators_for_range, game, ground_object)
|
||||
if groups:
|
||||
return groups
|
||||
return []
|
||||
|
||||
|
||||
def generate_ewr_group(game: Game, ground_object: TheaterGroundObject,
|
||||
faction: Faction) -> Optional[VehicleGroup]:
|
||||
def generate_ewr_group(
|
||||
game: Game, ground_object: TheaterGroundObject, faction: Faction
|
||||
) -> Optional[VehicleGroup]:
|
||||
"""Generates an early warning radar group.
|
||||
|
||||
:param game: The Game.
|
||||
|
||||
@ -18,20 +18,51 @@ class HawkGenerator(AirDefenseGroupGenerator):
|
||||
price = 115
|
||||
|
||||
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(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)
|
||||
self.add_unit(
|
||||
AirDefence.SAM_Hawk_SR_AN_MPQ_50,
|
||||
"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
|
||||
aa_group = self.add_auxiliary_group("AA")
|
||||
self.add_unit_to_group(aa_group, AirDefence.AAA_Vulcan_M163, "AAA",
|
||||
self.position + Point(20, 30), self.heading)
|
||||
self.add_unit_to_group(
|
||||
aa_group,
|
||||
AirDefence.AAA_Vulcan_M163,
|
||||
"AAA",
|
||||
self.position + Point(20, 30),
|
||||
self.heading,
|
||||
)
|
||||
|
||||
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):
|
||||
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
|
||||
def range(cls) -> AirDefenseRange:
|
||||
|
||||
@ -18,23 +18,51 @@ class HQ7Generator(AirDefenseGroupGenerator):
|
||||
price = 120
|
||||
|
||||
def generate(self):
|
||||
self.add_unit(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)
|
||||
self.add_unit(
|
||||
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
|
||||
aa_group = self.add_auxiliary_group("AA")
|
||||
self.add_unit_to_group(aa_group, AirDefence.AAA_ZU_23_on_Ural_375,
|
||||
"AAA1", self.position + Point(20, 30),
|
||||
self.heading)
|
||||
self.add_unit_to_group(aa_group, AirDefence.AAA_ZU_23_on_Ural_375,
|
||||
"AAA2", self.position - Point(20, 30),
|
||||
self.heading)
|
||||
self.add_unit_to_group(
|
||||
aa_group,
|
||||
AirDefence.AAA_ZU_23_on_Ural_375,
|
||||
"AAA1",
|
||||
self.position + Point(20, 30),
|
||||
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)
|
||||
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):
|
||||
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
|
||||
def range(cls) -> AirDefenseRange:
|
||||
|
||||
@ -19,10 +19,24 @@ class LinebackerGenerator(AirDefenseGroupGenerator):
|
||||
def generate(self):
|
||||
num_launchers = random.randint(2, 4)
|
||||
|
||||
self.add_unit(Unarmed.Transport_M818, "TRUCK", self.position.x, self.position.y, self.heading)
|
||||
positions = self.get_circular_position(num_launchers, launcher_distance=110, coverage=180)
|
||||
self.add_unit(
|
||||
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):
|
||||
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
|
||||
def range(cls) -> AirDefenseRange:
|
||||
|
||||
@ -19,24 +19,65 @@ class PatriotGenerator(AirDefenseGroupGenerator):
|
||||
|
||||
def generate(self):
|
||||
# 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(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)
|
||||
self.add_unit(
|
||||
AirDefence.SAM_Patriot_STR_AN_MPQ_53,
|
||||
"STR",
|
||||
self.position.x + 30,
|
||||
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)
|
||||
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):
|
||||
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
|
||||
aa_group = self.add_auxiliary_group("AA")
|
||||
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):
|
||||
self.add_unit_to_group(aa_group, AirDefence.AAA_Vulcan_M163,
|
||||
f"SPAAA#{i}", Point(x, y), heading)
|
||||
self.add_unit_to_group(
|
||||
aa_group, AirDefence.AAA_Vulcan_M163, f"SPAAA#{i}", Point(x, y), heading
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def range(cls) -> AirDefenseRange:
|
||||
|
||||
@ -17,14 +17,34 @@ class RapierGenerator(AirDefenseGroupGenerator):
|
||||
price = 50
|
||||
|
||||
def generate(self):
|
||||
self.add_unit(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)
|
||||
self.add_unit(
|
||||
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)
|
||||
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):
|
||||
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
|
||||
def range(cls) -> AirDefenseRange:
|
||||
|
||||
@ -15,9 +15,27 @@ class RolandGenerator(AirDefenseGroupGenerator):
|
||||
price = 40
|
||||
|
||||
def generate(self):
|
||||
self.add_unit(AirDefence.SAM_Roland_EWR, "EWR", self.position.x + 40, self.position.y, self.heading)
|
||||
self.add_unit(AirDefence.SAM_Roland_ADS, "ADS", self.position.x, self.position.y, self.heading)
|
||||
self.add_unit(Unarmed.Transport_M818, "TRUCK", self.position.x + 80, self.position.y, self.heading)
|
||||
self.add_unit(
|
||||
AirDefence.SAM_Roland_EWR,
|
||||
"EWR",
|
||||
self.position.x + 40,
|
||||
self.position.y,
|
||||
self.heading,
|
||||
)
|
||||
self.add_unit(
|
||||
AirDefence.SAM_Roland_ADS,
|
||||
"ADS",
|
||||
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
|
||||
def range(cls) -> AirDefenseRange:
|
||||
|
||||
@ -33,28 +33,41 @@ class SA10Generator(AirDefenseGroupGenerator):
|
||||
|
||||
def generate(self):
|
||||
# Search Radar
|
||||
self.add_unit(self.sr1, "SR1", self.position.x, self.position.y + 40, self.heading)
|
||||
self.add_unit(
|
||||
self.sr1, "SR1", self.position.x, self.position.y + 40, self.heading
|
||||
)
|
||||
|
||||
# Search radar for missiles (optionnal)
|
||||
self.add_unit(self.sr2, "SR2", self.position.x - 40, self.position.y, self.heading)
|
||||
self.add_unit(
|
||||
self.sr2, "SR2", self.position.x - 40, self.position.y, self.heading
|
||||
)
|
||||
|
||||
# Command Post
|
||||
self.add_unit(self.cp, "CP", self.position.x, self.position.y, self.heading)
|
||||
|
||||
# 2 Tracking radars
|
||||
self.add_unit(self.tr1, "TR1", self.position.x - 40, self.position.y - 40, self.heading)
|
||||
self.add_unit(
|
||||
self.tr1, "TR1", self.position.x - 40, self.position.y - 40, self.heading
|
||||
)
|
||||
|
||||
self.add_unit(self.tr2, "TR2", self.position.x + 40, self.position.y - 40,
|
||||
self.heading)
|
||||
self.add_unit(
|
||||
self.tr2, "TR2", self.position.x + 40, self.position.y - 40, self.heading
|
||||
)
|
||||
|
||||
# 2 different launcher type (C & D)
|
||||
num_launchers = random.randint(6, 8)
|
||||
positions = self.get_circular_position(num_launchers, launcher_distance=100, coverage=360)
|
||||
positions = self.get_circular_position(
|
||||
num_launchers, launcher_distance=100, coverage=360
|
||||
)
|
||||
for i, position in enumerate(positions):
|
||||
if i%2 == 0:
|
||||
self.add_unit(self.ln1, "LN#" + str(i), position[0], position[1], position[2])
|
||||
if i % 2 == 0:
|
||||
self.add_unit(
|
||||
self.ln1, "LN#" + str(i), position[0], position[1], position[2]
|
||||
)
|
||||
else:
|
||||
self.add_unit(self.ln2, "LN#" + str(i), position[0], position[1], position[2])
|
||||
self.add_unit(
|
||||
self.ln2, "LN#" + str(i), position[0], position[1], position[2]
|
||||
)
|
||||
|
||||
self.generate_defensive_groups()
|
||||
|
||||
@ -67,10 +80,16 @@ class SA10Generator(AirDefenseGroupGenerator):
|
||||
aa_group = self.add_auxiliary_group("AA")
|
||||
num_launchers = random.randint(6, 8)
|
||||
positions = self.get_circular_position(
|
||||
num_launchers, launcher_distance=210, coverage=360)
|
||||
num_launchers, launcher_distance=210, coverage=360
|
||||
)
|
||||
for i, (x, y, heading) in enumerate(positions):
|
||||
self.add_unit_to_group(aa_group, AirDefence.SPAAA_ZSU_23_4_Shilka,
|
||||
f"AA#{i}", Point(x, y), heading)
|
||||
self.add_unit_to_group(
|
||||
aa_group,
|
||||
AirDefence.SPAAA_ZSU_23_4_Shilka,
|
||||
f"AA#{i}",
|
||||
Point(x, y),
|
||||
heading,
|
||||
)
|
||||
|
||||
|
||||
class Tier2SA10Generator(SA10Generator):
|
||||
@ -82,10 +101,16 @@ class Tier2SA10Generator(SA10Generator):
|
||||
pd_group = self.add_auxiliary_group("PD")
|
||||
num_launchers = random.randint(2, 4)
|
||||
positions = self.get_circular_position(
|
||||
num_launchers, launcher_distance=140, coverage=360)
|
||||
num_launchers, launcher_distance=140, coverage=360
|
||||
)
|
||||
for i, (x, y, heading) in enumerate(positions):
|
||||
self.add_unit_to_group(pd_group, AirDefence.SAM_SA_15_Tor_9A331,
|
||||
f"PD#{i}", Point(x, y), heading)
|
||||
self.add_unit_to_group(
|
||||
pd_group,
|
||||
AirDefence.SAM_SA_15_Tor_9A331,
|
||||
f"PD#{i}",
|
||||
Point(x, y),
|
||||
heading,
|
||||
)
|
||||
|
||||
|
||||
class Tier3SA10Generator(SA10Generator):
|
||||
@ -94,19 +119,31 @@ class Tier3SA10Generator(SA10Generator):
|
||||
aa_group = self.add_auxiliary_group("AA")
|
||||
num_launchers = random.randint(6, 8)
|
||||
positions = self.get_circular_position(
|
||||
num_launchers, launcher_distance=210, coverage=360)
|
||||
num_launchers, launcher_distance=210, coverage=360
|
||||
)
|
||||
for i, (x, y, heading) in enumerate(positions):
|
||||
self.add_unit_to_group(aa_group, AirDefence.SAM_SA_19_Tunguska_2S6,
|
||||
f"AA#{i}", Point(x, y), heading)
|
||||
self.add_unit_to_group(
|
||||
aa_group,
|
||||
AirDefence.SAM_SA_19_Tunguska_2S6,
|
||||
f"AA#{i}",
|
||||
Point(x, y),
|
||||
heading,
|
||||
)
|
||||
|
||||
# SA-15 for both shorter range targets and point defense.
|
||||
pd_group = self.add_auxiliary_group("PD")
|
||||
num_launchers = random.randint(2, 4)
|
||||
positions = self.get_circular_position(
|
||||
num_launchers, launcher_distance=140, coverage=360)
|
||||
num_launchers, launcher_distance=140, coverage=360
|
||||
)
|
||||
for i, (x, y, heading) in enumerate(positions):
|
||||
self.add_unit_to_group(pd_group, AirDefence.SAM_SA_15_Tor_9A331,
|
||||
f"PD#{i}", Point(x, y), heading)
|
||||
self.add_unit_to_group(
|
||||
pd_group,
|
||||
AirDefence.SAM_SA_15_Tor_9A331,
|
||||
f"PD#{i}",
|
||||
Point(x, y),
|
||||
heading,
|
||||
)
|
||||
|
||||
|
||||
class SA10BGenerator(Tier3SA10Generator):
|
||||
@ -186,4 +223,4 @@ class SA23Generator(Tier3SA10Generator):
|
||||
self.tr1 = highdigitsams.SAM_SA_23_S_300VM_9S32ME_TR
|
||||
self.tr2 = highdigitsams.SAM_SA_23_S_300VM_9S32ME_TR
|
||||
self.ln1 = highdigitsams.SAM_SA_23_S_300VM_9A82ME_LN
|
||||
self.ln2 = highdigitsams.SAM_SA_23_S_300VM_9A83ME_LN
|
||||
self.ln2 = highdigitsams.SAM_SA_23_S_300VM_9A83ME_LN
|
||||
|
||||
@ -17,14 +17,34 @@ class SA11Generator(AirDefenseGroupGenerator):
|
||||
price = 180
|
||||
|
||||
def generate(self):
|
||||
self.add_unit(AirDefence.SAM_SA_11_Buk_SR_9S18M1, "SR", self.position.x+20, self.position.y, self.heading)
|
||||
self.add_unit(AirDefence.SAM_SA_11_Buk_CC_9S470M1, "CC", self.position.x, self.position.y, self.heading)
|
||||
self.add_unit(
|
||||
AirDefence.SAM_SA_11_Buk_SR_9S18M1,
|
||||
"SR",
|
||||
self.position.x + 20,
|
||||
self.position.y,
|
||||
self.heading,
|
||||
)
|
||||
self.add_unit(
|
||||
AirDefence.SAM_SA_11_Buk_CC_9S470M1,
|
||||
"CC",
|
||||
self.position.x,
|
||||
self.position.y,
|
||||
self.heading,
|
||||
)
|
||||
|
||||
num_launchers = random.randint(2, 4)
|
||||
positions = self.get_circular_position(num_launchers, launcher_distance=140, coverage=180)
|
||||
positions = self.get_circular_position(
|
||||
num_launchers, launcher_distance=140, coverage=180
|
||||
)
|
||||
|
||||
for i, position in enumerate(positions):
|
||||
self.add_unit(AirDefence.SAM_SA_11_Buk_LN_9A310M1, "LN#" + str(i), position[0], position[1], position[2])
|
||||
self.add_unit(
|
||||
AirDefence.SAM_SA_11_Buk_LN_9A310M1,
|
||||
"LN#" + str(i),
|
||||
position[0],
|
||||
position[1],
|
||||
position[2],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def range(cls) -> AirDefenseRange:
|
||||
|
||||
@ -17,13 +17,33 @@ class SA13Generator(AirDefenseGroupGenerator):
|
||||
price = 50
|
||||
|
||||
def generate(self):
|
||||
self.add_unit(Unarmed.Transport_UAZ_469, "UAZ", self.position.x, self.position.y, self.heading)
|
||||
self.add_unit(Unarmed.Transport_KAMAZ_43101, "TRUCK", self.position.x+40, self.position.y, self.heading)
|
||||
self.add_unit(
|
||||
Unarmed.Transport_UAZ_469,
|
||||
"UAZ",
|
||||
self.position.x,
|
||||
self.position.y,
|
||||
self.heading,
|
||||
)
|
||||
self.add_unit(
|
||||
Unarmed.Transport_KAMAZ_43101,
|
||||
"TRUCK",
|
||||
self.position.x + 40,
|
||||
self.position.y,
|
||||
self.heading,
|
||||
)
|
||||
|
||||
num_launchers = random.randint(2, 3)
|
||||
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):
|
||||
self.add_unit(AirDefence.SAM_SA_13_Strela_10M3_9A35M3, "LN#" + str(i), position[0], position[1], position[2])
|
||||
self.add_unit(
|
||||
AirDefence.SAM_SA_13_Strela_10M3_9A35M3,
|
||||
"LN#" + str(i),
|
||||
position[0],
|
||||
position[1],
|
||||
position[2],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def range(cls) -> AirDefenseRange:
|
||||
|
||||
@ -15,10 +15,28 @@ class SA15Generator(AirDefenseGroupGenerator):
|
||||
price = 55
|
||||
|
||||
def generate(self):
|
||||
self.add_unit(AirDefence.SAM_SA_15_Tor_9A331, "ADS", self.position.x, self.position.y, self.heading)
|
||||
self.add_unit(Unarmed.Transport_UAZ_469, "EWR", self.position.x + 40, self.position.y, self.heading)
|
||||
self.add_unit(Unarmed.Transport_KAMAZ_43101, "TRUCK", self.position.x + 80, self.position.y, self.heading)
|
||||
self.add_unit(
|
||||
AirDefence.SAM_SA_15_Tor_9A331,
|
||||
"ADS",
|
||||
self.position.x,
|
||||
self.position.y,
|
||||
self.heading,
|
||||
)
|
||||
self.add_unit(
|
||||
Unarmed.Transport_UAZ_469,
|
||||
"EWR",
|
||||
self.position.x + 40,
|
||||
self.position.y,
|
||||
self.heading,
|
||||
)
|
||||
self.add_unit(
|
||||
Unarmed.Transport_KAMAZ_43101,
|
||||
"TRUCK",
|
||||
self.position.x + 80,
|
||||
self.position.y,
|
||||
self.heading,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def range(cls) -> AirDefenseRange:
|
||||
return AirDefenseRange.Medium
|
||||
return AirDefenseRange.Medium
|
||||
|
||||
@ -16,14 +16,31 @@ class SA17Generator(AirDefenseGroupGenerator):
|
||||
price = 180
|
||||
|
||||
def generate(self):
|
||||
self.add_unit(AirDefence.SAM_SA_11_Buk_SR_9S18M1, "SR", self.position.x + 20, self.position.y, self.heading)
|
||||
self.add_unit(AirDefence.SAM_SA_11_Buk_CC_9S470M1, "CC", self.position.x, self.position.y, self.heading)
|
||||
self.add_unit(
|
||||
AirDefence.SAM_SA_11_Buk_SR_9S18M1,
|
||||
"SR",
|
||||
self.position.x + 20,
|
||||
self.position.y,
|
||||
self.heading,
|
||||
)
|
||||
self.add_unit(
|
||||
AirDefence.SAM_SA_11_Buk_CC_9S470M1,
|
||||
"CC",
|
||||
self.position.x,
|
||||
self.position.y,
|
||||
self.heading,
|
||||
)
|
||||
|
||||
positions = self.get_circular_position(3, launcher_distance=140, coverage=180)
|
||||
|
||||
for i, position in enumerate(positions):
|
||||
self.add_unit(highdigitsams.SAM_SA_17_Buk_M1_2_LN_9A310M1_2, "LN#" + str(i), position[0], position[1],
|
||||
position[2])
|
||||
self.add_unit(
|
||||
highdigitsams.SAM_SA_17_Buk_M1_2_LN_9A310M1_2,
|
||||
"LN#" + str(i),
|
||||
position[0],
|
||||
position[1],
|
||||
position[2],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
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