diff --git a/game/data/groundunitclass.py b/game/data/groundunitclass.py
index 4b2f6e58..e435267f 100644
--- a/game/data/groundunitclass.py
+++ b/game/data/groundunitclass.py
@@ -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"
diff --git a/game/db.py b/game/db.py
index 1171f3f3..4d5c9f4d 100644
--- a/game/db.py
+++ b/game/db.py
@@ -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:
diff --git a/game/dcs/aircrafttype.py b/game/dcs/aircrafttype.py
index 808142e5..1ed162b0 100644
--- a/game/dcs/aircrafttype.py
+++ b/game/dcs/aircrafttype.py
@@ -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
diff --git a/game/dcs/groundunittype.py b/game/dcs/groundunittype.py
new file mode 100644
index 00000000..ff03702d
--- /dev/null
+++ b/game/dcs/groundunittype.py
@@ -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),
+ )
diff --git a/game/dcs/unittype.py b/game/dcs/unittype.py
new file mode 100644
index 00000000..5b7144e5
--- /dev/null
+++ b/game/dcs/unittype.py
@@ -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
diff --git a/game/debriefing.py b/game/debriefing.py
index e2166a74..59c795db 100644
--- a/game/debriefing.py
+++ b/game/debriefing.py
@@ -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:
diff --git a/game/event/event.py b/game/event/event.py
index 6c842347..1fb0230f 100644
--- a/game/event/event.py
+++ b/game/event/event.py
@@ -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*...
diff --git a/game/factions/faction.py b/game/factions/faction.py
index 8a09807c..0a4b2548 100644
--- a/game/factions/faction.py
+++ b/game/factions/faction.py
@@ -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]]:
diff --git a/game/procurement.py b/game/procurement.py
index 430d2256..2e2c0e79 100644
--- a/game/procurement.py
+++ b/game/procurement.py
@@ -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
diff --git a/game/theater/base.py b/game/theater/base.py
index 2fe84981..4547e3d3 100644
--- a/game/theater/base.py
+++ b/game/theater/base.py
@@ -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:
diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py
index 73dc7d0a..490ee80b 100644
--- a/game/theater/controlpoint.py
+++ b/game/theater/controlpoint.py
@@ -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():
diff --git a/game/transfers.py b/game/transfers.py
index 0788cf11..5543d572 100644
--- a/game/transfers.py
+++ b/game/transfers.py
@@ -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
diff --git a/game/unitdelivery.py b/game/unitdelivery.py
index 06a4f7ed..4de3addf 100644
--- a/game/unitdelivery.py
+++ b/game/unitdelivery.py
@@ -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))
diff --git a/game/unitmap.py b/game/unitmap.py
index c3273cf7..98793991 100644
--- a/game/unitmap.py
+++ b/game/unitmap.py
@@ -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]:
diff --git a/gen/airsupportgen.py b/gen/airsupportgen.py
index 23ff09a3..875a0e58 100644
--- a/gen/airsupportgen.py
+++ b/gen/airsupportgen.py
@@ -110,7 +110,6 @@ class AirSupportConflictGenerator:
):
# TODO: Make loiter altitude a property of the unit type.
alt, airspeed = self._get_tanker_params(tanker_unit_type.dcs_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 = (
@@ -175,7 +174,7 @@ class AirSupportConflictGenerator:
TankerInfo(
str(tanker_group.name),
callsign,
- variant,
+ tanker_unit_type.name,
freq,
tacan,
blue=True,
diff --git a/gen/armor.py b/gen/armor.py
index dd16f94f..6fc49382 100644
--- a/gen/armor.py
+++ b/gen/armor.py
@@ -10,7 +10,6 @@ from dcs.action import AITaskPush
from dcs.condition import GroupLifeLess, Or, TimeAfter, UnitDamaged
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,
@@ -26,19 +25,18 @@ from dcs.task import (
from dcs.triggers import Event, TriggerOnce
from dcs.unit import Vehicle
from dcs.unitgroup import VehicleGroup
-from dcs.unittype import VehicleType
-from game import db
+
+from game.data.groundunitclass import GroundUnitClass
from game.dcs.aircrafttype import AircraftType
+from game.dcs.groundunittype import GroundUnitType
+from game.theater.controlpoint import ControlPoint
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 .callsigns import callsign_for_support_unit
from .conflictgen import Conflict
from .ground_forces.combat_stance import CombatStance
@@ -226,19 +224,18 @@ class GroundConflictGenerator:
else:
cp = self.conflict.red_cp
- if is_player:
- faction = self.game.player_name
- else:
- faction = self.game.enemy_name
+ faction = self.game.faction_for(is_player)
# Disable infantry unit gen if disabled
if not self.game.settings.perf_infantry:
if self.game.settings.manpads:
# 50% of armored units protected by manpad
if random.choice([True, False]):
- manpads = db.find_manpad(faction)
- if len(manpads) > 0:
- u = random.choice(manpads)
+ manpads = list(faction.infantry_with_class(GroundUnitClass.Manpads))
+ if manpads:
+ u = random.choices(
+ manpads, weights=[m.spawn_weight for m in manpads]
+ )[0]
self.mission.vehicle_group(
side,
namegen.next_infantry_name(side, cp.id, u),
@@ -250,30 +247,38 @@ class GroundConflictGenerator:
)
return
- possible_infantry_units = db.find_infantry(
- faction, allow_manpad=self.game.settings.manpads
+ possible_infantry_units = set(
+ faction.infantry_with_class(GroundUnitClass.Infantry)
)
- if len(possible_infantry_units) == 0:
+ if self.game.settings.manpads:
+ possible_infantry_units |= set(
+ faction.infantry_with_class(GroundUnitClass.Manpads)
+ )
+ if not possible_infantry_units:
return
- u = random.choice(possible_infantry_units)
+ infantry_choices = list(possible_infantry_units)
+ units = random.choices(
+ infantry_choices,
+ weights=[u.spawn_weight for u in infantry_choices],
+ k=INFANTRY_GROUP_SIZE,
+ )
self.mission.vehicle_group(
side,
- namegen.next_infantry_name(side, cp.id, u),
- u,
+ namegen.next_infantry_name(side, cp.id, units[0]),
+ units[0].dcs_unit_type,
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)
+ for unit in units[1:]:
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, unit),
+ unit.dcs_unit_type,
position=position,
group_size=1,
heading=forward_heading,
@@ -313,7 +318,7 @@ class GroundConflictGenerator:
)
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)
+ fire_task = FireAtPoint(target, gen_group.size * 10, 100)
fire_task.number = 2 if stance != CombatStance.RETREAT else 1
dcs_group.add_trigger_action(fire_task)
artillery_trigger.add_action(AITaskPush(dcs_group.id, len(dcs_group.tasks)))
@@ -503,7 +508,7 @@ class GroundConflictGenerator:
return
for dcs_group, group in ally_groups:
- if hasattr(group.units[0], "eplrs") and group.units[0].eplrs:
+ if getattr(group.unit_type.dcs_unit_type, "eplrs", False):
dcs_group.points[0].tasks.append(EPLRS(dcs_group.id))
if group.role == CombatGroupRole.ARTILLERY:
@@ -674,7 +679,7 @@ class GroundConflictGenerator:
Search the enemy groups for a potential target suitable to an artillery unit
"""
# TODO: Update to return a list of groups instead of a single point
- rng = group.units[0].threat_range
+ rng = getattr(group.unit_type.dcs_unit_type, "threat_range", 0)
if not enemy_groups:
return None
for _ in range(10):
@@ -691,7 +696,7 @@ class GroundConflictGenerator:
"""
For artilery group, decide the distance from frontline with the range of the unit
"""
- rg = group.units[0].threat_range - 7500
+ rg = getattr(group.unit_type.dcs_unit_type, "threat_range", 0) - 7500
if rg > DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][1]:
rg = random.randint(
DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][0],
@@ -724,7 +729,7 @@ class GroundConflictGenerator:
def _generate_groups(
self,
- groups: List[CombatGroup],
+ groups: list[CombatGroup],
frontline_vector: Tuple[Point, int, int],
is_player: bool,
) -> List[Tuple[VehicleGroup, CombatGroup]]:
@@ -755,10 +760,9 @@ class GroundConflictGenerator:
if final_position is not None:
g = self._generate_group(
self.mission.country(country),
- group.units[0],
- len(group.units),
+ group.unit_type,
+ group.size,
final_position,
- distance_from_frontline,
heading=opposite_heading(spawn_heading),
)
if is_player:
@@ -782,10 +786,9 @@ class GroundConflictGenerator:
def _generate_group(
self,
side: Country,
- unit: VehicleType,
+ unit_type: GroundUnitType,
count: int,
at: Point,
- distance_from_frontline,
move_formation: PointAction = PointAction.OffRoad,
heading=0,
) -> VehicleGroup:
@@ -795,18 +798,17 @@ class GroundConflictGenerator:
else:
cp = self.conflict.red_cp
- logging.info("armorgen: {} for {}".format(unit, side.id))
group = self.mission.vehicle_group(
side,
- namegen.next_unit_name(side, cp.id, unit),
- unit,
+ namegen.next_unit_name(side, cp.id, unit_type),
+ unit_type.dcs_unit_type,
position=at,
group_size=count,
heading=heading,
move_formation=move_formation,
)
- self.unit_map.add_front_line_units(group, cp)
+ self.unit_map.add_front_line_units(group, cp, unit_type)
for c in range(count):
vehicle: Vehicle = group.units[c]
diff --git a/gen/convoygen.py b/gen/convoygen.py
index 9c904009..303c286f 100644
--- a/gen/convoygen.py
+++ b/gen/convoygen.py
@@ -1,15 +1,15 @@
from __future__ import annotations
import itertools
-from typing import Dict, TYPE_CHECKING, Type
+from typing import TYPE_CHECKING
from dcs import Mission
from dcs.mapping import Point
from dcs.point import PointAction
from dcs.unit import Vehicle
from dcs.unitgroup import VehicleGroup
-from dcs.unittype import VehicleType
+from game.dcs.groundunittype import GroundUnitType
from game.transfers import Convoy
from game.unitmap import UnitMap
from game.utils import kph
@@ -50,7 +50,7 @@ class ConvoyGenerator:
self,
name: str,
position: Point,
- units: Dict[Type[VehicleType], int],
+ units: dict[GroundUnitType, int],
for_player: bool,
) -> VehicleGroup:
country = self.mission.country(
@@ -63,7 +63,7 @@ class ConvoyGenerator:
group = self.mission.vehicle_group(
country,
name,
- main_unit_type,
+ main_unit_type.dcs_unit_type,
position=position,
group_size=main_unit_count,
move_formation=PointAction.OnRoad,
@@ -76,7 +76,7 @@ class ConvoyGenerator:
for unit_type, count in unit_types[1:]:
for i in range(count):
v = self.mission.vehicle(
- f"{name} Unit #{next(unit_name_counter)}", unit_type
+ f"{name} Unit #{next(unit_name_counter)}", unit_type.dcs_unit_type
)
v.position.x = position.x
v.position.y = next(y)
diff --git a/gen/defenses/armor_group_generator.py b/gen/defenses/armor_group_generator.py
index fe429e72..fc549ca9 100644
--- a/gen/defenses/armor_group_generator.py
+++ b/gen/defenses/armor_group_generator.py
@@ -1,8 +1,11 @@
import random
-from dcs.vehicles import Armor
+from dcs.unitgroup import VehicleGroup
-from game import db
+from game import db, Game
+from game.data.groundunitclass import GroundUnitClass
+from game.dcs.groundunittype import GroundUnitType
+from game.theater.theatergroundobject import VehicleGroupGroundObject
from gen.defenses.armored_group_generator import (
ArmoredGroupGenerator,
FixedSizeArmorGroupGenerator,
@@ -14,8 +17,14 @@ def generate_armor_group(faction: str, game, ground_object):
This generate a group of ground units
:return: Generated group
"""
+ armor_types = (
+ GroundUnitClass.Apc,
+ GroundUnitClass.Atgm,
+ GroundUnitClass.Ifv,
+ GroundUnitClass.Tank,
+ )
possible_unit = [
- u for u in db.FACTIONS[faction].frontline_units if u in Armor.__dict__.values()
+ u for u in db.FACTIONS[faction].frontline_units if u.unit_class in armor_types
]
if len(possible_unit) > 0:
unit_type = random.choice(possible_unit)
@@ -23,7 +32,9 @@ def generate_armor_group(faction: str, game, ground_object):
return None
-def generate_armor_group_of_type(game, ground_object, unit_type):
+def generate_armor_group_of_type(
+ game: Game, ground_object: VehicleGroupGroundObject, unit_type: GroundUnitType
+) -> VehicleGroup:
"""
This generate a group of ground units of given type
:return: Generated group
@@ -33,7 +44,12 @@ def generate_armor_group_of_type(game, ground_object, unit_type):
return generator.get_generated_group()
-def generate_armor_group_of_type_and_size(game, ground_object, unit_type, size: int):
+def generate_armor_group_of_type_and_size(
+ game: Game,
+ ground_object: VehicleGroupGroundObject,
+ unit_type: GroundUnitType,
+ size: int,
+) -> VehicleGroup:
"""
This generate a group of ground units of given type and size
:return: Generated group
diff --git a/gen/defenses/armored_group_generator.py b/gen/defenses/armored_group_generator.py
index e25f5c74..f68b520b 100644
--- a/gen/defenses/armored_group_generator.py
+++ b/gen/defenses/armored_group_generator.py
@@ -1,15 +1,22 @@
import random
+from game import Game
+from game.dcs.groundunittype import GroundUnitType
+from game.theater.theatergroundobject import VehicleGroupGroundObject
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)
+ def __init__(
+ self,
+ game: Game,
+ ground_object: VehicleGroupGroundObject,
+ unit_type: GroundUnitType,
+ ) -> None:
+ super().__init__(game, ground_object)
self.unit_type = unit_type
- def generate(self):
-
+ def generate(self) -> None:
grid_x = random.randint(2, 3)
grid_y = random.randint(1, 2)
@@ -20,7 +27,7 @@ class ArmoredGroupGenerator(GroupGenerator):
for j in range(grid_y):
index = index + 1
self.add_unit(
- self.unit_type,
+ self.unit_type.dcs_unit_type,
"Armor#" + str(index),
self.position.x + spacing * i,
self.position.y + spacing * j,
@@ -29,8 +36,14 @@ class ArmoredGroupGenerator(GroupGenerator):
class FixedSizeArmorGroupGenerator(GroupGenerator):
- def __init__(self, game, ground_object, unit_type, size):
- super(FixedSizeArmorGroupGenerator, self).__init__(game, ground_object)
+ def __init__(
+ self,
+ game: Game,
+ ground_object: VehicleGroupGroundObject,
+ unit_type: GroundUnitType,
+ size: int,
+ ) -> None:
+ super().__init__(game, ground_object)
self.unit_type = unit_type
self.size = size
@@ -41,7 +54,7 @@ class FixedSizeArmorGroupGenerator(GroupGenerator):
for i in range(self.size):
index = index + 1
self.add_unit(
- self.unit_type,
+ self.unit_type.dcs_unit_type,
"Armor#" + str(index),
self.position.x + spacing * i,
self.position.y,
diff --git a/gen/ground_forces/ai_ground_planner.py b/gen/ground_forces/ai_ground_planner.py
index 75fb1bea..045c4b39 100644
--- a/gen/ground_forces/ai_ground_planner.py
+++ b/gen/ground_forces/ai_ground_planner.py
@@ -3,11 +3,9 @@ import random
from enum import Enum
from typing import Dict, List
-from dcs.unittype import VehicleType
-
-from game.theater import ControlPoint
-
from game.data.groundunitclass import GroundUnitClass
+from game.dcs.groundunittype import GroundUnitType
+from game.theater import ControlPoint
from gen.ground_forces.combat_stance import CombatStance
MAX_COMBAT_GROUP_PER_CP = 10
@@ -48,17 +46,19 @@ GROUP_SIZES_BY_COMBAT_STANCE = {
class CombatGroup:
- def __init__(self, role: CombatGroupRole):
- self.units: List[VehicleType] = []
+ def __init__(
+ self, role: CombatGroupRole, unit_type: GroundUnitType, size: int
+ ) -> None:
+ self.unit_type = unit_type
+ self.size = size
self.role = role
self.assigned_enemy_cp = None
self.start_position = None
def __str__(self):
- s = ""
- s += "ROLE : " + str(self.role) + "\n"
- if len(self.units) > 0:
- s += "UNITS " + self.units[0].name + " * " + str(len(self.units))
+ s = f"ROLE : {self.role}\n"
+ if self.size:
+ s += f"UNITS {self.unit_type} * {self.size}"
return s
@@ -97,28 +97,29 @@ class GroundPlanner:
# Create combat groups and assign them randomly to each enemy CP
for unit_type in self.cp.base.armor:
- if unit_type in GroundUnitClass.Tank:
+ unit_class = unit_type.unit_class
+ if unit_class is GroundUnitClass.Tank:
collection = self.tank_groups
role = CombatGroupRole.TANK
- elif unit_type in GroundUnitClass.Apc:
+ elif unit_class is GroundUnitClass.Apc:
collection = self.apc_group
role = CombatGroupRole.APC
- elif unit_type in GroundUnitClass.Artillery:
+ elif unit_class is GroundUnitClass.Artillery:
collection = self.art_group
role = CombatGroupRole.ARTILLERY
- elif unit_type in GroundUnitClass.Ifv:
+ elif unit_class is GroundUnitClass.Ifv:
collection = self.ifv_group
role = CombatGroupRole.IFV
- elif unit_type in GroundUnitClass.Logistics:
+ elif unit_class is GroundUnitClass.Logistics:
collection = self.logi_groups
role = CombatGroupRole.LOGI
- elif unit_type in GroundUnitClass.Atgm:
+ elif unit_class is GroundUnitClass.Atgm:
collection = self.atgm_group
role = CombatGroupRole.ATGM
- elif unit_type in GroundUnitClass.Shorads:
+ elif unit_class is GroundUnitClass.Shorads:
collection = self.shorad_groups
role = CombatGroupRole.SHORAD
- elif unit_type in GroundUnitClass.Recon:
+ elif unit_class is GroundUnitClass.Recon:
collection = self.recon_groups
role = CombatGroupRole.RECON
else:
@@ -137,17 +138,17 @@ class GroundPlanner:
while available > 0:
if role == CombatGroupRole.SHORAD:
- n = 1
+ count = 1
else:
- n = random.choice(group_size_choice)
- if n > available:
+ count = random.choice(group_size_choice)
+ if count > available:
if available >= 2:
- n = 2
+ count = 2
else:
- n = 1
- available -= n
+ count = 1
+ available -= count
- group = CombatGroup(role)
+ group = CombatGroup(role, unit_type, count)
if len(self.connected_enemy_cp) > 0:
enemy_cp = random.choice(self.connected_enemy_cp).id
self.units_per_cp[enemy_cp].append(group)
@@ -155,9 +156,6 @@ class GroundPlanner:
else:
self.reserve.append(group)
group.assigned_enemy_cp = "__reserve__"
-
- for i in range(n):
- group.units.append(unit_type)
collection.append(group)
if remaining_available_frontline_units == 0:
diff --git a/gen/naming.py b/gen/naming.py
index f4964cd2..df56ab64 100644
--- a/gen/naming.py
+++ b/gen/naming.py
@@ -3,11 +3,9 @@ import time
from typing import List
from dcs.country import Country
-from dcs.unittype import UnitType
-from game import db
from game.dcs.aircrafttype import AircraftType
-
+from game.dcs.unittype import UnitType
from gen.flights.flight import Flight
ALPHA_MILITARY = [
@@ -298,7 +296,7 @@ class NameGenerator:
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)
+ country.id, cls.number, parent_base_id, unit_type.name
)
@classmethod
@@ -310,7 +308,7 @@ class NameGenerator:
country.id,
cls.infantry_number,
parent_base_id,
- db.unit_type_name(unit_type),
+ unit_type.name,
)
@classmethod
diff --git a/qt_ui/widgets/map/mapmodel.py b/qt_ui/widgets/map/mapmodel.py
index 79aa0d70..0dadcf15 100644
--- a/qt_ui/widgets/map/mapmodel.py
+++ b/qt_ui/widgets/map/mapmodel.py
@@ -10,7 +10,8 @@ from dcs.unit import Unit
from dcs.vehicles import vehicle_map
from shapely.geometry import LineString, Point as ShapelyPoint, Polygon, MultiPolygon
-from game import Game, db
+from game import Game
+from game.dcs.groundunittype import GroundUnitType
from game.navmesh import NavMesh
from game.profiling import logged_duration
from game.theater import (
@@ -191,12 +192,6 @@ class GroundObjectJs(QObject):
self.game = game
self.theater = game.theater
self.buildings = self.theater.find_ground_objects_by_obj_name(self.tgo.obj_name)
-
- if self.tgo.is_friendly(to_player=True):
- self.country = game.player_country
- else:
- self.country = game.enemy_country
-
self.dialog: Optional[QGroundObjectMenu] = None
@Slot()
@@ -223,14 +218,15 @@ class GroundObjectJs(QObject):
def category(self) -> str:
return self.tgo.category
- def make_unit_name(self, unit: Unit, dead: bool) -> str:
+ @staticmethod
+ def make_unit_name(unit: Unit, dead: bool) -> str:
dead_label = " [DEAD]" if dead else ""
unit_display_name = unit.type
- unit_type = vehicle_map.get(unit.type)
- if unit_type is not None:
- unit_display_name = db.unit_get_expanded_info(
- self.country, unit_type, "name"
- )
+ dcs_unit_type = vehicle_map.get(unit.type)
+ if dcs_unit_type is not None:
+ # TODO: Make the TGO contain GroundUnitType instead of the pydcs Group.
+ # This is a hack because we can't know which variant was used.
+ unit_display_name = next(GroundUnitType.for_dcs_type(dcs_unit_type)).name
return f"Unit #{unit.id} - {unit_display_name}{dead_label}"
@Property(list, notify=unitsChanged)
diff --git a/qt_ui/windows/QDebriefingWindow.py b/qt_ui/windows/QDebriefingWindow.py
index f1635c3d..20e01cc3 100644
--- a/qt_ui/windows/QDebriefingWindow.py
+++ b/qt_ui/windows/QDebriefingWindow.py
@@ -11,7 +11,6 @@ from PySide2.QtWidgets import (
QVBoxLayout,
)
-from game import db
from game.debriefing import Debriefing
@@ -24,25 +23,19 @@ class LossGrid(QGridLayout):
self.add_loss_rows(debriefing.air_losses.by_type(player), lambda u: u.name)
self.add_loss_rows(
- debriefing.front_line_losses_by_type(player),
- lambda u: db.unit_type_name(u),
+ debriefing.front_line_losses_by_type(player), lambda u: str(u)
)
self.add_loss_rows(
- debriefing.convoy_losses_by_type(player),
- lambda u: f"{db.unit_type_name(u)} from convoy",
+ debriefing.convoy_losses_by_type(player), lambda u: f"{u} from convoy"
)
self.add_loss_rows(
debriefing.cargo_ship_losses_by_type(player),
- lambda u: f"{db.unit_type_name(u)} from cargo ship",
+ lambda u: f"{u} from cargo ship",
)
self.add_loss_rows(
- debriefing.airlift_losses_by_type(player),
- lambda u: f"{db.unit_type_name(u)} from airlift",
- )
- self.add_loss_rows(
- debriefing.building_losses_by_type(player),
- lambda u: u,
+ debriefing.airlift_losses_by_type(player), lambda u: f"{u} from airlift"
)
+ self.add_loss_rows(debriefing.building_losses_by_type(player), lambda u: u)
# TODO: Display dead ground object units and runways.
diff --git a/qt_ui/windows/QUnitInfoWindow.py b/qt_ui/windows/QUnitInfoWindow.py
index a5c67bb2..a87ce597 100644
--- a/qt_ui/windows/QUnitInfoWindow.py
+++ b/qt_ui/windows/QUnitInfoWindow.py
@@ -1,9 +1,5 @@
from __future__ import annotations
-from dataclasses import dataclass
-from typing import Type, Union
-
-import dcs
from PySide2.QtCore import Qt
from PySide2.QtGui import QIcon
from PySide2.QtWidgets import (
@@ -13,79 +9,23 @@ from PySide2.QtWidgets import (
QTextBrowser,
QFrame,
)
-from dcs.unittype import VehicleType
import gen.flights.ai_flight_planner_db
-from game import db
from game.dcs.aircrafttype import AircraftType
+from game.dcs.groundunittype import GroundUnitType
+from game.dcs.unittype import UnitType
from game.game import Game
from gen.flights.flight import FlightType
from qt_ui.uiconstants import AIRCRAFT_BANNERS, VEHICLE_BANNERS
-@dataclass(frozen=True)
-class UnitInfo:
- name: str
- description: str
- introduction_year: str
- origin: str
- manufacturer: str
- role: str
-
- @classmethod
- def from_unit_type(
- cls, country: str, unit_type: Union[AircraftType, Type[VehicleType]]
- ) -> UnitInfo:
- if isinstance(unit_type, AircraftType):
- return cls.from_aircraft(unit_type)
- else:
- return cls.from_vehicle_type(country, unit_type)
-
- @classmethod
- def from_aircraft(cls, aircraft: AircraftType) -> UnitInfo:
- return UnitInfo(
- aircraft.name,
- aircraft.description,
- aircraft.year_introduced,
- aircraft.country_of_origin,
- aircraft.manufacturer,
- aircraft.role,
- )
-
- @classmethod
- def from_vehicle_type(cls, country: str, unit_type: Type[VehicleType]) -> UnitInfo:
- name = db.unit_get_expanded_info(country, unit_type, "name")
- manufacturer = db.unit_get_expanded_info(country, unit_type, "manufacturer")
- origin = db.unit_get_expanded_info(country, unit_type, "country-of-origin")
- role = db.unit_get_expanded_info(country, unit_type, "role")
- introduction = db.unit_get_expanded_info(
- country, unit_type, "year-of-variant-introduction"
- )
- description = db.unit_get_expanded_info(country, unit_type, "text")
- return UnitInfo(
- name,
- description,
- introduction,
- origin,
- manufacturer,
- role,
- )
-
-
class QUnitInfoWindow(QDialog):
- def __init__(
- self, game: Game, unit_type: Union[AircraftType, Type[VehicleType]]
- ) -> None:
+ def __init__(self, game: Game, unit_type: UnitType) -> None:
super().__init__()
self.setModal(True)
self.game = game
self.unit_type = unit_type
- if isinstance(unit_type, AircraftType):
- self.name = unit_type.name
- else:
- self.name = db.unit_get_expanded_info(
- self.game.player_country, self.unit_type, "name"
- )
+ self.name = unit_type.name
self.setWindowTitle(f"Unit Info: {self.name}")
self.setWindowIcon(QIcon("./resources/icon.png"))
self.setMinimumHeight(570)
@@ -101,8 +41,8 @@ class QUnitInfoWindow(QDialog):
if isinstance(self.unit_type, AircraftType):
pixmap = AIRCRAFT_BANNERS.get(self.unit_type.dcs_id)
- elif dcs.vehicles.vehicle_map.get(self.unit_type.id) is not None:
- pixmap = VEHICLE_BANNERS.get(self.unit_type.id)
+ elif isinstance(self.unit_type, GroundUnitType):
+ pixmap = VEHICLE_BANNERS.get(self.unit_type.dcs_id)
if pixmap is None:
pixmap = AIRCRAFT_BANNERS.get("Missing")
header.setPixmap(pixmap.scaled(header.width(), header.height()))
@@ -115,20 +55,21 @@ class QUnitInfoWindow(QDialog):
self.details_grid_layout = QGridLayout()
self.details_grid_layout.setMargin(0)
- unit_info = UnitInfo.from_unit_type(self.game.player_country, self.unit_type)
self.name_box = QLabel(
- f"Name: {unit_info.manufacturer} {unit_info.name}"
+ f"Name: {unit_type.manufacturer} {unit_type.name}"
)
self.name_box.setProperty("style", "info-element")
- self.country_box = QLabel(f"Country of Origin: {unit_info.origin}")
+ self.country_box = QLabel(
+ f"Country of Origin: {unit_type.country_of_origin}"
+ )
self.country_box.setProperty("style", "info-element")
- self.role_box = QLabel(f"Role: {unit_info.role}")
+ self.role_box = QLabel(f"Role: {unit_type.role}")
self.role_box.setProperty("style", "info-element")
self.year_box = QLabel(
- f"Variant Introduction: {unit_info.introduction_year}"
+ f"Variant Introduction: {unit_type.year_introduced}"
)
self.year_box.setProperty("style", "info-element")
@@ -152,7 +93,7 @@ class QUnitInfoWindow(QDialog):
# Finally, add the description box.
self.details_text = QTextBrowser()
self.details_text.setProperty("style", "info-desc")
- self.details_text.setText(unit_info.description)
+ self.details_text.setText(unit_type.description)
self.gridLayout.addWidget(self.details_text, 3, 0)
self.layout.addLayout(self.gridLayout, 1, 0)
diff --git a/qt_ui/windows/basemenu/DepartingConvoysMenu.py b/qt_ui/windows/basemenu/DepartingConvoysMenu.py
index 8c1e67bd..1dd4b46c 100644
--- a/qt_ui/windows/basemenu/DepartingConvoysMenu.py
+++ b/qt_ui/windows/basemenu/DepartingConvoysMenu.py
@@ -10,7 +10,6 @@ from PySide2.QtWidgets import (
QWidget,
)
-from game import db
from game.theater import ControlPoint
from game.transfers import MultiGroupTransport
from qt_ui.dialogs import Dialog
@@ -19,7 +18,7 @@ from qt_ui.uiconstants import VEHICLES_ICONS
class DepartingConvoyInfo(QGroupBox):
- def __init__(self, convoy: MultiGroupTransport, game_model: GameModel) -> None:
+ def __init__(self, convoy: MultiGroupTransport) -> None:
super().__init__(f"{convoy.name} to {convoy.destination}")
self.convoy = convoy
@@ -31,17 +30,14 @@ class DepartingConvoyInfo(QGroupBox):
for idx, (unit_type, count) in enumerate(convoy.units.items()):
icon = QLabel()
- if unit_type.id in VEHICLES_ICONS.keys():
- icon.setPixmap(VEHICLES_ICONS[unit_type.id])
+ if unit_type.dcs_id in VEHICLES_ICONS.keys():
+ icon.setPixmap(VEHICLES_ICONS[unit_type.dcs_id])
else:
icon.setText("" + unit_type.id[:8] + "")
icon.setProperty("style", "icon-armor")
unit_layout.addWidget(icon, idx, 0)
- unit_display_name = db.unit_get_expanded_info(
- game_model.game.enemy_country, unit_type, "name"
- )
unit_layout.addWidget(
- QLabel(f"{count} x {unit_display_name}"),
+ QLabel(f"{count} x {unit_type.name}"),
idx,
1,
)
@@ -68,7 +64,6 @@ class DepartingConvoysList(QFrame):
def __init__(self, cp: ControlPoint, game_model: GameModel):
super().__init__()
self.cp = cp
- self.game_model = game_model
self.setMinimumWidth(500)
layout = QVBoxLayout()
@@ -79,11 +74,11 @@ class DepartingConvoysList(QFrame):
scroll_content.setLayout(task_box_layout)
for convoy in game_model.game.transfers.convoys.departing_from(cp):
- group_info = DepartingConvoyInfo(convoy, game_model)
+ group_info = DepartingConvoyInfo(convoy)
task_box_layout.addWidget(group_info)
for cargo_ship in game_model.game.transfers.cargo_ships.departing_from(cp):
- group_info = DepartingConvoyInfo(cargo_ship, game_model)
+ group_info = DepartingConvoyInfo(cargo_ship)
task_box_layout.addWidget(group_info)
scroll_content.setLayout(task_box_layout)
diff --git a/qt_ui/windows/basemenu/NewUnitTransferDialog.py b/qt_ui/windows/basemenu/NewUnitTransferDialog.py
index 57288d5f..9689c9fd 100644
--- a/qt_ui/windows/basemenu/NewUnitTransferDialog.py
+++ b/qt_ui/windows/basemenu/NewUnitTransferDialog.py
@@ -20,10 +20,10 @@ from PySide2.QtWidgets import (
QVBoxLayout,
QWidget,
)
-from dcs.task import PinpointStrike
from dcs.unittype import UnitType
-from game import Game, db
+from game import Game
+from game.dcs.groundunittype import GroundUnitType
from game.theater import ControlPoint
from game.transfers import TransferOrder
from qt_ui.models import GameModel
@@ -63,12 +63,7 @@ class UnitTransferList(QFrame):
task_box_layout = QGridLayout()
scroll_content.setLayout(task_box_layout)
- units_column = sorted(
- cp.base.armor,
- key=lambda u: db.unit_get_expanded_info(
- self.game_model.game.player_country, u, "name"
- ),
- )
+ units_column = sorted(cp.base.armor, key=lambda u: u.name)
count = 0
for count, unit_type in enumerate(units_column):
@@ -169,9 +164,7 @@ class ScrollingUnitTransferGrid(QFrame):
unit_types = set(self.game_model.game.faction_for(player=True).ground_units)
sorted_units = sorted(
{u for u in unit_types if self.cp.base.total_units_of_type(u)},
- key=lambda u: db.unit_get_expanded_info(
- self.game_model.game.player_country, u, "name"
- ),
+ key=lambda u: u.name,
)
for row, unit_type in enumerate(sorted_units):
self.add_unit_row(unit_type, task_box_layout, row)
@@ -190,7 +183,7 @@ class ScrollingUnitTransferGrid(QFrame):
def add_unit_row(
self,
- unit_type: Type[UnitType],
+ unit_type: GroundUnitType,
layout: QGridLayout,
row: int,
) -> None:
@@ -203,13 +196,7 @@ class ScrollingUnitTransferGrid(QFrame):
origin_inventory = self.cp.base.total_units_of_type(unit_type)
- unit_name = QLabel(
- ""
- + db.unit_get_expanded_info(
- self.game_model.game.player_country, unit_type, "name"
- )
- + ""
- )
+ unit_name = QLabel(f"{unit_type.name}")
unit_name.setSizePolicy(
QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
)
diff --git a/qt_ui/windows/basemenu/QRecruitBehaviour.py b/qt_ui/windows/basemenu/QRecruitBehaviour.py
index 3973015a..e471e4f5 100644
--- a/qt_ui/windows/basemenu/QRecruitBehaviour.py
+++ b/qt_ui/windows/basemenu/QRecruitBehaviour.py
@@ -1,5 +1,4 @@
import logging
-from typing import Type, Union
from PySide2.QtWidgets import (
QGroupBox,
@@ -10,9 +9,8 @@ from PySide2.QtWidgets import (
QSizePolicy,
QSpacerItem,
)
-from dcs.unittype import VehicleType
-from game.dcs.aircrafttype import AircraftType
+from game.dcs.unittype import UnitType
from game.theater import ControlPoint
from game.unitdelivery import PendingUnitDeliveries
from qt_ui.models import GameModel
@@ -38,7 +36,7 @@ class QRecruitBehaviour:
return self.cp.pending_unit_deliveries
@property
- def budget(self) -> int:
+ def budget(self) -> float:
return self.game_model.game.budget
@budget.setter
@@ -47,7 +45,7 @@ class QRecruitBehaviour:
def add_purchase_row(
self,
- unit_type: Union[AircraftType, Type[VehicleType]],
+ unit_type: UnitType,
layout: QLayout,
row: int,
) -> int:
@@ -61,7 +59,7 @@ class QRecruitBehaviour:
existing_units = self.cp.base.total_units_of_type(unit_type)
scheduled_units = self.pending_deliveries.units.get(unit_type, 0)
- unitName = QLabel(f"{self.name_of(unit_type)}")
+ unitName = QLabel(f"{unit_type.name}")
unitName.setSizePolicy(
QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
)
@@ -75,7 +73,7 @@ class QRecruitBehaviour:
self.existing_units_labels[unit_type] = existing_units
self.bought_amount_labels[unit_type] = amount_bought
- price = QLabel(f"$ {self.price_of(unit_type)} M")
+ price = QLabel(f"$ {unit_type.price} M")
price.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
buysell = QGroupBox()
@@ -149,8 +147,7 @@ class QRecruitBehaviour:
return row + 1
- def _update_count_label(self, unit_type: Union[AircraftType, Type[VehicleType]]):
-
+ def _update_count_label(self, unit_type: UnitType) -> None:
self.bought_amount_labels[unit_type].setText(
"{}".format(
unit_type in self.pending_deliveries.units
@@ -166,34 +163,32 @@ class QRecruitBehaviour:
def update_available_budget(self) -> None:
GameUpdateSignal.get_instance().updateBudget(self.game_model.game)
- def buy(self, unit_type: Union[AircraftType, Type[VehicleType]]):
+ def buy(self, unit_type: UnitType) -> None:
if not self.enable_purchase(unit_type):
- logging.error(f"Purchase of {unit_type.id} not allowed at {self.cp.name}")
+ logging.error(f"Purchase of {unit_type} not allowed at {self.cp.name}")
return
self.pending_deliveries.order({unit_type: 1})
- self.budget -= self.price_of(unit_type)
+ self.budget -= unit_type.price
self._update_count_label(unit_type)
self.update_available_budget()
- def sell(self, unit_type):
+ def sell(self, unit_type: UnitType) -> None:
if self.pending_deliveries.available_next_turn(unit_type) > 0:
- self.budget += self.price_of(unit_type)
+ self.budget += unit_type.price
self.pending_deliveries.sell({unit_type: 1})
if self.pending_deliveries.units[unit_type] == 0:
del self.pending_deliveries.units[unit_type]
self._update_count_label(unit_type)
self.update_available_budget()
- def enable_purchase(
- self, unit_type: Union[AircraftType, Type[VehicleType]]
- ) -> bool:
- return self.budget >= self.price_of(unit_type)
+ def enable_purchase(self, unit_type: UnitType) -> bool:
+ return self.budget >= unit_type.price
- def enable_sale(self, unit_type: Union[AircraftType, Type[VehicleType]]) -> bool:
+ def enable_sale(self, unit_type: UnitType) -> bool:
return True
- def info(self, unit_type):
+ def info(self, unit_type: UnitType) -> None:
self.info_window = QUnitInfoWindow(self.game_model.game, unit_type)
self.info_window.show()
@@ -202,9 +197,3 @@ class QRecruitBehaviour:
Set the maximum number of units that can be bought
"""
self.maximum_units = maximum_units
-
- def name_of(self, unit_type: Union[AircraftType, Type[VehicleType]]) -> str:
- raise NotImplementedError
-
- def price_of(self, unit_type: Union[AircraftType, Type[VehicleType]]) -> int:
- raise NotImplementedError
diff --git a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py
index 4e12a131..7d6f7e80 100644
--- a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py
+++ b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py
@@ -92,12 +92,6 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour):
return False
return True
- def name_of(self, unit_type: AircraftType) -> str:
- return unit_type.name
-
- def price_of(self, unit_type: AircraftType) -> int:
- return unit_type.price
-
def buy(self, unit_type: AircraftType) -> None:
if self.maximum_units > 0:
if self.cp.unclaimed_parking(self.game_model.game) <= 0:
diff --git a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py
index 63c80b6b..544d1623 100644
--- a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py
+++ b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py
@@ -1,5 +1,3 @@
-from typing import Type
-
from PySide2.QtCore import Qt
from PySide2.QtWidgets import (
QFrame,
@@ -8,10 +6,8 @@ from PySide2.QtWidgets import (
QVBoxLayout,
QWidget,
)
-from dcs.unittype import UnitType, VehicleType
-from game import db
-from game.db import PRICES
+from game.dcs.groundunittype import GroundUnitType
from game.theater import ControlPoint
from qt_ui.models import GameModel
from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour
@@ -39,11 +35,7 @@ class QArmorRecruitmentMenu(QFrame, QRecruitBehaviour):
unit_types = list(
set(self.game_model.game.faction_for(player=True).ground_units)
)
- unit_types.sort(
- key=lambda u: db.unit_get_expanded_info(
- self.game_model.game.player_country, u, "name"
- )
- )
+ unit_types.sort(key=lambda u: u.name)
for unit_type in unit_types:
row = self.add_purchase_row(unit_type, task_box_layout, row)
stretch = QVBoxLayout()
@@ -59,18 +51,10 @@ class QArmorRecruitmentMenu(QFrame, QRecruitBehaviour):
main_layout.addWidget(scroll)
self.setLayout(main_layout)
- def enable_purchase(self, unit_type: Type[UnitType]) -> bool:
+ def enable_purchase(self, unit_type: GroundUnitType) -> bool:
if not super().enable_purchase(unit_type):
return False
return self.cp.has_ground_unit_source(self.game_model.game)
- def enable_sale(self, unit_type: Type[UnitType]) -> bool:
+ def enable_sale(self, unit_type: GroundUnitType) -> bool:
return self.pending_deliveries.pending_orders(unit_type) > 0
-
- def name_of(self, unit_type: Type[VehicleType]) -> str:
- return db.unit_get_expanded_info(
- self.game_model.game.player_country, unit_type, "name"
- )
-
- def price_of(self, unit_type: Type[VehicleType]) -> int:
- return PRICES[unit_type]
diff --git a/qt_ui/windows/basemenu/intel/QIntelInfo.py b/qt_ui/windows/basemenu/intel/QIntelInfo.py
index 2f30d169..c8bf03e8 100644
--- a/qt_ui/windows/basemenu/intel/QIntelInfo.py
+++ b/qt_ui/windows/basemenu/intel/QIntelInfo.py
@@ -11,7 +11,7 @@ from PySide2.QtWidgets import (
QWidget,
)
-from game import Game, db
+from game import Game
from game.theater import ControlPoint
@@ -38,10 +38,7 @@ class QIntelInfo(QFrame):
front_line_units = defaultdict(int)
for unit_type, count in self.cp.base.armor.items():
if count:
- name = db.unit_get_expanded_info(
- self.game.enemy_country, unit_type, "name"
- )
- front_line_units[name] += count
+ front_line_units[unit_type.name] += count
units_by_task["Front line units"] = front_line_units
for task, unit_types in units_by_task.items():
diff --git a/qt_ui/windows/groundobject/QGroundObjectMenu.py b/qt_ui/windows/groundobject/QGroundObjectMenu.py
index d1fed719..628a79da 100644
--- a/qt_ui/windows/groundobject/QGroundObjectMenu.py
+++ b/qt_ui/windows/groundobject/QGroundObjectMenu.py
@@ -20,7 +20,8 @@ from dcs import vehicles
from game import Game, db
from game.data.building_data import FORTIFICATION_BUILDINGS
-from game.db import PRICES, REWARDS, unit_type_of
+from game.db import REWARDS
+from game.dcs.groundunittype import GroundUnitType
from game.theater import ControlPoint, TheaterGroundObject
from game.theater.theatergroundobject import (
VehicleGroupGroundObject,
@@ -108,17 +109,18 @@ class QGroundObjectMenu(QDialog):
for g in self.ground_object.groups:
if not hasattr(g, "units_losts"):
g.units_losts = []
- for u in g.units:
- unit_display_name = u.type
- unit_type = vehicles.vehicle_map.get(u.type)
- if unit_type is not None:
- unit_display_name = db.unit_get_expanded_info(
- self.game.enemy_country, unit_type, "name"
- )
+ for unit in g.units:
+ unit_display_name = unit.type
+ dcs_unit_type = vehicles.vehicle_map.get(unit.type)
+ if dcs_unit_type is not None:
+ # Hack: Don't know which variant is used.
+ unit_display_name = next(
+ GroundUnitType.for_dcs_type(dcs_unit_type)
+ ).name
self.intelLayout.addWidget(
QLabel(
"Unit #"
- + str(u.id)
+ + str(unit.id)
+ " - "
+ str(unit_display_name)
+ ""
@@ -128,26 +130,30 @@ class QGroundObjectMenu(QDialog):
)
i = i + 1
- for u in g.units_losts:
+ for unit in g.units_losts:
+ dcs_unit_type = vehicles.vehicle_map.get(unit.type)
+ if dcs_unit_type is None:
+ continue
- utype = unit_type_of(u)
- if utype in PRICES:
- price = PRICES[utype]
- else:
- price = 6
+ # Hack: Don't know which variant is used.
+ unit_type = next(GroundUnitType.for_dcs_type(dcs_unit_type))
self.intelLayout.addWidget(
QLabel(
- "Unit #" + str(u.id) + " - " + str(u.type) + " [DEAD]"
+ "Unit #"
+ + str(unit.id)
+ + " - "
+ + str(unit_type)
+ + " [DEAD]"
),
i,
0,
)
if self.cp.captured:
- repair = QPushButton("Repair [" + str(price) + "M]")
+ repair = QPushButton(f"Repair [{unit_type.price}M]")
repair.setProperty("style", "btn-success")
repair.clicked.connect(
- lambda u=u, g=g, p=price: self.repair_unit(g, u, p)
+ lambda u=unit, g=g, p=unit_type.price: self.repair_unit(g, u, p)
)
self.intelLayout.addWidget(repair, i, 1)
i = i + 1
@@ -217,13 +223,12 @@ class QGroundObjectMenu(QDialog):
def update_total_value(self):
total_value = 0
- for group in self.ground_object.groups:
- for u in group.units:
- utype = unit_type_of(u)
- if utype in PRICES:
- total_value = total_value + PRICES[utype]
- else:
- total_value = total_value + 1
+ if not self.ground_object.purchasable:
+ return
+ for u in self.ground_object.units:
+ # Hack: Unknown variant.
+ unit_type = next(GroundUnitType.for_dcs_type(vehicles.vehicle_map[u.type]))
+ total_value += unit_type.price
if self.sell_all_button is not None:
self.sell_all_button.setText("Disband (+$" + str(self.total_value) + "M)")
self.total_value = total_value
@@ -340,10 +345,7 @@ class QBuyGroupForGroundObjectDialog(QDialog):
# Armored units
for unit in set(faction.ground_units):
- self.buyArmorCombo.addItem(
- db.unit_type_name_2(unit) + " [$" + str(db.PRICES[unit]) + "M]",
- userData=unit,
- )
+ self.buyArmorCombo.addItem(f"{unit} [${unit.price}M]", userData=unit)
self.buyArmorCombo.currentIndexChanged.connect(self.armorComboChanged)
self.amount.setMinimum(2)
@@ -404,33 +406,19 @@ class QBuyGroupForGroundObjectDialog(QDialog):
)
def armorComboChanged(self, index):
- self.buyArmorButton.setText(
- "Buy [$"
- + str(db.PRICES[self.buyArmorCombo.itemData(index)] * self.amount.value())
- + "M][-$"
- + str(self.current_group_value)
- + "M]"
- )
+ unit_type = self.buyArmorCombo.itemData(self.buyArmorCombo.currentIndex())
+ price = unit_type.price * self.amount.value()
+ self.buyArmorButton.setText(f"Buy [${price}M][-${self.current_group_value}M]")
def amountComboChanged(self):
- self.buyArmorButton.setText(
- "Buy [$"
- + str(
- db.PRICES[
- self.buyArmorCombo.itemData(self.buyArmorCombo.currentIndex())
- ]
- * self.amount.value()
- )
- + "M][-$"
- + str(self.current_group_value)
- + "M]"
- )
+ unit_type = self.buyArmorCombo.itemData(self.buyArmorCombo.currentIndex())
+ price = unit_type.price * self.amount.value()
+ self.buyArmorButton.setText(f"Buy [${price}M][-${self.current_group_value}M]")
def buyArmor(self):
logging.info("Buying Armor ")
utype = self.buyArmorCombo.itemData(self.buyArmorCombo.currentIndex())
- logging.info(utype)
- price = db.PRICES[utype] * self.amount.value() - self.current_group_value
+ price = utype.price * self.amount.value() - self.current_group_value
if price > self.game.budget:
self.error_money()
self.close()
diff --git a/qt_ui/windows/intel.py b/qt_ui/windows/intel.py
index 6dd02915..65e352bf 100644
--- a/qt_ui/windows/intel.py
+++ b/qt_ui/windows/intel.py
@@ -15,7 +15,7 @@ from PySide2.QtWidgets import (
QWidget,
)
-from game.game import Game, db
+from game.game import Game
from qt_ui.uiconstants import ICONS
from qt_ui.windows.finances.QFinancesMenu import FinancesLayout
@@ -111,7 +111,7 @@ class ArmyIntelLayout(IntelTableLayout):
for vehicle, count in base.armor.items():
if not count:
continue
- self.add_row(vehicle.id, count)
+ self.add_row(vehicle.name, count)
self.add_spacer()
self.add_row("Total", total)