From 09704b6f379ef1e35aceca2d82affb272ca1b31c Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 17 Jun 2021 21:48:02 -0700 Subject: [PATCH] Add a wrapper type for ground unit info. --- game/data/groundunitclass.py | 246 +---------- game/db.py | 391 +----------------- game/dcs/aircrafttype.py | 11 +- game/dcs/groundunittype.py | 95 +++++ game/dcs/unittype.py | 21 + game/debriefing.py | 20 +- game/event/event.py | 3 +- game/factions/faction.py | 98 ++--- game/procurement.py | 17 +- game/theater/base.py | 36 +- game/theater/controlpoint.py | 24 +- game/transfers.py | 22 +- game/unitdelivery.py | 57 +-- game/unitmap.py | 31 +- gen/airsupportgen.py | 3 +- gen/armor.py | 76 ++-- gen/convoygen.py | 10 +- gen/defenses/armor_group_generator.py | 26 +- gen/defenses/armored_group_generator.py | 29 +- gen/ground_forces/ai_ground_planner.py | 54 ++- gen/naming.py | 8 +- qt_ui/widgets/map/mapmodel.py | 22 +- qt_ui/windows/QDebriefingWindow.py | 17 +- qt_ui/windows/QUnitInfoWindow.py | 85 +--- .../windows/basemenu/DepartingConvoysMenu.py | 17 +- .../windows/basemenu/NewUnitTransferDialog.py | 25 +- qt_ui/windows/basemenu/QRecruitBehaviour.py | 41 +- .../airfield/QAircraftRecruitmentMenu.py | 6 - .../ground_forces/QArmorRecruitmentMenu.py | 24 +- qt_ui/windows/basemenu/intel/QIntelInfo.py | 7 +- .../windows/groundobject/QGroundObjectMenu.py | 88 ++-- qt_ui/windows/intel.py | 4 +- 32 files changed, 469 insertions(+), 1145 deletions(-) create mode 100644 game/dcs/groundunittype.py create mode 100644 game/dcs/unittype.py 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)