Add a wrapper type for ground unit info.

This commit is contained in:
Dan Albert
2021-06-17 21:48:02 -07:00
parent 8a0824880e
commit 09704b6f37
32 changed files with 469 additions and 1145 deletions

View File

@@ -1,239 +1,17 @@
from __future__ import annotations
from enum import unique, Enum
from typing import Type
from dcs.vehicles import AirDefence, Infantry, Unarmed, Artillery, Armor
from dcs.unittype import VehicleType
from pydcs_extensions.frenchpack import frenchpack
@unique
class GroundUnitClass(Enum):
Tank = (
"Tank",
(
Armor.MBT_T_55,
Armor.MBT_T_72B,
Armor.MBT_T_72B3,
Armor.MBT_T_80U,
Armor.MBT_T_90,
Armor.MBT_Leopard_2A4,
Armor.MBT_Leopard_2A4_Trs,
Armor.MBT_Leopard_2A5,
Armor.MBT_Leopard_2A6M,
Armor.MBT_Leopard_1A3,
Armor.MBT_Leclerc,
Armor.MBT_Challenger_II,
Armor.MBT_Chieftain_Mk_3,
Armor.MBT_M1A2_Abrams,
Armor.MBT_M60A3_Patton,
Armor.MBT_Merkava_IV,
Armor.ZTZ_96B,
# WW2
# Axis
Armor.Tk_PzIV_H,
Armor.SPG_Sturmpanzer_IV_Brummbar,
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G,
Armor.HT_Pz_Kpfw_VI_Tiger_I,
Armor.HT_Pz_Kpfw_VI_Ausf__B_Tiger_II,
# Allies
Armor.Tk_M4_Sherman,
Armor.CT_Centaur_IV,
Armor.CT_Cromwell_IV,
Armor.HIT_Churchill_VII,
# Mods
frenchpack.DIM__TOYOTA_BLUE,
frenchpack.DIM__TOYOTA_GREEN,
frenchpack.DIM__TOYOTA_DESERT,
frenchpack.DIM__KAMIKAZE,
frenchpack.AMX_30B2,
frenchpack.Leclerc_Serie_XXI,
),
)
Atgm = (
"ATGM",
(
Armor.ATGM_HMMWV,
Armor.ATGM_VAB_Mephisto,
Armor.ATGM_Stryker,
Armor.IFV_BMP_2,
# WW2 (Tank Destroyers)
# Axxis
Armor.SPG_StuG_III_Ausf__G,
Armor.SPG_StuG_IV,
Armor.SPG_Jagdpanzer_IV,
Armor.SPG_Jagdpanther_G1,
Armor.SPG_Sd_Kfz_184_Elefant,
# Allies
Armor.SPG_M10_GMC,
Armor.MT_M4A4_Sherman_Firefly,
# Mods
frenchpack.VBAE_CRAB_MMP,
frenchpack.VAB_MEPHISTO,
frenchpack.TRM_2000_PAMELA,
),
)
Ifv = (
"IFV",
(
Armor.IFV_BMP_3,
Armor.IFV_BMP_2,
Armor.IFV_BMP_1,
Armor.IFV_Marder,
Armor.IFV_Warrior,
Armor.SPG_Stryker_MGS,
Armor.IFV_M2A2_Bradley,
Armor.IFV_BMD_1,
Armor.ZBD_04A,
# Mods
frenchpack.VBAE_CRAB,
frenchpack.VAB_T20_13,
),
)
Apc = (
"APC",
(
Armor.IFV_M1126_Stryker_ICV,
Armor.APC_M113,
Armor.APC_BTR_80,
Armor.IFV_BTR_82A,
Armor.APC_MTLB,
Armor.APC_AAV_7_Amphibious,
Armor.APC_TPz_Fuchs,
Armor.APC_BTR_RD,
# WW2
Armor.APC_M2A1_Halftrack,
Armor.APC_Sd_Kfz_251_Halftrack,
# Mods
frenchpack.VAB__50,
frenchpack.VBL__50,
frenchpack.VBL_AANF1,
),
)
Artillery = (
"Artillery",
(
Artillery.Grad_MRL_FDDM__FC,
Artillery.MLRS_9A52_Smerch_HE_300mm,
Artillery.SPH_2S1_Gvozdika_122mm,
Artillery.SPH_2S3_Akatsia_152mm,
Artillery.MLRS_BM_21_Grad_122mm,
Artillery.MLRS_9K57_Uragan_BM_27_220mm,
Artillery.SPH_M109_Paladin_155mm,
Artillery.MLRS_M270_227mm,
Artillery.SPM_2S9_Nona_120mm_M,
Artillery.SPH_Dana_vz77_152mm,
Artillery.SPH_T155_Firtina_155mm,
Artillery.PLZ_05,
Artillery.SPH_2S19_Msta_152mm,
Artillery.MLRS_9A52_Smerch_CM_300mm,
# WW2
Artillery.SPG_M12_GMC_155mm,
),
)
Logistics = (
"Logistics",
(
Unarmed.Carrier_M30_Cargo,
Unarmed.Truck_M818_6x6,
Unarmed.Truck_KAMAZ_43101,
Unarmed.Truck_Ural_375,
Unarmed.Truck_GAZ_66,
Unarmed.Truck_GAZ_3307,
Unarmed.Truck_GAZ_3308,
Unarmed.Truck_Ural_4320_31_Arm_d,
Unarmed.Truck_Ural_4320T,
Unarmed.Truck_Opel_Blitz,
Unarmed.LUV_Kubelwagen_82,
Unarmed.Carrier_Sd_Kfz_7_Tractor,
Unarmed.LUV_Kettenrad,
Unarmed.Car_Willys_Jeep,
Unarmed.LUV_Land_Rover_109,
Unarmed.Truck_Land_Rover_101_FC,
# Mods
frenchpack.VBL,
frenchpack.VAB,
),
)
Recon = (
"Recon",
(
Armor.Scout_HMMWV,
Armor.Scout_Cobra,
Armor.LT_PT_76,
Armor.IFV_LAV_25,
Armor.Scout_BRDM_2,
# WW2
Armor.LT_Mk_VII_Tetrarch,
Armor.IFV_Sd_Kfz_234_2_Puma,
Armor.Car_M8_Greyhound_Armored,
Armor.Car_Daimler_Armored,
# Mods
frenchpack.ERC_90,
frenchpack.AMX_10RCR,
frenchpack.AMX_10RCR_SEPAR,
),
)
Infantry = (
"Infantry",
(
Infantry.Insurgent_AK_74,
Infantry.Infantry_AK_74,
Infantry.Infantry_M1_Garand,
Infantry.Infantry_Mauser_98,
Infantry.Infantry_SMLE_No_4_Mk_1,
Infantry.Infantry_M4_Georgia,
Infantry.Infantry_AK_74_Rus,
Infantry.Paratrooper_AKS,
Infantry.Paratrooper_RPG_16,
Infantry.Infantry_M249,
Infantry.Infantry_M4,
Infantry.Infantry_RPG,
),
)
Shorads = (
"SHORADS",
(
AirDefence.SPAAA_ZU_23_2_Mounted_Ural_375,
AirDefence.SPAAA_ZU_23_2_Insurgent_Mounted_Ural_375,
AirDefence.SPAAA_ZSU_57_2,
AirDefence.SPAAA_ZSU_23_4_Shilka_Gun_Dish,
AirDefence.SAM_SA_8_Osa_Gecko_TEL,
AirDefence.SAM_SA_9_Strela_1_Gaskin_TEL,
AirDefence.SAM_SA_13_Strela_10M3_Gopher_TEL,
AirDefence.SAM_SA_15_Tor_Gauntlet,
AirDefence.SAM_SA_19_Tunguska_Grison,
AirDefence.SPAAA_Gepard,
AirDefence.SPAAA_Vulcan_M163,
AirDefence.SAM_Linebacker___Bradley_M6,
AirDefence.SAM_Chaparral_M48,
AirDefence.SAM_Avenger__Stinger,
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,
AirDefence.AAA_8_8cm_Flak_41,
AirDefence.AAA_Bofors_40mm,
AirDefence.AAA_S_60_57mm,
AirDefence.AAA_M1_37mm,
AirDefence.AAA_QF_3_7,
),
)
def __init__(
self, class_name: str, unit_list: tuple[Type[VehicleType], ...]
) -> None:
self.class_name = class_name
self.unit_list = unit_list
def __contains__(self, unit_type: Type[VehicleType]) -> bool:
return unit_type in self.unit_list
Tank = "Tank"
Atgm = "ATGM"
Ifv = "IFV"
Apc = "APC"
Artillery = "Artillery"
Logistics = "Logistics"
Recon = "Recon"
Infantry = "Infantry"
Shorads = "SHORADS"
Manpads = "MANPADS"

View File

@@ -1,8 +1,7 @@
import json
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import List, Optional, Type, Union
from typing import Optional, Type, Union
from dcs.countries import country_dict
from dcs.helicopters import (
@@ -21,8 +20,6 @@ from dcs.planes import (
plane_map,
)
from dcs.ships import (
Boat_Armed_Hi_speed,
Bulker_Yakushev,
CVN_71_Theodore_Roosevelt,
CVN_72_Abraham_Lincoln,
CVN_73_George_Washington,
@@ -30,21 +27,12 @@ from dcs.ships import (
CVN_75_Harry_S__Truman,
CV_1143_5_Admiral_Kuznetsov,
CV_1143_5_Admiral_Kuznetsov_2017,
Cargo_Ivanov,
LHA_1_Tarawa,
Tanker_Elnya_160,
ship_map,
)
from dcs.terrain.terrain import Airport
from dcs.unit import Ship, Unit, Vehicle
from dcs.unitgroup import ShipGroup, StaticGroup
from dcs.unittype import UnitType, VehicleType
from dcs.unittype import UnitType
from dcs.vehicles import (
AirDefence,
Armor,
Artillery,
Infantry,
Unarmed,
vehicle_map,
)
@@ -250,271 +238,6 @@ For example, player accessible Hornet is called `FA_18C_hornet`, and MANPAD Igla
# to be cheap enough to repair with a single turn's income.
RUNWAY_REPAIR_COST = 100
"""
Prices for the aircraft.
This defines both price for the player (although only aircraft listed in CAP/CAS/Transport/Armor/AirDefense roles will be purchasable)
and prioritization for the enemy (i.e. less important bases will receive units with lower price)
"""
PRICES = {
# armor
Armor.APC_MTLB: 4,
Artillery.Grad_MRL_FDDM__FC: 4,
Armor.Scout_BRDM_2: 6,
Armor.APC_BTR_RD: 6,
Armor.APC_BTR_80: 8,
Armor.IFV_BTR_82A: 10,
Armor.MBT_T_55: 18,
Armor.MBT_T_72B: 20,
Armor.MBT_T_72B3: 25,
Armor.MBT_T_80U: 25,
Armor.MBT_T_90: 30,
Armor.IFV_BMD_1: 8,
Armor.IFV_BMP_1: 14,
Armor.IFV_BMP_2: 16,
Armor.IFV_BMP_3: 18,
Armor.LT_PT_76: 9,
Armor.ZBD_04A: 12,
Armor.ZTZ_96B: 30,
Armor.Scout_Cobra: 4,
Armor.APC_M113: 6,
Armor.Scout_HMMWV: 2,
Armor.ATGM_HMMWV: 8,
Armor.ATGM_VAB_Mephisto: 12,
Armor.IFV_M2A2_Bradley: 12,
Armor.IFV_M1126_Stryker_ICV: 10,
Armor.SPG_Stryker_MGS: 14,
Armor.ATGM_Stryker: 12,
Armor.MBT_M60A3_Patton: 16,
Armor.MBT_M1A2_Abrams: 25,
Armor.MBT_Leclerc: 25,
Armor.MBT_Leopard_1A3: 18,
Armor.MBT_Leopard_2A4: 20,
Armor.MBT_Leopard_2A4_Trs: 20,
Armor.MBT_Leopard_2A5: 22,
Armor.MBT_Leopard_2A6M: 25,
Armor.MBT_Merkava_IV: 25,
Armor.APC_TPz_Fuchs: 5,
Armor.MBT_Challenger_II: 25,
Armor.MBT_Chieftain_Mk_3: 20,
Armor.IFV_Marder: 10,
Armor.IFV_Warrior: 10,
Armor.IFV_LAV_25: 7,
Armor.APC_AAV_7_Amphibious: 10,
Artillery.MLRS_M270_227mm: 55,
Artillery.SPH_M109_Paladin_155mm: 25,
Artillery.SPM_2S9_Nona_120mm_M: 12,
Artillery.SPH_2S1_Gvozdika_122mm: 18,
Artillery.SPH_2S3_Akatsia_152mm: 24,
Artillery.SPH_2S19_Msta_152mm: 30,
Artillery.MLRS_BM_21_Grad_122mm: 15,
Artillery.MLRS_9K57_Uragan_BM_27_220mm: 50,
Artillery.MLRS_9A52_Smerch_HE_300mm: 40,
Artillery.Mortar_2B11_120mm: 4,
Artillery.SPH_Dana_vz77_152mm: 26,
Artillery.PLZ_05: 25,
Artillery.SPH_T155_Firtina_155mm: 28,
Artillery.MLRS_9A52_Smerch_CM_300mm: 60,
Unarmed.LUV_UAZ_469_Jeep: 3,
Unarmed.Truck_Ural_375: 3,
Unarmed.Truck_GAZ_3307: 2,
Infantry.Infantry_M4: 1,
Infantry.Infantry_AK_74: 1,
Unarmed.Truck_M818_6x6: 3,
Unarmed.LUV_Land_Rover_109: 1,
Unarmed.Truck_GAZ_3308: 1,
Unarmed.Truck_GAZ_66: 1,
Unarmed.Truck_KAMAZ_43101: 1,
Unarmed.Truck_Land_Rover_101_FC: 1,
Unarmed.Truck_Ural_4320_31_Arm_d: 1,
Unarmed.Truck_Ural_4320T: 1,
# WW2
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G: 24,
Armor.Tk_PzIV_H: 16,
Armor.HT_Pz_Kpfw_VI_Tiger_I: 24,
Armor.HT_Pz_Kpfw_VI_Ausf__B_Tiger_II: 26,
Armor.SPG_Jagdpanther_G1: 18,
Armor.SPG_Jagdpanzer_IV: 11,
Armor.SPG_Sd_Kfz_184_Elefant: 18,
Armor.APC_Sd_Kfz_251_Halftrack: 4,
Armor.IFV_Sd_Kfz_234_2_Puma: 8,
Armor.Tk_M4_Sherman: 12,
Armor.MT_M4A4_Sherman_Firefly: 16,
Armor.CT_Cromwell_IV: 12,
Unarmed.Carrier_M30_Cargo: 2,
Armor.APC_M2A1_Halftrack: 4,
Armor.CT_Centaur_IV: 10,
Armor.HIT_Churchill_VII: 16,
Armor.Car_M8_Greyhound_Armored: 8,
Armor.SPG_M10_GMC: 14,
Armor.SPG_StuG_III_Ausf__G: 12,
Armor.SPG_StuG_IV: 14,
Artillery.SPG_M12_GMC_155mm: 10,
Armor.SPG_Sturmpanzer_IV_Brummbar: 10,
Armor.Car_Daimler_Armored: 8,
Armor.LT_Mk_VII_Tetrarch: 8,
Unarmed.Tractor_M4_Hi_Speed: 2,
Unarmed.Carrier_Sd_Kfz_7_Tractor: 1,
Unarmed.LUV_Kettenrad: 1,
Unarmed.LUV_Kubelwagen_82: 1,
Unarmed.Truck_Opel_Blitz: 1,
Unarmed.Truck_Bedford: 1,
Unarmed.Truck_GMC_Jimmy_6x6_Truck: 1,
Unarmed.Car_Willys_Jeep: 1,
# ship
CV_1143_5_Admiral_Kuznetsov: 100,
CVN_74_John_C__Stennis: 100,
LHA_1_Tarawa: 50,
Bulker_Yakushev: 10,
Boat_Armed_Hi_speed: 10,
Cargo_Ivanov: 10,
Tanker_Elnya_160: 10,
# Air Defence units
AirDefence.SAM_SA_19_Tunguska_Grison: 30,
AirDefence.SAM_SA_6_Kub_Gainful_TEL: 20,
AirDefence.SAM_SA_3_S_125_Goa_LN: 6,
AirDefence.SAM_SA_11_Buk_Gadfly_Fire_Dome_TEL: 30,
AirDefence.SAM_SA_11_Buk_Gadfly_C2: 25,
AirDefence.SAM_SA_11_Buk_Gadfly_Snow_Drift_SR: 28,
AirDefence.SAM_SA_8_Osa_Gecko_TEL: 28,
AirDefence.SAM_SA_15_Tor_Gauntlet: 40,
AirDefence.SAM_SA_13_Strela_10M3_Gopher_TEL: 16,
AirDefence.SAM_SA_9_Strela_1_Gaskin_TEL: 12,
AirDefence.SAM_SA_8_Osa_LD_9T217: 22,
AirDefence.SAM_Patriot_CR__AMG_AN_MRC_137: 35,
AirDefence.SAM_Patriot_ECS: 30,
AirDefence.SPAAA_Gepard: 24,
AirDefence.SAM_Hawk_Platoon_Command_Post__PCP: 14,
AirDefence.SPAAA_Vulcan_M163: 10,
AirDefence.SAM_Hawk_LN_M192: 8,
AirDefence.SAM_Chaparral_M48: 16,
AirDefence.SAM_Linebacker___Bradley_M6: 18,
AirDefence.SAM_Patriot_LN: 15,
AirDefence.SAM_Avenger__Stinger: 20,
AirDefence.SAM_Patriot_EPP_III: 15,
AirDefence.SAM_Patriot_C2_ICC: 18,
AirDefence.SAM_Roland_ADS: 12,
AirDefence.MANPADS_Stinger: 6,
AirDefence.MANPADS_Stinger_C2_Desert: 4,
AirDefence.MANPADS_Stinger_C2: 4,
AirDefence.SPAAA_ZSU_23_4_Shilka_Gun_Dish: 10,
AirDefence.SPAAA_ZSU_57_2: 12,
AirDefence.AAA_ZU_23_Closed_Emplacement: 6,
AirDefence.AAA_ZU_23_Emplacement: 6,
AirDefence.SPAAA_ZU_23_2_Mounted_Ural_375: 7,
AirDefence.AAA_ZU_23_Insurgent_Closed_Emplacement: 6,
AirDefence.SPAAA_ZU_23_2_Insurgent_Mounted_Ural_375: 7,
AirDefence.AAA_ZU_23_Insurgent_Emplacement: 6,
AirDefence.MANPADS_SA_18_Igla_Grouse: 10,
AirDefence.MANPADS_SA_18_Igla_Grouse_C2: 8,
AirDefence.MANPADS_SA_18_Igla_S_Grouse: 12,
AirDefence.MANPADS_SA_18_Igla_S_Grouse_C2: 8,
AirDefence.EWR_1L13: 30,
AirDefence.SAM_SA_6_Kub_Straight_Flush_STR: 22,
AirDefence.EWR_55G6: 30,
AirDefence.MCC_SR_Sborka_Dog_Ear_SR: 10,
AirDefence.SAM_Hawk_TR__AN_MPQ_46: 14,
AirDefence.SAM_Hawk_SR__AN_MPQ_50: 18,
AirDefence.SAM_Patriot_STR: 22,
AirDefence.SAM_Hawk_CWAR_AN_MPQ_55: 20,
AirDefence.SAM_P19_Flat_Face_SR__SA_2_3: 14,
AirDefence.SAM_Roland_EWR: 16,
AirDefence.SAM_SA_3_S_125_Low_Blow_TR: 14,
AirDefence.SAM_SA_2_S_75_Guideline_LN: 8,
AirDefence.SAM_SA_2_S_75_Fan_Song_TR: 12,
AirDefence.SAM_Rapier_LN: 6,
AirDefence.SAM_Rapier_Tracker: 6,
AirDefence.SAM_Rapier_Blindfire_TR: 8,
AirDefence.HQ_7_Self_Propelled_LN: 20,
AirDefence.HQ_7_Self_Propelled_STR: 24,
AirDefence.AAA_8_8cm_Flak_18: 6,
AirDefence.AAA_Flak_38_20mm: 6,
AirDefence.AAA_8_8cm_Flak_36: 8,
AirDefence.AAA_8_8cm_Flak_37: 9,
AirDefence.AAA_Flak_Vierling_38_Quad_20mm: 5,
AirDefence.AAA_SP_Kdo_G_40: 8,
AirDefence.SL_Flakscheinwerfer_37: 4,
AirDefence.PU_Maschinensatz_33: 10,
AirDefence.AAA_8_8cm_Flak_41: 10,
AirDefence.EWR_FuMG_401_Freya_LZ: 25,
AirDefence.AAA_Bofors_40mm: 8,
AirDefence.AAA_S_60_57mm: 8,
AirDefence.AAA_M1_37mm: 7,
AirDefence.AAA_M45_Quadmount_HB_12_7mm: 4,
AirDefence.AAA_QF_3_7: 10,
# FRENCH PACK MOD
frenchpack.AMX_10RCR: 10,
frenchpack.AMX_10RCR_SEPAR: 12,
frenchpack.ERC_90: 12,
frenchpack.MO_120_RT: 10,
frenchpack._53T2: 4,
frenchpack.TRM_2000: 4,
frenchpack.TRM_2000_Fuel: 4,
frenchpack.TRM_2000_53T2: 8,
frenchpack.TRM_2000_PAMELA: 14,
frenchpack.VAB_MEDICAL: 8,
frenchpack.VAB: 6,
frenchpack.VAB__50: 4,
frenchpack.VAB_T20_13: 6,
frenchpack.VAB_MEPHISTO: 8,
frenchpack.VAB_MORTIER: 10,
frenchpack.VBL__50: 4,
frenchpack.VBL_AANF1: 2,
frenchpack.VBL: 1,
frenchpack.VBAE_CRAB: 8,
frenchpack.VBAE_CRAB_MMP: 12,
frenchpack.AMX_30B2: 18,
frenchpack.Tracma_TD_1500: 2,
frenchpack.Infantry_Soldier_JTAC: 1,
frenchpack.Leclerc_Serie_XXI: 35,
frenchpack.DIM__TOYOTA_BLUE: 2,
frenchpack.DIM__TOYOTA_GREEN: 2,
frenchpack.DIM__TOYOTA_DESERT: 2,
frenchpack.DIM__KAMIKAZE: 6,
# SA-10
AirDefence.SAM_SA_10_S_300_Grumble_C2: 18,
AirDefence.SAM_SA_10_S_300_Grumble_Flap_Lid_TR: 24,
AirDefence.SAM_SA_10_S_300_Grumble_Clam_Shell_SR: 30,
AirDefence.SAM_SA_10_S_300_Grumble_Big_Bird_SR: 30,
AirDefence.SAM_SA_10_S_300_Grumble_TEL_C: 22,
AirDefence.SAM_SA_10_S_300_Grumble_TEL_D: 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,
highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85CE: 24,
highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85DE: 24,
highdigitsams.SAM_SA_10B_S_300PS_30N6_TR: 26,
highdigitsams.SAM_SA_10B_S_300PS_40B6M_TR: 26,
highdigitsams.SAM_SA_10B_S_300PS_40B6MD_SR: 32,
highdigitsams.SAM_SA_10B_S_300PS_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,
highdigitsams.SAM_SA_20_S_300PMU1_SR_5N66E: 38,
highdigitsams.SAM_SA_20_S_300PMU1_SR_64N6E: 38,
highdigitsams.SAM_SA_20_S_300PMU1_LN_5P85CE: 28,
highdigitsams.SAM_SA_20_S_300PMU1_LN_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,
}
"""
Units separated by country.
country : DCS Country name
@@ -620,107 +343,6 @@ def upgrade_to_supercarrier(unit, name: str):
return unit
MANPADS: List[Type[VehicleType]] = [
AirDefence.MANPADS_SA_18_Igla_Grouse,
AirDefence.MANPADS_SA_18_Igla_S_Grouse,
AirDefence.MANPADS_Stinger,
]
INFANTRY: List[VehicleType] = [
Infantry.Paratrooper_AKS,
Infantry.Paratrooper_AKS,
Infantry.Paratrooper_AKS,
Infantry.Paratrooper_AKS,
Infantry.Paratrooper_AKS,
Infantry.Infantry_RPG,
Infantry.Infantry_M4,
Infantry.Infantry_M4,
Infantry.Infantry_M4,
Infantry.Infantry_M4,
Infantry.Infantry_M4,
Infantry.Infantry_M249,
Artillery.Mortar_2B11_120mm,
Infantry.Infantry_AK_74,
Infantry.Infantry_AK_74,
Infantry.Infantry_AK_74,
Infantry.Infantry_AK_74,
Infantry.Infantry_AK_74,
Infantry.Paratrooper_RPG_16,
Infantry.Infantry_M4_Georgia,
Infantry.Infantry_M4_Georgia,
Infantry.Infantry_M4_Georgia,
Infantry.Infantry_M4_Georgia,
Infantry.Infantry_AK_74_Rus,
Infantry.Infantry_AK_74_Rus,
Infantry.Infantry_AK_74_Rus,
Infantry.Infantry_AK_74_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_Mauser_98,
Infantry.Infantry_M1_Garand,
Infantry.Infantry_M1_Garand,
Infantry.Infantry_M1_Garand,
Infantry.Insurgent_AK_74,
Infantry.Insurgent_AK_74,
Infantry.Insurgent_AK_74,
]
def find_manpad(country_name: str) -> List[VehicleType]:
return [x for x in MANPADS if x in FACTIONS[country_name].infantry_units]
def find_infantry(country_name: str, allow_manpad: bool = False) -> List[VehicleType]:
if allow_manpad:
inf = INFANTRY + MANPADS
else:
inf = INFANTRY
return [x for x in inf if x in FACTIONS[country_name].infantry_units]
def unit_type_name(unit_type) -> str:
return unit_type.id and unit_type.id or unit_type.name
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: Type[UnitType], request_type: str
) -> str:
original_name = unit_type.name and unit_type.name or unit_type.id
default_value = None
faction_value = None
with UNITINFOTEXT_PATH.open("r", encoding="utf-8") as fdata:
data = json.load(fdata)
type_exists = data.get(unit_type.id)
if type_exists is not None:
for faction in type_exists:
if default_value is None:
default_exists = faction.get("default")
if default_exists is not None:
default_value = default_exists.get(request_type)
if faction_value is None:
faction_exists = faction.get(country_name)
if faction_exists is not None:
faction_value = faction_exists.get(request_type)
if default_value is None:
if request_type == "text":
return "WIP - This unit doesn't have any description text yet."
if request_type == "name":
return original_name
else:
return "Unknown"
if faction_value is None:
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]
@@ -734,15 +356,6 @@ def unit_type_from_name(name: str) -> Optional[Type[UnitType]]:
return None
def unit_type_of(unit: Unit) -> UnitType:
if isinstance(unit, Vehicle):
return vehicle_map[unit.type]
elif isinstance(unit, Ship):
return ship_map[unit.type]
else:
return unit.type
def country_id_from_name(name):
for k, v in country_dict.items():
if v.name == name:

View File

@@ -12,6 +12,7 @@ from dcs.helicopters import helicopter_map
from dcs.planes import plane_map
from dcs.unittype import FlyingType
from game.dcs.unittype import UnitType
from game.radio.channels import (
ChannelNamer,
RadioChannelAllocator,
@@ -90,15 +91,7 @@ class RadioConfig:
@dataclass(frozen=True)
class AircraftType:
dcs_unit_type: Type[FlyingType]
name: str
description: str
year_introduced: str
country_of_origin: str
manufacturer: str
role: str
price: int
class AircraftType(UnitType[FlyingType]):
carrier_capable: bool
lha_capable: bool
always_keeps_gun: bool

View File

@@ -0,0 +1,95 @@
from __future__ import annotations
import logging
from collections import defaultdict
from dataclasses import dataclass
from pathlib import Path
from typing import Type, Optional, ClassVar, Iterator
import yaml
from dcs.unittype import VehicleType
from dcs.vehicles import vehicle_map
from game.data.groundunitclass import GroundUnitClass
from game.dcs.unittype import UnitType
@dataclass(frozen=True)
class GroundUnitType(UnitType[VehicleType]):
unit_class: Optional[GroundUnitClass]
spawn_weight: int
_by_name: ClassVar[dict[str, GroundUnitType]] = {}
_by_unit_type: ClassVar[
dict[Type[VehicleType], list[GroundUnitType]]
] = defaultdict(list)
_loaded: ClassVar[bool] = False
def __str__(self) -> str:
return self.name
@property
def dcs_id(self) -> str:
return self.dcs_unit_type.id
@classmethod
def register(cls, aircraft_type: GroundUnitType) -> None:
cls._by_name[aircraft_type.name] = aircraft_type
cls._by_unit_type[aircraft_type.dcs_unit_type].append(aircraft_type)
@classmethod
def named(cls, name: str) -> GroundUnitType:
if not cls._loaded:
cls._load_all()
return cls._by_name[name]
@classmethod
def for_dcs_type(cls, dcs_unit_type: Type[VehicleType]) -> Iterator[GroundUnitType]:
yield from cls._by_unit_type[dcs_unit_type]
@staticmethod
def _each_unit_type() -> Iterator[Type[VehicleType]]:
yield from vehicle_map.values()
@classmethod
def _load_all(cls) -> None:
for unit_type in cls._each_unit_type():
for data in cls._each_variant_of(unit_type):
cls.register(data)
cls._loaded = True
@classmethod
def _each_variant_of(cls, vehicle: Type[VehicleType]) -> Iterator[GroundUnitType]:
data_path = Path("resources/units/ground_units") / f"{vehicle.id}.yaml"
if not data_path.exists():
logging.warning(f"No data for {vehicle.id}; it will not be available")
return
with data_path.open() as data_file:
data = yaml.safe_load(data_file)
try:
introduction = data["introduced"]
if introduction is None:
introduction = "N/A"
except KeyError:
introduction = "No data."
class_name = data.get("class")
unit_class: Optional[GroundUnitClass] = None
if class_name is not None:
unit_class = GroundUnitClass(class_name)
for variant in data.get("variants", [vehicle.id]):
yield GroundUnitType(
dcs_unit_type=vehicle,
unit_class=unit_class,
spawn_weight=data.get("spawn_weight", 0),
name=variant,
description=data.get("description", "No data."),
year_introduced=introduction,
country_of_origin=data.get("origin", "No data."),
manufacturer=data.get("manufacturer", "No data."),
role=data.get("role", "No data."),
price=data.get("price", 1),
)

21
game/dcs/unittype.py Normal file
View File

@@ -0,0 +1,21 @@
from dataclasses import dataclass
from typing import TypeVar, Generic, Type
from dcs.unittype import UnitType as DcsUnitType
DcsUnitTypeT = TypeVar("DcsUnitTypeT", bound=DcsUnitType)
@dataclass(frozen=True)
class UnitType(Generic[DcsUnitTypeT]):
dcs_unit_type: Type[DcsUnitTypeT]
name: str
description: str
year_introduced: str
country_of_origin: str
manufacturer: str
role: str
price: int
def __str__(self) -> str:
return self.name

View File

@@ -14,14 +14,12 @@ from typing import (
Dict,
Iterator,
List,
Type,
TYPE_CHECKING,
)
from dcs.unittype import UnitType
from game import db
from game.dcs.aircrafttype import AircraftType
from game.dcs.groundunittype import GroundUnitType
from game.theater import Airfield, ControlPoint
from game.transfers import CargoShip
from game.unitmap import (
@@ -183,8 +181,8 @@ class Debriefing:
def casualty_count(self, control_point: ControlPoint) -> int:
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]:
losses_by_type: Dict[Type[UnitType], int] = defaultdict(int)
def front_line_losses_by_type(self, player: bool) -> dict[GroundUnitType, int]:
losses_by_type: dict[GroundUnitType, int] = defaultdict(int)
if player:
losses = self.ground_losses.player_front_line
else:
@@ -193,8 +191,8 @@ class Debriefing:
losses_by_type[loss.unit_type] += 1
return losses_by_type
def convoy_losses_by_type(self, player: bool) -> Dict[Type[UnitType], int]:
losses_by_type: Dict[Type[UnitType], int] = defaultdict(int)
def convoy_losses_by_type(self, player: bool) -> dict[GroundUnitType, int]:
losses_by_type: dict[GroundUnitType, int] = defaultdict(int)
if player:
losses = self.ground_losses.player_convoy
else:
@@ -203,8 +201,8 @@ class Debriefing:
losses_by_type[loss.unit_type] += 1
return losses_by_type
def cargo_ship_losses_by_type(self, player: bool) -> Dict[Type[UnitType], int]:
losses_by_type: Dict[Type[UnitType], int] = defaultdict(int)
def cargo_ship_losses_by_type(self, player: bool) -> dict[GroundUnitType, int]:
losses_by_type: dict[GroundUnitType, int] = defaultdict(int)
if player:
ships = self.ground_losses.player_cargo_ships
else:
@@ -214,8 +212,8 @@ class Debriefing:
losses_by_type[unit_type] += count
return losses_by_type
def airlift_losses_by_type(self, player: bool) -> Dict[Type[UnitType], int]:
losses_by_type: Dict[Type[UnitType], int] = defaultdict(int)
def airlift_losses_by_type(self, player: bool) -> dict[GroundUnitType, int]:
losses_by_type: dict[GroundUnitType, int] = defaultdict(int)
if player:
losses = self.ground_losses.player_airlifts
else:

View File

@@ -14,6 +14,7 @@ from game.operation.operation import Operation
from game.theater import ControlPoint
from gen import AirTaskingOrder
from gen.ground_forces.combat_stance import CombatStance
from ..dcs.groundunittype import GroundUnitType
from ..unitmap import UnitMap
if TYPE_CHECKING:
@@ -439,7 +440,7 @@ class Event:
# Also transfer pending deliveries.
for unit_type, count in source.pending_unit_deliveries.units.items():
if not issubclass(unit_type, VehicleType):
if not isinstance(unit_type, GroundUnitType):
continue
if count <= 0:
# Don't transfer *sales*...

View File

@@ -1,14 +1,13 @@
from __future__ import annotations
import itertools
import logging
from dataclasses import dataclass, field
from typing import Optional, Dict, Type, List, Any, cast, Iterator
from typing import Optional, Dict, Type, List, Any, Iterator
import dcs
from dcs.countries import country_dict
from dcs.planes import plane_map
from dcs.unittype import FlyingType, ShipType, VehicleType, UnitType
from dcs.vehicles import Armor, Unarmed, Infantry, Artillery, AirDefence
from dcs.unittype import ShipType, UnitType
from game.data.building_data import (
WW2_ALLIES_BUILDINGS,
@@ -24,7 +23,7 @@ from game.data.doctrine import (
)
from game.data.groundunitclass import GroundUnitClass
from game.dcs.aircrafttype import AircraftType
from pydcs_extensions.mod_units import MODDED_VEHICLES
from game.dcs.groundunittype import GroundUnitType
@dataclass
@@ -55,16 +54,16 @@ class Faction:
tankers: List[AircraftType] = field(default_factory=list)
# Available frontline units
frontline_units: List[Type[VehicleType]] = field(default_factory=list)
frontline_units: List[GroundUnitType] = field(default_factory=list)
# Available artillery units
artillery_units: List[Type[VehicleType]] = field(default_factory=list)
artillery_units: List[GroundUnitType] = field(default_factory=list)
# Infantry units used
infantry_units: List[Type[VehicleType]] = field(default_factory=list)
infantry_units: List[GroundUnitType] = field(default_factory=list)
# Logistics units used
logistics_units: List[Type[VehicleType]] = field(default_factory=list)
logistics_units: List[GroundUnitType] = field(default_factory=list)
# Possible SAMS site generators for this faction
air_defenses: List[str] = field(default_factory=list)
@@ -135,15 +134,11 @@ class Faction:
#: both will use it.
unrestricted_satnav: bool = False
def has_access_to_unittype(self, unitclass: GroundUnitClass) -> bool:
has_access = False
for vehicle in unitclass.unit_list:
if vehicle in self.frontline_units:
def has_access_to_unittype(self, unit_class: GroundUnitClass) -> bool:
for vehicle in itertools.chain(self.frontline_units, self.artillery_units):
if vehicle.unit_class is unit_class:
return True
if vehicle in self.artillery_units:
return True
return has_access
return False
@classmethod
def from_json(cls: Type[Faction], json: Dict[str, Any]) -> Faction:
@@ -172,10 +167,18 @@ class Faction:
set(faction.aircrafts + faction.awacs + faction.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 = [
GroundUnitType.named(n) for n in json.get("frontline_units", [])
]
faction.artillery_units = [
GroundUnitType.named(n) for n in json.get("artillery_units", [])
]
faction.infantry_units = [
GroundUnitType.named(n) for n in json.get("infantry_units", [])
]
faction.logistics_units = [
GroundUnitType.named(n) for n in json.get("logistics_units", [])
]
faction.ewrs = json.get("ewrs", [])
@@ -242,55 +245,24 @@ class Faction:
return faction
@property
def ground_units(self) -> Iterator[Type[VehicleType]]:
def ground_units(self) -> Iterator[GroundUnitType]:
yield from self.artillery_units
yield from self.frontline_units
yield from self.logistics_units
def unit_loader(unit: str, class_repository: List[Any]) -> Optional[Type[UnitType]]:
"""
Find unit by name
:param unit: Unit name as string
:param class_repository: Repository of classes (Either a module, a class, or a list of classes)
:return: The unit as a PyDCS type
"""
if unit is None:
return None
elif unit in plane_map.keys():
return plane_map[unit]
else:
for mother_class in class_repository:
if getattr(mother_class, unit, None) is not None:
return getattr(mother_class, unit)
if type(mother_class) is list:
for m in mother_class:
if m.__name__ == unit:
return m
logging.error(f"FACTION ERROR : Unable to find {unit} in pydcs")
return None
def load_vehicle(name: str) -> Optional[Type[VehicleType]]:
return cast(
Optional[FlyingType],
unit_loader(
name, [Infantry, Unarmed, Armor, AirDefence, Artillery, MODDED_VEHICLES]
),
)
def load_all_vehicles(data) -> List[Type[VehicleType]]:
items = []
for name in data:
item = load_vehicle(name)
if item is not None:
items.append(item)
return items
def infantry_with_class(
self, unit_class: GroundUnitClass
) -> Iterator[GroundUnitType]:
for unit in self.infantry_units:
if unit.unit_class is unit_class:
yield unit
def load_ship(name: str) -> Optional[Type[ShipType]]:
return cast(Optional[FlyingType], unit_loader(name, [dcs.ships]))
if (ship := getattr(dcs.ships, name, None)) is not None:
return ship
logging.error(f"FACTION ERROR : Unable to find {name} in dcs.ships")
return None
def load_all_ships(data) -> List[Type[ShipType]]:

View File

@@ -3,13 +3,12 @@ from __future__ import annotations
import math
import random
from dataclasses import dataclass
from typing import Iterator, List, Optional, TYPE_CHECKING, Tuple, Type
from dcs.unittype import FlyingType, VehicleType
from typing import Iterator, List, Optional, TYPE_CHECKING, Tuple
from game import db
from game.data.groundunitclass import GroundUnitClass
from game.dcs.aircrafttype import AircraftType
from game.dcs.groundunittype import GroundUnitType
from game.factions.faction import Faction
from game.theater import ControlPoint, MissionTarget
from game.utils import Distance
@@ -148,17 +147,17 @@ class ProcurementAi:
def affordable_ground_unit_of_class(
self, budget: float, unit_class: GroundUnitClass
) -> Optional[Type[VehicleType]]:
) -> Optional[GroundUnitType]:
faction_units = set(self.faction.frontline_units) | set(
self.faction.artillery_units
)
of_class = set(unit_class.unit_list) & faction_units
of_class = {u for u in faction_units if u.unit_class is unit_class}
# faction has no access to needed unit type, take a random unit
if not of_class:
of_class = faction_units
affordable_units = [u for u in of_class if db.PRICES[u] <= budget]
affordable_units = [u for u in of_class if u.price <= budget]
if not affordable_units:
return None
return random.choice(affordable_units)
@@ -180,7 +179,7 @@ class ProcurementAi:
# Can't afford any more units.
break
budget -= db.PRICES[unit]
budget -= unit.price
cp.pending_unit_deliveries.order({unit: 1})
return budget
@@ -361,9 +360,9 @@ class ProcurementAi:
class_cost = 0
total_cost = 0
for unit_type, count in allocations.all.items():
cost = db.PRICES[unit_type] * count
cost = unit_type.price * count
total_cost += cost
if unit_type in unit_class:
if unit_type.unit_class is unit_class:
class_cost += cost
if not total_cost:
return 0

View File

@@ -1,12 +1,10 @@
import itertools
import logging
import typing
from typing import Dict, Type
from typing import Any
from dcs.unittype import VehicleType
from game.db import PRICES
from game.dcs.aircrafttype import AircraftType
from game.dcs.groundunittype import GroundUnitType
from game.dcs.unittype import UnitType
BASE_MAX_STRENGTH = 1
BASE_MIN_STRENGTH = 0
@@ -14,8 +12,8 @@ BASE_MIN_STRENGTH = 0
class Base:
def __init__(self):
self.aircraft: Dict[AircraftType, int] = {}
self.armor: Dict[Type[VehicleType], int] = {}
self.aircraft: dict[AircraftType, int] = {}
self.armor: dict[GroundUnitType, int] = {}
self.strength = 1
@property
@@ -30,13 +28,10 @@ class Base:
def total_armor_value(self) -> int:
total = 0
for unit_type, count in self.armor.items():
try:
total += PRICES[unit_type] * count
except KeyError:
logging.exception(f"No price found for {unit_type.id}")
total += unit_type.price * count
return total
def total_units_of_type(self, unit_type: typing.Any) -> int:
def total_units_of_type(self, unit_type: UnitType) -> int:
return sum(
[
c
@@ -45,30 +40,25 @@ class Base:
]
)
def commission_units(self, units: typing.Dict[typing.Any, int]):
def commission_units(self, units: dict[Any, int]):
for unit_type, unit_count in units.items():
if unit_count <= 0:
continue
target_dict: dict[typing.Any, int]
target_dict: dict[Any, int]
if isinstance(unit_type, AircraftType):
target_dict = self.aircraft
elif issubclass(unit_type, VehicleType):
elif isinstance(unit_type, GroundUnitType):
target_dict = self.armor
else:
logging.error(
f"Unexpected unit type of {unit_type}: "
f"{unit_type.__module__}.{unit_type.__name__}"
)
logging.error(f"Unexpected unit type of {unit_type}")
return
target_dict[unit_type] = target_dict.get(unit_type, 0) + unit_count
def commit_losses(self, units_lost: typing.Dict[typing.Any, int]):
def commit_losses(self, units_lost: dict[Any, int]):
for unit_type, count in units_lost.items():
target_dict: dict[typing.Any, int]
target_dict: dict[Any, int]
if unit_type in self.aircraft:
target_dict = self.aircraft
elif unit_type in self.armor:

View File

@@ -16,7 +16,6 @@ from typing import (
Optional,
Set,
TYPE_CHECKING,
Type,
Union,
Sequence,
Iterable,
@@ -32,7 +31,6 @@ from dcs.ships import (
)
from dcs.terrain.terrain import Airport, ParkingSlot
from dcs.unit import Unit
from dcs.unittype import VehicleType
from game import db
from game.point_with_heading import PointWithHeading
@@ -46,8 +44,8 @@ from .theatergroundobject import (
GenericCarrierGroundObject,
TheaterGroundObject,
)
from ..db import PRICES
from ..dcs.aircrafttype import AircraftType
from ..dcs.groundunittype import GroundUnitType
from ..utils import nautical_miles
from ..weather import Conditions
@@ -161,13 +159,13 @@ class AircraftAllocations:
@dataclass(frozen=True)
class GroundUnitAllocations:
present: dict[Type[VehicleType], int]
ordered: dict[Type[VehicleType], int]
transferring: dict[Type[VehicleType], int]
present: dict[GroundUnitType, int]
ordered: dict[GroundUnitType, int]
transferring: dict[GroundUnitType, int]
@property
def all(self) -> dict[Type[VehicleType], int]:
combined: dict[Type[VehicleType], int] = defaultdict(int)
def all(self) -> dict[GroundUnitType, int]:
combined: dict[GroundUnitType, int] = defaultdict(int)
for unit_type, count in itertools.chain(
self.present.items(), self.ordered.items(), self.transferring.items()
):
@@ -178,11 +176,11 @@ class GroundUnitAllocations:
def total_value(self) -> int:
total: int = 0
for unit_type, count in self.present.items():
total += PRICES[unit_type] * count
total += unit_type.price * count
for unit_type, count in self.ordered.items():
total += PRICES[unit_type] * count
total += unit_type.price * count
for unit_type, count in self.transferring.items():
total += PRICES[unit_type] * count
total += unit_type.price * count
return total
@@ -697,10 +695,10 @@ class ControlPoint(MissionTarget, ABC):
) -> GroundUnitAllocations:
on_order = {}
for unit_bought, count in self.pending_unit_deliveries.units.items():
if type(unit_bought) == type and issubclass(unit_bought, VehicleType):
if isinstance(unit_bought, GroundUnitType):
on_order[unit_bought] = count
transferring: dict[Type[VehicleType], int] = defaultdict(int)
transferring: dict[GroundUnitType, int] = defaultdict(int)
for transfer in transfers:
if transfer.destination == self:
for unit_type, count in transfer.units.items():

View File

@@ -12,15 +12,14 @@ from typing import (
List,
Optional,
TYPE_CHECKING,
Type,
TypeVar,
Sequence,
)
from dcs.mapping import Point
from dcs.unittype import VehicleType
from game.dcs.aircrafttype import AircraftType
from game.dcs.groundunittype import GroundUnitType
from game.procurement import AircraftProcurementRequest
from game.squadrons import Squadron
from game.theater import ControlPoint, MissionTarget
@@ -73,7 +72,7 @@ class TransferOrder:
player: bool = field(init=False)
#: The units being transferred.
units: Dict[Type[VehicleType], int]
units: Dict[GroundUnitType, int]
transport: Optional[Transport] = field(default=None)
@@ -90,7 +89,7 @@ class TransferOrder:
def kill_all(self) -> None:
self.units.clear()
def kill_unit(self, unit_type: Type[VehicleType]) -> None:
def kill_unit(self, unit_type: GroundUnitType) -> None:
if unit_type not in self.units or not self.units[unit_type]:
raise KeyError(f"{self.destination} has no {unit_type} remaining")
self.units[unit_type] -= 1
@@ -99,7 +98,7 @@ class TransferOrder:
def size(self) -> int:
return sum(c for c in self.units.values())
def iter_units(self) -> Iterator[Type[VehicleType]]:
def iter_units(self) -> Iterator[GroundUnitType]:
for unit_type, count in self.units.items():
for _ in range(count):
yield unit_type
@@ -157,7 +156,7 @@ class Airlift(Transport):
self.flight = flight
@property
def units(self) -> Dict[Type[VehicleType], int]:
def units(self) -> Dict[GroundUnitType, int]:
return self.transfer.units
@property
@@ -315,7 +314,7 @@ class MultiGroupTransport(MissionTarget, Transport):
transfer.transport = None
self.transfers.remove(transfer)
def kill_unit(self, unit_type: Type[VehicleType]) -> None:
def kill_unit(self, unit_type: GroundUnitType) -> None:
for transfer in self.transfers:
try:
transfer.kill_unit(unit_type)
@@ -338,13 +337,18 @@ class MultiGroupTransport(MissionTarget, Transport):
return sum(sum(t.units.values()) for t in self.transfers)
@property
def units(self) -> Dict[Type[VehicleType], int]:
units: Dict[Type[VehicleType], int] = defaultdict(int)
def units(self) -> dict[GroundUnitType, int]:
units: Dict[GroundUnitType, int] = defaultdict(int)
for transfer in self.transfers:
for unit_type, count in transfer.units.items():
units[unit_type] += count
return units
def iter_units(self) -> Iterator[GroundUnitType]:
for unit_type, count in self.units.items():
for _ in range(count):
yield unit_type
@property
def player_owned(self) -> bool:
return self.origin.captured

View File

@@ -3,13 +3,11 @@ from __future__ import annotations
import logging
from collections import defaultdict
from dataclasses import dataclass
from typing import Dict, Optional, TYPE_CHECKING, Type, Any
from dcs.unittype import UnitType, VehicleType
from typing import Dict, Optional, TYPE_CHECKING, Any
from game.theater import ControlPoint
from .db import PRICES
from .dcs.aircrafttype import AircraftType
from .dcs.groundunittype import GroundUnitType
from .dcs.unittype import UnitType
from .theater.transitnetwork import (
NoPathError,
TransitNetwork,
@@ -25,24 +23,21 @@ class GroundUnitSource:
control_point: ControlPoint
AircraftOrVehicleType = Any
class PendingUnitDeliveries:
def __init__(self, destination: ControlPoint) -> None:
self.destination = destination
# Maps unit type to order quantity.
self.units: Dict[AircraftOrVehicleType, int] = defaultdict(int)
self.units: Dict[UnitType, int] = defaultdict(int)
def __str__(self) -> str:
return f"Pending delivery to {self.destination}"
def order(self, units: Dict[AircraftOrVehicleType, int]) -> None:
def order(self, units: Dict[UnitType, int]) -> None:
for k, v in units.items():
self.units[k] += v
def sell(self, units: Dict[AircraftOrVehicleType, int]) -> None:
def sell(self, units: Dict[UnitType, int]) -> None:
for k, v in units.items():
self.units[k] -= v
@@ -50,24 +45,20 @@ class PendingUnitDeliveries:
self.refund(game, self.units)
self.units = defaultdict(int)
def refund(self, game: Game, units: Dict[Type[UnitType], int]) -> None:
def refund(self, game: Game, units: Dict[UnitType, int]) -> None:
for unit_type, count in units.items():
try:
price = PRICES[unit_type]
except KeyError:
logging.error(f"Could not refund {unit_type.id}, price unknown")
continue
logging.info(f"Refunding {count} {unit_type} at {self.destination.name}")
game.adjust_budget(
unit_type.price * count, player=self.destination.captured
)
logging.info(f"Refunding {count} {unit_type.id} at {self.destination.name}")
game.adjust_budget(price * count, player=self.destination.captured)
def pending_orders(self, unit_type: AircraftOrVehicleType) -> int:
def pending_orders(self, unit_type: UnitType) -> int:
pending_units = self.units.get(unit_type)
if pending_units is None:
pending_units = 0
return pending_units
def available_next_turn(self, unit_type: AircraftOrVehicleType) -> int:
def available_next_turn(self, unit_type: UnitType) -> int:
current_units = self.destination.base.total_units_of_type(unit_type)
return self.pending_orders(unit_type) + current_units
@@ -81,20 +72,14 @@ class PendingUnitDeliveries:
self.refund_all(game)
return
bought_units: Dict[AircraftOrVehicleType, int] = {}
units_needing_transfer: Dict[Type[VehicleType], int] = {}
sold_units: Dict[AircraftOrVehicleType, int] = {}
bought_units: Dict[UnitType, int] = {}
units_needing_transfer: Dict[GroundUnitType, int] = {}
sold_units: Dict[UnitType, int] = {}
for unit_type, count in self.units.items():
coalition = "Ally" if self.destination.captured else "Enemy"
if isinstance(unit_type, AircraftType):
name = unit_type.name
else:
name = unit_type.id
d: dict[Any, int]
if (
type(unit_type) == type
and issubclass(unit_type, VehicleType)
isinstance(unit_type, GroundUnitType)
and self.destination != ground_unit_source
):
source = ground_unit_source
@@ -106,11 +91,11 @@ class PendingUnitDeliveries:
if count >= 0:
d[unit_type] = count
game.message(
f"{coalition} reinforcements: {name} x {count} at {source}"
f"{coalition} reinforcements: {unit_type} x {count} at {source}"
)
else:
sold_units[unit_type] = -count
game.message(f"{coalition} sold: {name} x {-count} at {source}")
game.message(f"{coalition} sold: {unit_type} x {-count} at {source}")
self.units = defaultdict(int)
self.destination.base.commission_units(bought_units)
@@ -121,7 +106,7 @@ class PendingUnitDeliveries:
self.create_transfer(game, ground_unit_source, units_needing_transfer)
def create_transfer(
self, game: Game, source: ControlPoint, units: Dict[Type[VehicleType], int]
self, game: Game, source: ControlPoint, units: Dict[GroundUnitType, int]
) -> None:
game.transfers.new_transfer(TransferOrder(source, self.destination, units))

View File

@@ -2,13 +2,12 @@
import itertools
import math
from dataclasses import dataclass
from typing import Dict, Optional, Type
from typing import Dict, Optional
from dcs.unit import Unit
from dcs.unitgroup import FlyingGroup, Group, VehicleGroup
from dcs.unittype import VehicleType
from game import db
from game.dcs.groundunittype import GroundUnitType
from game.squadrons import Pilot
from game.theater import Airfield, ControlPoint, TheaterGroundObject
from game.theater.theatergroundobject import BuildingGroundObject, SceneryGroundObject
@@ -24,7 +23,7 @@ class FlyingUnit:
@dataclass(frozen=True)
class FrontLineUnit:
unit_type: Type[VehicleType]
unit_type: GroundUnitType
origin: ControlPoint
@@ -37,13 +36,13 @@ class GroundObjectUnit:
@dataclass(frozen=True)
class ConvoyUnit:
unit_type: Type[VehicleType]
unit_type: GroundUnitType
convoy: Convoy
@dataclass(frozen=True)
class AirliftUnits:
cargo: tuple[Type[VehicleType], ...]
cargo: tuple[GroundUnitType, ...]
transfer: TransferOrder
@@ -85,20 +84,15 @@ class UnitMap:
def airfield(self, name: str) -> Optional[Airfield]:
return self.airfields.get(name, None)
def add_front_line_units(self, group: Group, origin: ControlPoint) -> None:
def add_front_line_units(
self, group: Group, origin: ControlPoint, unit_type: GroundUnitType
) -> None:
for unit in group.units:
# The actual name is a String (the pydcs translatable string), which
# doesn't define __eq__.
name = str(unit.name)
if name in self.front_line_units:
raise RuntimeError(f"Duplicate front line unit: {name}")
unit_type = db.unit_type_from_name(unit.type)
if unit_type is None:
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"
)
self.front_line_units[name] = FrontLineUnit(unit_type, origin)
def front_line_unit(self, name: str) -> Optional[FrontLineUnit]:
@@ -141,19 +135,12 @@ class UnitMap:
return self.ground_object_units.get(name, None)
def add_convoy_units(self, group: Group, convoy: Convoy) -> None:
for unit in group.units:
for unit, unit_type in zip(group.units, convoy.iter_units()):
# The actual name is a String (the pydcs translatable string), which
# doesn't define __eq__.
name = str(unit.name)
if name in self.convoys:
raise RuntimeError(f"Duplicate convoy unit: {name}")
unit_type = db.unit_type_from_name(unit.type)
if unit_type is None:
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"
)
self.convoys[name] = ConvoyUnit(unit_type, convoy)
def convoy_unit(self, name: str) -> Optional[ConvoyUnit]: