Add a wrapper type for ground unit info.

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

View File

@ -1,239 +1,17 @@
from __future__ import annotations
from enum import unique, Enum from 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 @unique
class GroundUnitClass(Enum): class GroundUnitClass(Enum):
Tank = ( Tank = "Tank"
"Tank", Atgm = "ATGM"
( Ifv = "IFV"
Armor.MBT_T_55, Apc = "APC"
Armor.MBT_T_72B, Artillery = "Artillery"
Armor.MBT_T_72B3, Logistics = "Logistics"
Armor.MBT_T_80U, Recon = "Recon"
Armor.MBT_T_90, Infantry = "Infantry"
Armor.MBT_Leopard_2A4, Shorads = "SHORADS"
Armor.MBT_Leopard_2A4_Trs, Manpads = "MANPADS"
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

View File

@ -1,8 +1,7 @@
import json
from datetime import datetime from datetime import datetime
from enum import Enum from enum import Enum
from pathlib import Path 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.countries import country_dict
from dcs.helicopters import ( from dcs.helicopters import (
@ -21,8 +20,6 @@ from dcs.planes import (
plane_map, plane_map,
) )
from dcs.ships import ( from dcs.ships import (
Boat_Armed_Hi_speed,
Bulker_Yakushev,
CVN_71_Theodore_Roosevelt, CVN_71_Theodore_Roosevelt,
CVN_72_Abraham_Lincoln, CVN_72_Abraham_Lincoln,
CVN_73_George_Washington, CVN_73_George_Washington,
@ -30,21 +27,12 @@ from dcs.ships import (
CVN_75_Harry_S__Truman, CVN_75_Harry_S__Truman,
CV_1143_5_Admiral_Kuznetsov, CV_1143_5_Admiral_Kuznetsov,
CV_1143_5_Admiral_Kuznetsov_2017, CV_1143_5_Admiral_Kuznetsov_2017,
Cargo_Ivanov,
LHA_1_Tarawa,
Tanker_Elnya_160,
ship_map, ship_map,
) )
from dcs.terrain.terrain import Airport from dcs.terrain.terrain import Airport
from dcs.unit import Ship, Unit, Vehicle
from dcs.unitgroup import ShipGroup, StaticGroup from dcs.unitgroup import ShipGroup, StaticGroup
from dcs.unittype import UnitType, VehicleType from dcs.unittype import UnitType
from dcs.vehicles import ( from dcs.vehicles import (
AirDefence,
Armor,
Artillery,
Infantry,
Unarmed,
vehicle_map, 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. # to be cheap enough to repair with a single turn's income.
RUNWAY_REPAIR_COST = 100 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. Units separated by country.
country : DCS Country name country : DCS Country name
@ -620,107 +343,6 @@ def upgrade_to_supercarrier(unit, name: str):
return unit 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]]: def unit_type_from_name(name: str) -> Optional[Type[UnitType]]:
if name in vehicle_map: if name in vehicle_map:
return vehicle_map[name] return vehicle_map[name]
@ -734,15 +356,6 @@ def unit_type_from_name(name: str) -> Optional[Type[UnitType]]:
return None 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): def country_id_from_name(name):
for k, v in country_dict.items(): for k, v in country_dict.items():
if v.name == name: if v.name == name:

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -110,7 +110,6 @@ class AirSupportConflictGenerator:
): ):
# TODO: Make loiter altitude a property of the unit type. # TODO: Make loiter altitude a property of the unit type.
alt, airspeed = self._get_tanker_params(tanker_unit_type.dcs_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() freq = self.radio_registry.alloc_uhf()
tacan = self.tacan_registry.alloc_for_band(TacanBand.Y) tacan = self.tacan_registry.alloc_for_band(TacanBand.Y)
tanker_heading = ( tanker_heading = (
@ -175,7 +174,7 @@ class AirSupportConflictGenerator:
TankerInfo( TankerInfo(
str(tanker_group.name), str(tanker_group.name),
callsign, callsign,
variant, tanker_unit_type.name,
freq, freq,
tacan, tacan,
blue=True, blue=True,

View File

@ -10,7 +10,6 @@ from dcs.action import AITaskPush
from dcs.condition import GroupLifeLess, Or, TimeAfter, UnitDamaged from dcs.condition import GroupLifeLess, Or, TimeAfter, UnitDamaged
from dcs.country import Country from dcs.country import Country
from dcs.mapping import Point from dcs.mapping import Point
from dcs.planes import MQ_9_Reaper
from dcs.point import PointAction from dcs.point import PointAction
from dcs.task import ( from dcs.task import (
EPLRS, EPLRS,
@ -26,19 +25,18 @@ from dcs.task import (
from dcs.triggers import Event, TriggerOnce from dcs.triggers import Event, TriggerOnce
from dcs.unit import Vehicle from dcs.unit import Vehicle
from dcs.unitgroup import VehicleGroup from dcs.unitgroup import VehicleGroup
from dcs.unittype import VehicleType
from game import db from game.data.groundunitclass import GroundUnitClass
from game.dcs.aircrafttype import AircraftType 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.unitmap import UnitMap
from game.utils import heading_sum, opposite_heading from game.utils import heading_sum, opposite_heading
from game.theater.controlpoint import ControlPoint
from gen.ground_forces.ai_ground_planner import ( from gen.ground_forces.ai_ground_planner import (
DISTANCE_FROM_FRONTLINE, DISTANCE_FROM_FRONTLINE,
CombatGroup, CombatGroup,
CombatGroupRole, CombatGroupRole,
) )
from .callsigns import callsign_for_support_unit from .callsigns import callsign_for_support_unit
from .conflictgen import Conflict from .conflictgen import Conflict
from .ground_forces.combat_stance import CombatStance from .ground_forces.combat_stance import CombatStance
@ -226,19 +224,18 @@ class GroundConflictGenerator:
else: else:
cp = self.conflict.red_cp cp = self.conflict.red_cp
if is_player: faction = self.game.faction_for(is_player)
faction = self.game.player_name
else:
faction = self.game.enemy_name
# Disable infantry unit gen if disabled # Disable infantry unit gen if disabled
if not self.game.settings.perf_infantry: if not self.game.settings.perf_infantry:
if self.game.settings.manpads: if self.game.settings.manpads:
# 50% of armored units protected by manpad # 50% of armored units protected by manpad
if random.choice([True, False]): if random.choice([True, False]):
manpads = db.find_manpad(faction) manpads = list(faction.infantry_with_class(GroundUnitClass.Manpads))
if len(manpads) > 0: if manpads:
u = random.choice(manpads) u = random.choices(
manpads, weights=[m.spawn_weight for m in manpads]
)[0]
self.mission.vehicle_group( self.mission.vehicle_group(
side, side,
namegen.next_infantry_name(side, cp.id, u), namegen.next_infantry_name(side, cp.id, u),
@ -250,30 +247,38 @@ class GroundConflictGenerator:
) )
return return
possible_infantry_units = db.find_infantry( possible_infantry_units = set(
faction, allow_manpad=self.game.settings.manpads 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 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( self.mission.vehicle_group(
side, side,
namegen.next_infantry_name(side, cp.id, u), namegen.next_infantry_name(side, cp.id, units[0]),
u, units[0].dcs_unit_type,
position=infantry_position, position=infantry_position,
group_size=1, group_size=1,
heading=forward_heading, heading=forward_heading,
move_formation=PointAction.OffRoad, move_formation=PointAction.OffRoad,
) )
for i in range(INFANTRY_GROUP_SIZE): for unit in units[1:]:
u = random.choice(possible_infantry_units)
position = infantry_position.random_point_within(55, 5) position = infantry_position.random_point_within(55, 5)
self.mission.vehicle_group( self.mission.vehicle_group(
side, side,
namegen.next_infantry_name(side, cp.id, u), namegen.next_infantry_name(side, cp.id, unit),
u, unit.dcs_unit_type,
position=position, position=position,
group_size=1, group_size=1,
heading=forward_heading, heading=forward_heading,
@ -313,7 +318,7 @@ class GroundConflictGenerator:
) )
artillery_trigger.add_condition(TimeAfter(seconds=random.randint(1, 45) * 60)) artillery_trigger.add_condition(TimeAfter(seconds=random.randint(1, 45) * 60))
# TODO: Update to fire at group instead of point # TODO: Update to fire at group instead of point
fire_task = FireAtPoint(target, len(gen_group.units) * 10, 100) fire_task = FireAtPoint(target, gen_group.size * 10, 100)
fire_task.number = 2 if stance != CombatStance.RETREAT else 1 fire_task.number = 2 if stance != CombatStance.RETREAT else 1
dcs_group.add_trigger_action(fire_task) dcs_group.add_trigger_action(fire_task)
artillery_trigger.add_action(AITaskPush(dcs_group.id, len(dcs_group.tasks))) artillery_trigger.add_action(AITaskPush(dcs_group.id, len(dcs_group.tasks)))
@ -503,7 +508,7 @@ class GroundConflictGenerator:
return return
for dcs_group, group in ally_groups: for dcs_group, group in ally_groups:
if hasattr(group.units[0], "eplrs") and group.units[0].eplrs: if getattr(group.unit_type.dcs_unit_type, "eplrs", False):
dcs_group.points[0].tasks.append(EPLRS(dcs_group.id)) dcs_group.points[0].tasks.append(EPLRS(dcs_group.id))
if group.role == CombatGroupRole.ARTILLERY: if group.role == CombatGroupRole.ARTILLERY:
@ -674,7 +679,7 @@ class GroundConflictGenerator:
Search the enemy groups for a potential target suitable to an artillery unit Search the enemy groups for a potential target suitable to an artillery unit
""" """
# TODO: Update to return a list of groups instead of a single point # 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: if not enemy_groups:
return None return None
for _ in range(10): for _ in range(10):
@ -691,7 +696,7 @@ class GroundConflictGenerator:
""" """
For artilery group, decide the distance from frontline with the range of the unit 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]: if rg > DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][1]:
rg = random.randint( rg = random.randint(
DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][0], DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][0],
@ -724,7 +729,7 @@ class GroundConflictGenerator:
def _generate_groups( def _generate_groups(
self, self,
groups: List[CombatGroup], groups: list[CombatGroup],
frontline_vector: Tuple[Point, int, int], frontline_vector: Tuple[Point, int, int],
is_player: bool, is_player: bool,
) -> List[Tuple[VehicleGroup, CombatGroup]]: ) -> List[Tuple[VehicleGroup, CombatGroup]]:
@ -755,10 +760,9 @@ class GroundConflictGenerator:
if final_position is not None: if final_position is not None:
g = self._generate_group( g = self._generate_group(
self.mission.country(country), self.mission.country(country),
group.units[0], group.unit_type,
len(group.units), group.size,
final_position, final_position,
distance_from_frontline,
heading=opposite_heading(spawn_heading), heading=opposite_heading(spawn_heading),
) )
if is_player: if is_player:
@ -782,10 +786,9 @@ class GroundConflictGenerator:
def _generate_group( def _generate_group(
self, self,
side: Country, side: Country,
unit: VehicleType, unit_type: GroundUnitType,
count: int, count: int,
at: Point, at: Point,
distance_from_frontline,
move_formation: PointAction = PointAction.OffRoad, move_formation: PointAction = PointAction.OffRoad,
heading=0, heading=0,
) -> VehicleGroup: ) -> VehicleGroup:
@ -795,18 +798,17 @@ class GroundConflictGenerator:
else: else:
cp = self.conflict.red_cp cp = self.conflict.red_cp
logging.info("armorgen: {} for {}".format(unit, side.id))
group = self.mission.vehicle_group( group = self.mission.vehicle_group(
side, side,
namegen.next_unit_name(side, cp.id, unit), namegen.next_unit_name(side, cp.id, unit_type),
unit, unit_type.dcs_unit_type,
position=at, position=at,
group_size=count, group_size=count,
heading=heading, heading=heading,
move_formation=move_formation, move_formation=move_formation,
) )
self.unit_map.add_front_line_units(group, cp) self.unit_map.add_front_line_units(group, cp, unit_type)
for c in range(count): for c in range(count):
vehicle: Vehicle = group.units[c] vehicle: Vehicle = group.units[c]

View File

@ -1,15 +1,15 @@
from __future__ import annotations from __future__ import annotations
import itertools import itertools
from typing import Dict, TYPE_CHECKING, Type from typing import TYPE_CHECKING
from dcs import Mission from dcs import Mission
from dcs.mapping import Point from dcs.mapping import Point
from dcs.point import PointAction from dcs.point import PointAction
from dcs.unit import Vehicle from dcs.unit import Vehicle
from dcs.unitgroup import VehicleGroup from dcs.unitgroup import VehicleGroup
from dcs.unittype import VehicleType
from game.dcs.groundunittype import GroundUnitType
from game.transfers import Convoy from game.transfers import Convoy
from game.unitmap import UnitMap from game.unitmap import UnitMap
from game.utils import kph from game.utils import kph
@ -50,7 +50,7 @@ class ConvoyGenerator:
self, self,
name: str, name: str,
position: Point, position: Point,
units: Dict[Type[VehicleType], int], units: dict[GroundUnitType, int],
for_player: bool, for_player: bool,
) -> VehicleGroup: ) -> VehicleGroup:
country = self.mission.country( country = self.mission.country(
@ -63,7 +63,7 @@ class ConvoyGenerator:
group = self.mission.vehicle_group( group = self.mission.vehicle_group(
country, country,
name, name,
main_unit_type, main_unit_type.dcs_unit_type,
position=position, position=position,
group_size=main_unit_count, group_size=main_unit_count,
move_formation=PointAction.OnRoad, move_formation=PointAction.OnRoad,
@ -76,7 +76,7 @@ class ConvoyGenerator:
for unit_type, count in unit_types[1:]: for unit_type, count in unit_types[1:]:
for i in range(count): for i in range(count):
v = self.mission.vehicle( 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.x = position.x
v.position.y = next(y) v.position.y = next(y)

View File

@ -1,8 +1,11 @@
import random 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 ( from gen.defenses.armored_group_generator import (
ArmoredGroupGenerator, ArmoredGroupGenerator,
FixedSizeArmorGroupGenerator, FixedSizeArmorGroupGenerator,
@ -14,8 +17,14 @@ def generate_armor_group(faction: str, game, ground_object):
This generate a group of ground units This generate a group of ground units
:return: Generated group :return: Generated group
""" """
armor_types = (
GroundUnitClass.Apc,
GroundUnitClass.Atgm,
GroundUnitClass.Ifv,
GroundUnitClass.Tank,
)
possible_unit = [ 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: if len(possible_unit) > 0:
unit_type = random.choice(possible_unit) unit_type = random.choice(possible_unit)
@ -23,7 +32,9 @@ def generate_armor_group(faction: str, game, ground_object):
return None 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 This generate a group of ground units of given type
:return: Generated group :return: Generated group
@ -33,7 +44,12 @@ def generate_armor_group_of_type(game, ground_object, unit_type):
return generator.get_generated_group() 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 This generate a group of ground units of given type and size
:return: Generated group :return: Generated group

View File

@ -1,15 +1,22 @@
import random 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 from gen.sam.group_generator import GroupGenerator
class ArmoredGroupGenerator(GroupGenerator): class ArmoredGroupGenerator(GroupGenerator):
def __init__(self, game, ground_object, unit_type): def __init__(
super(ArmoredGroupGenerator, self).__init__(game, ground_object) self,
game: Game,
ground_object: VehicleGroupGroundObject,
unit_type: GroundUnitType,
) -> None:
super().__init__(game, ground_object)
self.unit_type = unit_type self.unit_type = unit_type
def generate(self): def generate(self) -> None:
grid_x = random.randint(2, 3) grid_x = random.randint(2, 3)
grid_y = random.randint(1, 2) grid_y = random.randint(1, 2)
@ -20,7 +27,7 @@ class ArmoredGroupGenerator(GroupGenerator):
for j in range(grid_y): for j in range(grid_y):
index = index + 1 index = index + 1
self.add_unit( self.add_unit(
self.unit_type, self.unit_type.dcs_unit_type,
"Armor#" + str(index), "Armor#" + str(index),
self.position.x + spacing * i, self.position.x + spacing * i,
self.position.y + spacing * j, self.position.y + spacing * j,
@ -29,8 +36,14 @@ class ArmoredGroupGenerator(GroupGenerator):
class FixedSizeArmorGroupGenerator(GroupGenerator): class FixedSizeArmorGroupGenerator(GroupGenerator):
def __init__(self, game, ground_object, unit_type, size): def __init__(
super(FixedSizeArmorGroupGenerator, self).__init__(game, ground_object) self,
game: Game,
ground_object: VehicleGroupGroundObject,
unit_type: GroundUnitType,
size: int,
) -> None:
super().__init__(game, ground_object)
self.unit_type = unit_type self.unit_type = unit_type
self.size = size self.size = size
@ -41,7 +54,7 @@ class FixedSizeArmorGroupGenerator(GroupGenerator):
for i in range(self.size): for i in range(self.size):
index = index + 1 index = index + 1
self.add_unit( self.add_unit(
self.unit_type, self.unit_type.dcs_unit_type,
"Armor#" + str(index), "Armor#" + str(index),
self.position.x + spacing * i, self.position.x + spacing * i,
self.position.y, self.position.y,

View File

@ -3,11 +3,9 @@ import random
from enum import Enum from enum import Enum
from typing import Dict, List from typing import Dict, List
from dcs.unittype import VehicleType
from game.theater import ControlPoint
from game.data.groundunitclass import GroundUnitClass 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 from gen.ground_forces.combat_stance import CombatStance
MAX_COMBAT_GROUP_PER_CP = 10 MAX_COMBAT_GROUP_PER_CP = 10
@ -48,17 +46,19 @@ GROUP_SIZES_BY_COMBAT_STANCE = {
class CombatGroup: class CombatGroup:
def __init__(self, role: CombatGroupRole): def __init__(
self.units: List[VehicleType] = [] self, role: CombatGroupRole, unit_type: GroundUnitType, size: int
) -> None:
self.unit_type = unit_type
self.size = size
self.role = role self.role = role
self.assigned_enemy_cp = None self.assigned_enemy_cp = None
self.start_position = None self.start_position = None
def __str__(self): def __str__(self):
s = "" s = f"ROLE : {self.role}\n"
s += "ROLE : " + str(self.role) + "\n" if self.size:
if len(self.units) > 0: s += f"UNITS {self.unit_type} * {self.size}"
s += "UNITS " + self.units[0].name + " * " + str(len(self.units))
return s return s
@ -97,28 +97,29 @@ class GroundPlanner:
# Create combat groups and assign them randomly to each enemy CP # Create combat groups and assign them randomly to each enemy CP
for unit_type in self.cp.base.armor: 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 collection = self.tank_groups
role = CombatGroupRole.TANK role = CombatGroupRole.TANK
elif unit_type in GroundUnitClass.Apc: elif unit_class is GroundUnitClass.Apc:
collection = self.apc_group collection = self.apc_group
role = CombatGroupRole.APC role = CombatGroupRole.APC
elif unit_type in GroundUnitClass.Artillery: elif unit_class is GroundUnitClass.Artillery:
collection = self.art_group collection = self.art_group
role = CombatGroupRole.ARTILLERY role = CombatGroupRole.ARTILLERY
elif unit_type in GroundUnitClass.Ifv: elif unit_class is GroundUnitClass.Ifv:
collection = self.ifv_group collection = self.ifv_group
role = CombatGroupRole.IFV role = CombatGroupRole.IFV
elif unit_type in GroundUnitClass.Logistics: elif unit_class is GroundUnitClass.Logistics:
collection = self.logi_groups collection = self.logi_groups
role = CombatGroupRole.LOGI role = CombatGroupRole.LOGI
elif unit_type in GroundUnitClass.Atgm: elif unit_class is GroundUnitClass.Atgm:
collection = self.atgm_group collection = self.atgm_group
role = CombatGroupRole.ATGM role = CombatGroupRole.ATGM
elif unit_type in GroundUnitClass.Shorads: elif unit_class is GroundUnitClass.Shorads:
collection = self.shorad_groups collection = self.shorad_groups
role = CombatGroupRole.SHORAD role = CombatGroupRole.SHORAD
elif unit_type in GroundUnitClass.Recon: elif unit_class is GroundUnitClass.Recon:
collection = self.recon_groups collection = self.recon_groups
role = CombatGroupRole.RECON role = CombatGroupRole.RECON
else: else:
@ -137,17 +138,17 @@ class GroundPlanner:
while available > 0: while available > 0:
if role == CombatGroupRole.SHORAD: if role == CombatGroupRole.SHORAD:
n = 1 count = 1
else: else:
n = random.choice(group_size_choice) count = random.choice(group_size_choice)
if n > available: if count > available:
if available >= 2: if available >= 2:
n = 2 count = 2
else: else:
n = 1 count = 1
available -= n available -= count
group = CombatGroup(role) group = CombatGroup(role, unit_type, count)
if len(self.connected_enemy_cp) > 0: if len(self.connected_enemy_cp) > 0:
enemy_cp = random.choice(self.connected_enemy_cp).id enemy_cp = random.choice(self.connected_enemy_cp).id
self.units_per_cp[enemy_cp].append(group) self.units_per_cp[enemy_cp].append(group)
@ -155,9 +156,6 @@ class GroundPlanner:
else: else:
self.reserve.append(group) self.reserve.append(group)
group.assigned_enemy_cp = "__reserve__" group.assigned_enemy_cp = "__reserve__"
for i in range(n):
group.units.append(unit_type)
collection.append(group) collection.append(group)
if remaining_available_frontline_units == 0: if remaining_available_frontline_units == 0:

View File

@ -3,11 +3,9 @@ import time
from typing import List from typing import List
from dcs.country import Country from dcs.country import Country
from dcs.unittype import UnitType
from game import db
from game.dcs.aircrafttype import AircraftType from game.dcs.aircrafttype import AircraftType
from game.dcs.unittype import UnitType
from gen.flights.flight import Flight from gen.flights.flight import Flight
ALPHA_MILITARY = [ ALPHA_MILITARY = [
@ -298,7 +296,7 @@ class NameGenerator:
def next_unit_name(cls, country: Country, parent_base_id: int, unit_type: UnitType): def next_unit_name(cls, country: Country, parent_base_id: int, unit_type: UnitType):
cls.number += 1 cls.number += 1
return "unit|{}|{}|{}|{}|".format( 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 @classmethod
@ -310,7 +308,7 @@ class NameGenerator:
country.id, country.id,
cls.infantry_number, cls.infantry_number,
parent_base_id, parent_base_id,
db.unit_type_name(unit_type), unit_type.name,
) )
@classmethod @classmethod

View File

@ -10,7 +10,8 @@ from dcs.unit import Unit
from dcs.vehicles import vehicle_map from dcs.vehicles import vehicle_map
from shapely.geometry import LineString, Point as ShapelyPoint, Polygon, MultiPolygon 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.navmesh import NavMesh
from game.profiling import logged_duration from game.profiling import logged_duration
from game.theater import ( from game.theater import (
@ -191,12 +192,6 @@ class GroundObjectJs(QObject):
self.game = game self.game = game
self.theater = game.theater self.theater = game.theater
self.buildings = self.theater.find_ground_objects_by_obj_name(self.tgo.obj_name) 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 self.dialog: Optional[QGroundObjectMenu] = None
@Slot() @Slot()
@ -223,14 +218,15 @@ class GroundObjectJs(QObject):
def category(self) -> str: def category(self) -> str:
return self.tgo.category 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 "" dead_label = " [DEAD]" if dead else ""
unit_display_name = unit.type unit_display_name = unit.type
unit_type = vehicle_map.get(unit.type) dcs_unit_type = vehicle_map.get(unit.type)
if unit_type is not None: if dcs_unit_type is not None:
unit_display_name = db.unit_get_expanded_info( # TODO: Make the TGO contain GroundUnitType instead of the pydcs Group.
self.country, unit_type, "name" # 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}" return f"Unit #{unit.id} - {unit_display_name}{dead_label}"
@Property(list, notify=unitsChanged) @Property(list, notify=unitsChanged)

View File

@ -11,7 +11,6 @@ from PySide2.QtWidgets import (
QVBoxLayout, QVBoxLayout,
) )
from game import db
from game.debriefing import Debriefing 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.air_losses.by_type(player), lambda u: u.name)
self.add_loss_rows( self.add_loss_rows(
debriefing.front_line_losses_by_type(player), debriefing.front_line_losses_by_type(player), lambda u: str(u)
lambda u: db.unit_type_name(u),
) )
self.add_loss_rows( self.add_loss_rows(
debriefing.convoy_losses_by_type(player), debriefing.convoy_losses_by_type(player), lambda u: f"{u} from convoy"
lambda u: f"{db.unit_type_name(u)} from convoy",
) )
self.add_loss_rows( self.add_loss_rows(
debriefing.cargo_ship_losses_by_type(player), 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( self.add_loss_rows(
debriefing.airlift_losses_by_type(player), debriefing.airlift_losses_by_type(player), lambda u: f"{u} from airlift"
lambda u: f"{db.unit_type_name(u)} from airlift",
)
self.add_loss_rows(
debriefing.building_losses_by_type(player),
lambda u: u,
) )
self.add_loss_rows(debriefing.building_losses_by_type(player), lambda u: u)
# TODO: Display dead ground object units and runways. # TODO: Display dead ground object units and runways.

View File

@ -1,9 +1,5 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass
from typing import Type, Union
import dcs
from PySide2.QtCore import Qt from PySide2.QtCore import Qt
from PySide2.QtGui import QIcon from PySide2.QtGui import QIcon
from PySide2.QtWidgets import ( from PySide2.QtWidgets import (
@ -13,79 +9,23 @@ from PySide2.QtWidgets import (
QTextBrowser, QTextBrowser,
QFrame, QFrame,
) )
from dcs.unittype import VehicleType
import gen.flights.ai_flight_planner_db import gen.flights.ai_flight_planner_db
from game import db
from game.dcs.aircrafttype import AircraftType from game.dcs.aircrafttype import AircraftType
from game.dcs.groundunittype import GroundUnitType
from game.dcs.unittype import UnitType
from game.game import Game from game.game import Game
from gen.flights.flight import FlightType from gen.flights.flight import FlightType
from qt_ui.uiconstants import AIRCRAFT_BANNERS, VEHICLE_BANNERS 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): class QUnitInfoWindow(QDialog):
def __init__( def __init__(self, game: Game, unit_type: UnitType) -> None:
self, game: Game, unit_type: Union[AircraftType, Type[VehicleType]]
) -> None:
super().__init__() super().__init__()
self.setModal(True) self.setModal(True)
self.game = game self.game = game
self.unit_type = unit_type self.unit_type = unit_type
if isinstance(unit_type, AircraftType):
self.name = unit_type.name self.name = unit_type.name
else:
self.name = db.unit_get_expanded_info(
self.game.player_country, self.unit_type, "name"
)
self.setWindowTitle(f"Unit Info: {self.name}") self.setWindowTitle(f"Unit Info: {self.name}")
self.setWindowIcon(QIcon("./resources/icon.png")) self.setWindowIcon(QIcon("./resources/icon.png"))
self.setMinimumHeight(570) self.setMinimumHeight(570)
@ -101,8 +41,8 @@ class QUnitInfoWindow(QDialog):
if isinstance(self.unit_type, AircraftType): if isinstance(self.unit_type, AircraftType):
pixmap = AIRCRAFT_BANNERS.get(self.unit_type.dcs_id) pixmap = AIRCRAFT_BANNERS.get(self.unit_type.dcs_id)
elif dcs.vehicles.vehicle_map.get(self.unit_type.id) is not None: elif isinstance(self.unit_type, GroundUnitType):
pixmap = VEHICLE_BANNERS.get(self.unit_type.id) pixmap = VEHICLE_BANNERS.get(self.unit_type.dcs_id)
if pixmap is None: if pixmap is None:
pixmap = AIRCRAFT_BANNERS.get("Missing") pixmap = AIRCRAFT_BANNERS.get("Missing")
header.setPixmap(pixmap.scaled(header.width(), header.height())) header.setPixmap(pixmap.scaled(header.width(), header.height()))
@ -115,20 +55,21 @@ class QUnitInfoWindow(QDialog):
self.details_grid_layout = QGridLayout() self.details_grid_layout = QGridLayout()
self.details_grid_layout.setMargin(0) self.details_grid_layout.setMargin(0)
unit_info = UnitInfo.from_unit_type(self.game.player_country, self.unit_type)
self.name_box = QLabel( self.name_box = QLabel(
f"<b>Name:</b> {unit_info.manufacturer} {unit_info.name}" f"<b>Name:</b> {unit_type.manufacturer} {unit_type.name}"
) )
self.name_box.setProperty("style", "info-element") self.name_box.setProperty("style", "info-element")
self.country_box = QLabel(f"<b>Country of Origin:</b> {unit_info.origin}") self.country_box = QLabel(
f"<b>Country of Origin:</b> {unit_type.country_of_origin}"
)
self.country_box.setProperty("style", "info-element") self.country_box.setProperty("style", "info-element")
self.role_box = QLabel(f"<b>Role:</b> {unit_info.role}") self.role_box = QLabel(f"<b>Role:</b> {unit_type.role}")
self.role_box.setProperty("style", "info-element") self.role_box.setProperty("style", "info-element")
self.year_box = QLabel( self.year_box = QLabel(
f"<b>Variant Introduction:</b> {unit_info.introduction_year}" f"<b>Variant Introduction:</b> {unit_type.year_introduced}"
) )
self.year_box.setProperty("style", "info-element") self.year_box.setProperty("style", "info-element")
@ -152,7 +93,7 @@ class QUnitInfoWindow(QDialog):
# Finally, add the description box. # Finally, add the description box.
self.details_text = QTextBrowser() self.details_text = QTextBrowser()
self.details_text.setProperty("style", "info-desc") 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.gridLayout.addWidget(self.details_text, 3, 0)
self.layout.addLayout(self.gridLayout, 1, 0) self.layout.addLayout(self.gridLayout, 1, 0)

View File

@ -10,7 +10,6 @@ from PySide2.QtWidgets import (
QWidget, QWidget,
) )
from game import db
from game.theater import ControlPoint from game.theater import ControlPoint
from game.transfers import MultiGroupTransport from game.transfers import MultiGroupTransport
from qt_ui.dialogs import Dialog from qt_ui.dialogs import Dialog
@ -19,7 +18,7 @@ from qt_ui.uiconstants import VEHICLES_ICONS
class DepartingConvoyInfo(QGroupBox): 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}") super().__init__(f"{convoy.name} to {convoy.destination}")
self.convoy = convoy self.convoy = convoy
@ -31,17 +30,14 @@ class DepartingConvoyInfo(QGroupBox):
for idx, (unit_type, count) in enumerate(convoy.units.items()): for idx, (unit_type, count) in enumerate(convoy.units.items()):
icon = QLabel() icon = QLabel()
if unit_type.id in VEHICLES_ICONS.keys(): if unit_type.dcs_id in VEHICLES_ICONS.keys():
icon.setPixmap(VEHICLES_ICONS[unit_type.id]) icon.setPixmap(VEHICLES_ICONS[unit_type.dcs_id])
else: else:
icon.setText("<b>" + unit_type.id[:8] + "</b>") icon.setText("<b>" + unit_type.id[:8] + "</b>")
icon.setProperty("style", "icon-armor") icon.setProperty("style", "icon-armor")
unit_layout.addWidget(icon, idx, 0) 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( unit_layout.addWidget(
QLabel(f"{count} x <strong>{unit_display_name}</strong>"), QLabel(f"{count} x <strong>{unit_type.name}</strong>"),
idx, idx,
1, 1,
) )
@ -68,7 +64,6 @@ class DepartingConvoysList(QFrame):
def __init__(self, cp: ControlPoint, game_model: GameModel): def __init__(self, cp: ControlPoint, game_model: GameModel):
super().__init__() super().__init__()
self.cp = cp self.cp = cp
self.game_model = game_model
self.setMinimumWidth(500) self.setMinimumWidth(500)
layout = QVBoxLayout() layout = QVBoxLayout()
@ -79,11 +74,11 @@ class DepartingConvoysList(QFrame):
scroll_content.setLayout(task_box_layout) scroll_content.setLayout(task_box_layout)
for convoy in game_model.game.transfers.convoys.departing_from(cp): 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) task_box_layout.addWidget(group_info)
for cargo_ship in game_model.game.transfers.cargo_ships.departing_from(cp): 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) task_box_layout.addWidget(group_info)
scroll_content.setLayout(task_box_layout) scroll_content.setLayout(task_box_layout)

View File

@ -20,10 +20,10 @@ from PySide2.QtWidgets import (
QVBoxLayout, QVBoxLayout,
QWidget, QWidget,
) )
from dcs.task import PinpointStrike
from dcs.unittype import UnitType 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.theater import ControlPoint
from game.transfers import TransferOrder from game.transfers import TransferOrder
from qt_ui.models import GameModel from qt_ui.models import GameModel
@ -63,12 +63,7 @@ class UnitTransferList(QFrame):
task_box_layout = QGridLayout() task_box_layout = QGridLayout()
scroll_content.setLayout(task_box_layout) scroll_content.setLayout(task_box_layout)
units_column = sorted( units_column = sorted(cp.base.armor, key=lambda u: u.name)
cp.base.armor,
key=lambda u: db.unit_get_expanded_info(
self.game_model.game.player_country, u, "name"
),
)
count = 0 count = 0
for count, unit_type in enumerate(units_column): 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) unit_types = set(self.game_model.game.faction_for(player=True).ground_units)
sorted_units = sorted( sorted_units = sorted(
{u for u in unit_types if self.cp.base.total_units_of_type(u)}, {u for u in unit_types if self.cp.base.total_units_of_type(u)},
key=lambda u: db.unit_get_expanded_info( key=lambda u: u.name,
self.game_model.game.player_country, u, "name"
),
) )
for row, unit_type in enumerate(sorted_units): for row, unit_type in enumerate(sorted_units):
self.add_unit_row(unit_type, task_box_layout, row) self.add_unit_row(unit_type, task_box_layout, row)
@ -190,7 +183,7 @@ class ScrollingUnitTransferGrid(QFrame):
def add_unit_row( def add_unit_row(
self, self,
unit_type: Type[UnitType], unit_type: GroundUnitType,
layout: QGridLayout, layout: QGridLayout,
row: int, row: int,
) -> None: ) -> None:
@ -203,13 +196,7 @@ class ScrollingUnitTransferGrid(QFrame):
origin_inventory = self.cp.base.total_units_of_type(unit_type) origin_inventory = self.cp.base.total_units_of_type(unit_type)
unit_name = QLabel( unit_name = QLabel(f"<b>{unit_type.name}</b>")
"<b>"
+ db.unit_get_expanded_info(
self.game_model.game.player_country, unit_type, "name"
)
+ "</b>"
)
unit_name.setSizePolicy( unit_name.setSizePolicy(
QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
) )

View File

@ -1,5 +1,4 @@
import logging import logging
from typing import Type, Union
from PySide2.QtWidgets import ( from PySide2.QtWidgets import (
QGroupBox, QGroupBox,
@ -10,9 +9,8 @@ from PySide2.QtWidgets import (
QSizePolicy, QSizePolicy,
QSpacerItem, 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.theater import ControlPoint
from game.unitdelivery import PendingUnitDeliveries from game.unitdelivery import PendingUnitDeliveries
from qt_ui.models import GameModel from qt_ui.models import GameModel
@ -38,7 +36,7 @@ class QRecruitBehaviour:
return self.cp.pending_unit_deliveries return self.cp.pending_unit_deliveries
@property @property
def budget(self) -> int: def budget(self) -> float:
return self.game_model.game.budget return self.game_model.game.budget
@budget.setter @budget.setter
@ -47,7 +45,7 @@ class QRecruitBehaviour:
def add_purchase_row( def add_purchase_row(
self, self,
unit_type: Union[AircraftType, Type[VehicleType]], unit_type: UnitType,
layout: QLayout, layout: QLayout,
row: int, row: int,
) -> int: ) -> int:
@ -61,7 +59,7 @@ class QRecruitBehaviour:
existing_units = self.cp.base.total_units_of_type(unit_type) existing_units = self.cp.base.total_units_of_type(unit_type)
scheduled_units = self.pending_deliveries.units.get(unit_type, 0) scheduled_units = self.pending_deliveries.units.get(unit_type, 0)
unitName = QLabel(f"<b>{self.name_of(unit_type)}</b>") unitName = QLabel(f"<b>{unit_type.name}</b>")
unitName.setSizePolicy( unitName.setSizePolicy(
QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
) )
@ -75,7 +73,7 @@ class QRecruitBehaviour:
self.existing_units_labels[unit_type] = existing_units self.existing_units_labels[unit_type] = existing_units
self.bought_amount_labels[unit_type] = amount_bought self.bought_amount_labels[unit_type] = amount_bought
price = QLabel(f"<b>$ {self.price_of(unit_type)}</b> M") price = QLabel(f"<b>$ {unit_type.price}</b> M")
price.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) price.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
buysell = QGroupBox() buysell = QGroupBox()
@ -149,8 +147,7 @@ class QRecruitBehaviour:
return row + 1 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( self.bought_amount_labels[unit_type].setText(
"<b>{}</b>".format( "<b>{}</b>".format(
unit_type in self.pending_deliveries.units unit_type in self.pending_deliveries.units
@ -166,34 +163,32 @@ class QRecruitBehaviour:
def update_available_budget(self) -> None: def update_available_budget(self) -> None:
GameUpdateSignal.get_instance().updateBudget(self.game_model.game) 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): 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 return
self.pending_deliveries.order({unit_type: 1}) 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_count_label(unit_type)
self.update_available_budget() 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: 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}) self.pending_deliveries.sell({unit_type: 1})
if self.pending_deliveries.units[unit_type] == 0: if self.pending_deliveries.units[unit_type] == 0:
del self.pending_deliveries.units[unit_type] del self.pending_deliveries.units[unit_type]
self._update_count_label(unit_type) self._update_count_label(unit_type)
self.update_available_budget() self.update_available_budget()
def enable_purchase( def enable_purchase(self, unit_type: UnitType) -> bool:
self, unit_type: Union[AircraftType, Type[VehicleType]] return self.budget >= unit_type.price
) -> bool:
return self.budget >= self.price_of(unit_type)
def enable_sale(self, unit_type: Union[AircraftType, Type[VehicleType]]) -> bool: def enable_sale(self, unit_type: UnitType) -> bool:
return True 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 = QUnitInfoWindow(self.game_model.game, unit_type)
self.info_window.show() self.info_window.show()
@ -202,9 +197,3 @@ class QRecruitBehaviour:
Set the maximum number of units that can be bought Set the maximum number of units that can be bought
""" """
self.maximum_units = maximum_units 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

View File

@ -92,12 +92,6 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour):
return False return False
return True 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: def buy(self, unit_type: AircraftType) -> None:
if self.maximum_units > 0: if self.maximum_units > 0:
if self.cp.unclaimed_parking(self.game_model.game) <= 0: if self.cp.unclaimed_parking(self.game_model.game) <= 0:

View File

@ -1,5 +1,3 @@
from typing import Type
from PySide2.QtCore import Qt from PySide2.QtCore import Qt
from PySide2.QtWidgets import ( from PySide2.QtWidgets import (
QFrame, QFrame,
@ -8,10 +6,8 @@ from PySide2.QtWidgets import (
QVBoxLayout, QVBoxLayout,
QWidget, QWidget,
) )
from dcs.unittype import UnitType, VehicleType
from game import db from game.dcs.groundunittype import GroundUnitType
from game.db import PRICES
from game.theater import ControlPoint from game.theater import ControlPoint
from qt_ui.models import GameModel from qt_ui.models import GameModel
from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour
@ -39,11 +35,7 @@ class QArmorRecruitmentMenu(QFrame, QRecruitBehaviour):
unit_types = list( unit_types = list(
set(self.game_model.game.faction_for(player=True).ground_units) set(self.game_model.game.faction_for(player=True).ground_units)
) )
unit_types.sort( unit_types.sort(key=lambda u: u.name)
key=lambda u: db.unit_get_expanded_info(
self.game_model.game.player_country, u, "name"
)
)
for unit_type in unit_types: for unit_type in unit_types:
row = self.add_purchase_row(unit_type, task_box_layout, row) row = self.add_purchase_row(unit_type, task_box_layout, row)
stretch = QVBoxLayout() stretch = QVBoxLayout()
@ -59,18 +51,10 @@ class QArmorRecruitmentMenu(QFrame, QRecruitBehaviour):
main_layout.addWidget(scroll) main_layout.addWidget(scroll)
self.setLayout(main_layout) 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): if not super().enable_purchase(unit_type):
return False return False
return self.cp.has_ground_unit_source(self.game_model.game) 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 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]

View File

@ -11,7 +11,7 @@ from PySide2.QtWidgets import (
QWidget, QWidget,
) )
from game import Game, db from game import Game
from game.theater import ControlPoint from game.theater import ControlPoint
@ -38,10 +38,7 @@ class QIntelInfo(QFrame):
front_line_units = defaultdict(int) front_line_units = defaultdict(int)
for unit_type, count in self.cp.base.armor.items(): for unit_type, count in self.cp.base.armor.items():
if count: if count:
name = db.unit_get_expanded_info( front_line_units[unit_type.name] += count
self.game.enemy_country, unit_type, "name"
)
front_line_units[name] += count
units_by_task["Front line units"] = front_line_units units_by_task["Front line units"] = front_line_units
for task, unit_types in units_by_task.items(): for task, unit_types in units_by_task.items():

View File

@ -20,7 +20,8 @@ from dcs import vehicles
from game import Game, db from game import Game, db
from game.data.building_data import FORTIFICATION_BUILDINGS 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 import ControlPoint, TheaterGroundObject
from game.theater.theatergroundobject import ( from game.theater.theatergroundobject import (
VehicleGroupGroundObject, VehicleGroupGroundObject,
@ -108,17 +109,18 @@ class QGroundObjectMenu(QDialog):
for g in self.ground_object.groups: for g in self.ground_object.groups:
if not hasattr(g, "units_losts"): if not hasattr(g, "units_losts"):
g.units_losts = [] g.units_losts = []
for u in g.units: for unit in g.units:
unit_display_name = u.type unit_display_name = unit.type
unit_type = vehicles.vehicle_map.get(u.type) dcs_unit_type = vehicles.vehicle_map.get(unit.type)
if unit_type is not None: if dcs_unit_type is not None:
unit_display_name = db.unit_get_expanded_info( # Hack: Don't know which variant is used.
self.game.enemy_country, unit_type, "name" unit_display_name = next(
) GroundUnitType.for_dcs_type(dcs_unit_type)
).name
self.intelLayout.addWidget( self.intelLayout.addWidget(
QLabel( QLabel(
"<b>Unit #" "<b>Unit #"
+ str(u.id) + str(unit.id)
+ " - " + " - "
+ str(unit_display_name) + str(unit_display_name)
+ "</b>" + "</b>"
@ -128,26 +130,30 @@ class QGroundObjectMenu(QDialog):
) )
i = i + 1 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) # Hack: Don't know which variant is used.
if utype in PRICES: unit_type = next(GroundUnitType.for_dcs_type(dcs_unit_type))
price = PRICES[utype]
else:
price = 6
self.intelLayout.addWidget( self.intelLayout.addWidget(
QLabel( QLabel(
"<b>Unit #" + str(u.id) + " - " + str(u.type) + "</b> [DEAD]" "<b>Unit #"
+ str(unit.id)
+ " - "
+ str(unit_type)
+ "</b> [DEAD]"
), ),
i, i,
0, 0,
) )
if self.cp.captured: if self.cp.captured:
repair = QPushButton("Repair [" + str(price) + "M]") repair = QPushButton(f"Repair [{unit_type.price}M]")
repair.setProperty("style", "btn-success") repair.setProperty("style", "btn-success")
repair.clicked.connect( 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) self.intelLayout.addWidget(repair, i, 1)
i = i + 1 i = i + 1
@ -217,13 +223,12 @@ class QGroundObjectMenu(QDialog):
def update_total_value(self): def update_total_value(self):
total_value = 0 total_value = 0
for group in self.ground_object.groups: if not self.ground_object.purchasable:
for u in group.units: return
utype = unit_type_of(u) for u in self.ground_object.units:
if utype in PRICES: # Hack: Unknown variant.
total_value = total_value + PRICES[utype] unit_type = next(GroundUnitType.for_dcs_type(vehicles.vehicle_map[u.type]))
else: total_value += unit_type.price
total_value = total_value + 1
if self.sell_all_button is not None: if self.sell_all_button is not None:
self.sell_all_button.setText("Disband (+$" + str(self.total_value) + "M)") self.sell_all_button.setText("Disband (+$" + str(self.total_value) + "M)")
self.total_value = total_value self.total_value = total_value
@ -340,10 +345,7 @@ class QBuyGroupForGroundObjectDialog(QDialog):
# Armored units # Armored units
for unit in set(faction.ground_units): for unit in set(faction.ground_units):
self.buyArmorCombo.addItem( self.buyArmorCombo.addItem(f"{unit} [${unit.price}M]", userData=unit)
db.unit_type_name_2(unit) + " [$" + str(db.PRICES[unit]) + "M]",
userData=unit,
)
self.buyArmorCombo.currentIndexChanged.connect(self.armorComboChanged) self.buyArmorCombo.currentIndexChanged.connect(self.armorComboChanged)
self.amount.setMinimum(2) self.amount.setMinimum(2)
@ -404,33 +406,19 @@ class QBuyGroupForGroundObjectDialog(QDialog):
) )
def armorComboChanged(self, index): def armorComboChanged(self, index):
self.buyArmorButton.setText( unit_type = self.buyArmorCombo.itemData(self.buyArmorCombo.currentIndex())
"Buy [$" price = unit_type.price * self.amount.value()
+ str(db.PRICES[self.buyArmorCombo.itemData(index)] * self.amount.value()) self.buyArmorButton.setText(f"Buy [${price}M][-${self.current_group_value}M]")
+ "M][-$"
+ str(self.current_group_value)
+ "M]"
)
def amountComboChanged(self): def amountComboChanged(self):
self.buyArmorButton.setText( unit_type = self.buyArmorCombo.itemData(self.buyArmorCombo.currentIndex())
"Buy [$" price = unit_type.price * self.amount.value()
+ str( self.buyArmorButton.setText(f"Buy [${price}M][-${self.current_group_value}M]")
db.PRICES[
self.buyArmorCombo.itemData(self.buyArmorCombo.currentIndex())
]
* self.amount.value()
)
+ "M][-$"
+ str(self.current_group_value)
+ "M]"
)
def buyArmor(self): def buyArmor(self):
logging.info("Buying Armor ") logging.info("Buying Armor ")
utype = self.buyArmorCombo.itemData(self.buyArmorCombo.currentIndex()) utype = self.buyArmorCombo.itemData(self.buyArmorCombo.currentIndex())
logging.info(utype) price = utype.price * self.amount.value() - self.current_group_value
price = db.PRICES[utype] * self.amount.value() - self.current_group_value
if price > self.game.budget: if price > self.game.budget:
self.error_money() self.error_money()
self.close() self.close()

View File

@ -15,7 +15,7 @@ from PySide2.QtWidgets import (
QWidget, QWidget,
) )
from game.game import Game, db from game.game import Game
from qt_ui.uiconstants import ICONS from qt_ui.uiconstants import ICONS
from qt_ui.windows.finances.QFinancesMenu import FinancesLayout from qt_ui.windows.finances.QFinancesMenu import FinancesLayout
@ -111,7 +111,7 @@ class ArmyIntelLayout(IntelTableLayout):
for vehicle, count in base.armor.items(): for vehicle, count in base.armor.items():
if not count: if not count:
continue continue
self.add_row(vehicle.id, count) self.add_row(vehicle.name, count)
self.add_spacer() self.add_spacer()
self.add_row("<b>Total</b>", total) self.add_row("<b>Total</b>", total)