mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Add a wrapper type for ground unit info.
This commit is contained in:
parent
8a0824880e
commit
09704b6f37
@ -1,239 +1,17 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import unique, Enum
|
||||
from typing import Type
|
||||
|
||||
from dcs.vehicles import AirDefence, Infantry, Unarmed, Artillery, Armor
|
||||
from dcs.unittype import VehicleType
|
||||
|
||||
from pydcs_extensions.frenchpack import frenchpack
|
||||
|
||||
|
||||
@unique
|
||||
class GroundUnitClass(Enum):
|
||||
Tank = (
|
||||
"Tank",
|
||||
(
|
||||
Armor.MBT_T_55,
|
||||
Armor.MBT_T_72B,
|
||||
Armor.MBT_T_72B3,
|
||||
Armor.MBT_T_80U,
|
||||
Armor.MBT_T_90,
|
||||
Armor.MBT_Leopard_2A4,
|
||||
Armor.MBT_Leopard_2A4_Trs,
|
||||
Armor.MBT_Leopard_2A5,
|
||||
Armor.MBT_Leopard_2A6M,
|
||||
Armor.MBT_Leopard_1A3,
|
||||
Armor.MBT_Leclerc,
|
||||
Armor.MBT_Challenger_II,
|
||||
Armor.MBT_Chieftain_Mk_3,
|
||||
Armor.MBT_M1A2_Abrams,
|
||||
Armor.MBT_M60A3_Patton,
|
||||
Armor.MBT_Merkava_IV,
|
||||
Armor.ZTZ_96B,
|
||||
# WW2
|
||||
# Axis
|
||||
Armor.Tk_PzIV_H,
|
||||
Armor.SPG_Sturmpanzer_IV_Brummbar,
|
||||
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G,
|
||||
Armor.HT_Pz_Kpfw_VI_Tiger_I,
|
||||
Armor.HT_Pz_Kpfw_VI_Ausf__B_Tiger_II,
|
||||
# Allies
|
||||
Armor.Tk_M4_Sherman,
|
||||
Armor.CT_Centaur_IV,
|
||||
Armor.CT_Cromwell_IV,
|
||||
Armor.HIT_Churchill_VII,
|
||||
# Mods
|
||||
frenchpack.DIM__TOYOTA_BLUE,
|
||||
frenchpack.DIM__TOYOTA_GREEN,
|
||||
frenchpack.DIM__TOYOTA_DESERT,
|
||||
frenchpack.DIM__KAMIKAZE,
|
||||
frenchpack.AMX_30B2,
|
||||
frenchpack.Leclerc_Serie_XXI,
|
||||
),
|
||||
)
|
||||
|
||||
Atgm = (
|
||||
"ATGM",
|
||||
(
|
||||
Armor.ATGM_HMMWV,
|
||||
Armor.ATGM_VAB_Mephisto,
|
||||
Armor.ATGM_Stryker,
|
||||
Armor.IFV_BMP_2,
|
||||
# WW2 (Tank Destroyers)
|
||||
# Axxis
|
||||
Armor.SPG_StuG_III_Ausf__G,
|
||||
Armor.SPG_StuG_IV,
|
||||
Armor.SPG_Jagdpanzer_IV,
|
||||
Armor.SPG_Jagdpanther_G1,
|
||||
Armor.SPG_Sd_Kfz_184_Elefant,
|
||||
# Allies
|
||||
Armor.SPG_M10_GMC,
|
||||
Armor.MT_M4A4_Sherman_Firefly,
|
||||
# Mods
|
||||
frenchpack.VBAE_CRAB_MMP,
|
||||
frenchpack.VAB_MEPHISTO,
|
||||
frenchpack.TRM_2000_PAMELA,
|
||||
),
|
||||
)
|
||||
|
||||
Ifv = (
|
||||
"IFV",
|
||||
(
|
||||
Armor.IFV_BMP_3,
|
||||
Armor.IFV_BMP_2,
|
||||
Armor.IFV_BMP_1,
|
||||
Armor.IFV_Marder,
|
||||
Armor.IFV_Warrior,
|
||||
Armor.SPG_Stryker_MGS,
|
||||
Armor.IFV_M2A2_Bradley,
|
||||
Armor.IFV_BMD_1,
|
||||
Armor.ZBD_04A,
|
||||
# Mods
|
||||
frenchpack.VBAE_CRAB,
|
||||
frenchpack.VAB_T20_13,
|
||||
),
|
||||
)
|
||||
|
||||
Apc = (
|
||||
"APC",
|
||||
(
|
||||
Armor.IFV_M1126_Stryker_ICV,
|
||||
Armor.APC_M113,
|
||||
Armor.APC_BTR_80,
|
||||
Armor.IFV_BTR_82A,
|
||||
Armor.APC_MTLB,
|
||||
Armor.APC_AAV_7_Amphibious,
|
||||
Armor.APC_TPz_Fuchs,
|
||||
Armor.APC_BTR_RD,
|
||||
# WW2
|
||||
Armor.APC_M2A1_Halftrack,
|
||||
Armor.APC_Sd_Kfz_251_Halftrack,
|
||||
# Mods
|
||||
frenchpack.VAB__50,
|
||||
frenchpack.VBL__50,
|
||||
frenchpack.VBL_AANF1,
|
||||
),
|
||||
)
|
||||
|
||||
Artillery = (
|
||||
"Artillery",
|
||||
(
|
||||
Artillery.Grad_MRL_FDDM__FC,
|
||||
Artillery.MLRS_9A52_Smerch_HE_300mm,
|
||||
Artillery.SPH_2S1_Gvozdika_122mm,
|
||||
Artillery.SPH_2S3_Akatsia_152mm,
|
||||
Artillery.MLRS_BM_21_Grad_122mm,
|
||||
Artillery.MLRS_9K57_Uragan_BM_27_220mm,
|
||||
Artillery.SPH_M109_Paladin_155mm,
|
||||
Artillery.MLRS_M270_227mm,
|
||||
Artillery.SPM_2S9_Nona_120mm_M,
|
||||
Artillery.SPH_Dana_vz77_152mm,
|
||||
Artillery.SPH_T155_Firtina_155mm,
|
||||
Artillery.PLZ_05,
|
||||
Artillery.SPH_2S19_Msta_152mm,
|
||||
Artillery.MLRS_9A52_Smerch_CM_300mm,
|
||||
# WW2
|
||||
Artillery.SPG_M12_GMC_155mm,
|
||||
),
|
||||
)
|
||||
|
||||
Logistics = (
|
||||
"Logistics",
|
||||
(
|
||||
Unarmed.Carrier_M30_Cargo,
|
||||
Unarmed.Truck_M818_6x6,
|
||||
Unarmed.Truck_KAMAZ_43101,
|
||||
Unarmed.Truck_Ural_375,
|
||||
Unarmed.Truck_GAZ_66,
|
||||
Unarmed.Truck_GAZ_3307,
|
||||
Unarmed.Truck_GAZ_3308,
|
||||
Unarmed.Truck_Ural_4320_31_Arm_d,
|
||||
Unarmed.Truck_Ural_4320T,
|
||||
Unarmed.Truck_Opel_Blitz,
|
||||
Unarmed.LUV_Kubelwagen_82,
|
||||
Unarmed.Carrier_Sd_Kfz_7_Tractor,
|
||||
Unarmed.LUV_Kettenrad,
|
||||
Unarmed.Car_Willys_Jeep,
|
||||
Unarmed.LUV_Land_Rover_109,
|
||||
Unarmed.Truck_Land_Rover_101_FC,
|
||||
# Mods
|
||||
frenchpack.VBL,
|
||||
frenchpack.VAB,
|
||||
),
|
||||
)
|
||||
|
||||
Recon = (
|
||||
"Recon",
|
||||
(
|
||||
Armor.Scout_HMMWV,
|
||||
Armor.Scout_Cobra,
|
||||
Armor.LT_PT_76,
|
||||
Armor.IFV_LAV_25,
|
||||
Armor.Scout_BRDM_2,
|
||||
# WW2
|
||||
Armor.LT_Mk_VII_Tetrarch,
|
||||
Armor.IFV_Sd_Kfz_234_2_Puma,
|
||||
Armor.Car_M8_Greyhound_Armored,
|
||||
Armor.Car_Daimler_Armored,
|
||||
# Mods
|
||||
frenchpack.ERC_90,
|
||||
frenchpack.AMX_10RCR,
|
||||
frenchpack.AMX_10RCR_SEPAR,
|
||||
),
|
||||
)
|
||||
|
||||
Infantry = (
|
||||
"Infantry",
|
||||
(
|
||||
Infantry.Insurgent_AK_74,
|
||||
Infantry.Infantry_AK_74,
|
||||
Infantry.Infantry_M1_Garand,
|
||||
Infantry.Infantry_Mauser_98,
|
||||
Infantry.Infantry_SMLE_No_4_Mk_1,
|
||||
Infantry.Infantry_M4_Georgia,
|
||||
Infantry.Infantry_AK_74_Rus,
|
||||
Infantry.Paratrooper_AKS,
|
||||
Infantry.Paratrooper_RPG_16,
|
||||
Infantry.Infantry_M249,
|
||||
Infantry.Infantry_M4,
|
||||
Infantry.Infantry_RPG,
|
||||
),
|
||||
)
|
||||
|
||||
Shorads = (
|
||||
"SHORADS",
|
||||
(
|
||||
AirDefence.SPAAA_ZU_23_2_Mounted_Ural_375,
|
||||
AirDefence.SPAAA_ZU_23_2_Insurgent_Mounted_Ural_375,
|
||||
AirDefence.SPAAA_ZSU_57_2,
|
||||
AirDefence.SPAAA_ZSU_23_4_Shilka_Gun_Dish,
|
||||
AirDefence.SAM_SA_8_Osa_Gecko_TEL,
|
||||
AirDefence.SAM_SA_9_Strela_1_Gaskin_TEL,
|
||||
AirDefence.SAM_SA_13_Strela_10M3_Gopher_TEL,
|
||||
AirDefence.SAM_SA_15_Tor_Gauntlet,
|
||||
AirDefence.SAM_SA_19_Tunguska_Grison,
|
||||
AirDefence.SPAAA_Gepard,
|
||||
AirDefence.SPAAA_Vulcan_M163,
|
||||
AirDefence.SAM_Linebacker___Bradley_M6,
|
||||
AirDefence.SAM_Chaparral_M48,
|
||||
AirDefence.SAM_Avenger__Stinger,
|
||||
AirDefence.SAM_Roland_ADS,
|
||||
AirDefence.HQ_7_Self_Propelled_LN,
|
||||
AirDefence.AAA_8_8cm_Flak_18,
|
||||
AirDefence.AAA_8_8cm_Flak_36,
|
||||
AirDefence.AAA_8_8cm_Flak_37,
|
||||
AirDefence.AAA_8_8cm_Flak_41,
|
||||
AirDefence.AAA_Bofors_40mm,
|
||||
AirDefence.AAA_S_60_57mm,
|
||||
AirDefence.AAA_M1_37mm,
|
||||
AirDefence.AAA_QF_3_7,
|
||||
),
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self, class_name: str, unit_list: tuple[Type[VehicleType], ...]
|
||||
) -> None:
|
||||
self.class_name = class_name
|
||||
self.unit_list = unit_list
|
||||
|
||||
def __contains__(self, unit_type: Type[VehicleType]) -> bool:
|
||||
return unit_type in self.unit_list
|
||||
Tank = "Tank"
|
||||
Atgm = "ATGM"
|
||||
Ifv = "IFV"
|
||||
Apc = "APC"
|
||||
Artillery = "Artillery"
|
||||
Logistics = "Logistics"
|
||||
Recon = "Recon"
|
||||
Infantry = "Infantry"
|
||||
Shorads = "SHORADS"
|
||||
Manpads = "MANPADS"
|
||||
|
||||
391
game/db.py
391
game/db.py
@ -1,8 +1,7 @@
|
||||
import json
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Type, Union
|
||||
from typing import Optional, Type, Union
|
||||
|
||||
from dcs.countries import country_dict
|
||||
from dcs.helicopters import (
|
||||
@ -21,8 +20,6 @@ from dcs.planes import (
|
||||
plane_map,
|
||||
)
|
||||
from dcs.ships import (
|
||||
Boat_Armed_Hi_speed,
|
||||
Bulker_Yakushev,
|
||||
CVN_71_Theodore_Roosevelt,
|
||||
CVN_72_Abraham_Lincoln,
|
||||
CVN_73_George_Washington,
|
||||
@ -30,21 +27,12 @@ from dcs.ships import (
|
||||
CVN_75_Harry_S__Truman,
|
||||
CV_1143_5_Admiral_Kuznetsov,
|
||||
CV_1143_5_Admiral_Kuznetsov_2017,
|
||||
Cargo_Ivanov,
|
||||
LHA_1_Tarawa,
|
||||
Tanker_Elnya_160,
|
||||
ship_map,
|
||||
)
|
||||
from dcs.terrain.terrain import Airport
|
||||
from dcs.unit import Ship, Unit, Vehicle
|
||||
from dcs.unitgroup import ShipGroup, StaticGroup
|
||||
from dcs.unittype import UnitType, VehicleType
|
||||
from dcs.unittype import UnitType
|
||||
from dcs.vehicles import (
|
||||
AirDefence,
|
||||
Armor,
|
||||
Artillery,
|
||||
Infantry,
|
||||
Unarmed,
|
||||
vehicle_map,
|
||||
)
|
||||
|
||||
@ -250,271 +238,6 @@ For example, player accessible Hornet is called `FA_18C_hornet`, and MANPAD Igla
|
||||
# to be cheap enough to repair with a single turn's income.
|
||||
RUNWAY_REPAIR_COST = 100
|
||||
|
||||
"""
|
||||
Prices for the aircraft.
|
||||
This defines both price for the player (although only aircraft listed in CAP/CAS/Transport/Armor/AirDefense roles will be purchasable)
|
||||
and prioritization for the enemy (i.e. less important bases will receive units with lower price)
|
||||
"""
|
||||
PRICES = {
|
||||
# armor
|
||||
Armor.APC_MTLB: 4,
|
||||
Artillery.Grad_MRL_FDDM__FC: 4,
|
||||
Armor.Scout_BRDM_2: 6,
|
||||
Armor.APC_BTR_RD: 6,
|
||||
Armor.APC_BTR_80: 8,
|
||||
Armor.IFV_BTR_82A: 10,
|
||||
Armor.MBT_T_55: 18,
|
||||
Armor.MBT_T_72B: 20,
|
||||
Armor.MBT_T_72B3: 25,
|
||||
Armor.MBT_T_80U: 25,
|
||||
Armor.MBT_T_90: 30,
|
||||
Armor.IFV_BMD_1: 8,
|
||||
Armor.IFV_BMP_1: 14,
|
||||
Armor.IFV_BMP_2: 16,
|
||||
Armor.IFV_BMP_3: 18,
|
||||
Armor.LT_PT_76: 9,
|
||||
Armor.ZBD_04A: 12,
|
||||
Armor.ZTZ_96B: 30,
|
||||
Armor.Scout_Cobra: 4,
|
||||
Armor.APC_M113: 6,
|
||||
Armor.Scout_HMMWV: 2,
|
||||
Armor.ATGM_HMMWV: 8,
|
||||
Armor.ATGM_VAB_Mephisto: 12,
|
||||
Armor.IFV_M2A2_Bradley: 12,
|
||||
Armor.IFV_M1126_Stryker_ICV: 10,
|
||||
Armor.SPG_Stryker_MGS: 14,
|
||||
Armor.ATGM_Stryker: 12,
|
||||
Armor.MBT_M60A3_Patton: 16,
|
||||
Armor.MBT_M1A2_Abrams: 25,
|
||||
Armor.MBT_Leclerc: 25,
|
||||
Armor.MBT_Leopard_1A3: 18,
|
||||
Armor.MBT_Leopard_2A4: 20,
|
||||
Armor.MBT_Leopard_2A4_Trs: 20,
|
||||
Armor.MBT_Leopard_2A5: 22,
|
||||
Armor.MBT_Leopard_2A6M: 25,
|
||||
Armor.MBT_Merkava_IV: 25,
|
||||
Armor.APC_TPz_Fuchs: 5,
|
||||
Armor.MBT_Challenger_II: 25,
|
||||
Armor.MBT_Chieftain_Mk_3: 20,
|
||||
Armor.IFV_Marder: 10,
|
||||
Armor.IFV_Warrior: 10,
|
||||
Armor.IFV_LAV_25: 7,
|
||||
Armor.APC_AAV_7_Amphibious: 10,
|
||||
Artillery.MLRS_M270_227mm: 55,
|
||||
Artillery.SPH_M109_Paladin_155mm: 25,
|
||||
Artillery.SPM_2S9_Nona_120mm_M: 12,
|
||||
Artillery.SPH_2S1_Gvozdika_122mm: 18,
|
||||
Artillery.SPH_2S3_Akatsia_152mm: 24,
|
||||
Artillery.SPH_2S19_Msta_152mm: 30,
|
||||
Artillery.MLRS_BM_21_Grad_122mm: 15,
|
||||
Artillery.MLRS_9K57_Uragan_BM_27_220mm: 50,
|
||||
Artillery.MLRS_9A52_Smerch_HE_300mm: 40,
|
||||
Artillery.Mortar_2B11_120mm: 4,
|
||||
Artillery.SPH_Dana_vz77_152mm: 26,
|
||||
Artillery.PLZ_05: 25,
|
||||
Artillery.SPH_T155_Firtina_155mm: 28,
|
||||
Artillery.MLRS_9A52_Smerch_CM_300mm: 60,
|
||||
Unarmed.LUV_UAZ_469_Jeep: 3,
|
||||
Unarmed.Truck_Ural_375: 3,
|
||||
Unarmed.Truck_GAZ_3307: 2,
|
||||
Infantry.Infantry_M4: 1,
|
||||
Infantry.Infantry_AK_74: 1,
|
||||
Unarmed.Truck_M818_6x6: 3,
|
||||
Unarmed.LUV_Land_Rover_109: 1,
|
||||
Unarmed.Truck_GAZ_3308: 1,
|
||||
Unarmed.Truck_GAZ_66: 1,
|
||||
Unarmed.Truck_KAMAZ_43101: 1,
|
||||
Unarmed.Truck_Land_Rover_101_FC: 1,
|
||||
Unarmed.Truck_Ural_4320_31_Arm_d: 1,
|
||||
Unarmed.Truck_Ural_4320T: 1,
|
||||
# WW2
|
||||
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G: 24,
|
||||
Armor.Tk_PzIV_H: 16,
|
||||
Armor.HT_Pz_Kpfw_VI_Tiger_I: 24,
|
||||
Armor.HT_Pz_Kpfw_VI_Ausf__B_Tiger_II: 26,
|
||||
Armor.SPG_Jagdpanther_G1: 18,
|
||||
Armor.SPG_Jagdpanzer_IV: 11,
|
||||
Armor.SPG_Sd_Kfz_184_Elefant: 18,
|
||||
Armor.APC_Sd_Kfz_251_Halftrack: 4,
|
||||
Armor.IFV_Sd_Kfz_234_2_Puma: 8,
|
||||
Armor.Tk_M4_Sherman: 12,
|
||||
Armor.MT_M4A4_Sherman_Firefly: 16,
|
||||
Armor.CT_Cromwell_IV: 12,
|
||||
Unarmed.Carrier_M30_Cargo: 2,
|
||||
Armor.APC_M2A1_Halftrack: 4,
|
||||
Armor.CT_Centaur_IV: 10,
|
||||
Armor.HIT_Churchill_VII: 16,
|
||||
Armor.Car_M8_Greyhound_Armored: 8,
|
||||
Armor.SPG_M10_GMC: 14,
|
||||
Armor.SPG_StuG_III_Ausf__G: 12,
|
||||
Armor.SPG_StuG_IV: 14,
|
||||
Artillery.SPG_M12_GMC_155mm: 10,
|
||||
Armor.SPG_Sturmpanzer_IV_Brummbar: 10,
|
||||
Armor.Car_Daimler_Armored: 8,
|
||||
Armor.LT_Mk_VII_Tetrarch: 8,
|
||||
Unarmed.Tractor_M4_Hi_Speed: 2,
|
||||
Unarmed.Carrier_Sd_Kfz_7_Tractor: 1,
|
||||
Unarmed.LUV_Kettenrad: 1,
|
||||
Unarmed.LUV_Kubelwagen_82: 1,
|
||||
Unarmed.Truck_Opel_Blitz: 1,
|
||||
Unarmed.Truck_Bedford: 1,
|
||||
Unarmed.Truck_GMC_Jimmy_6x6_Truck: 1,
|
||||
Unarmed.Car_Willys_Jeep: 1,
|
||||
# ship
|
||||
CV_1143_5_Admiral_Kuznetsov: 100,
|
||||
CVN_74_John_C__Stennis: 100,
|
||||
LHA_1_Tarawa: 50,
|
||||
Bulker_Yakushev: 10,
|
||||
Boat_Armed_Hi_speed: 10,
|
||||
Cargo_Ivanov: 10,
|
||||
Tanker_Elnya_160: 10,
|
||||
# Air Defence units
|
||||
AirDefence.SAM_SA_19_Tunguska_Grison: 30,
|
||||
AirDefence.SAM_SA_6_Kub_Gainful_TEL: 20,
|
||||
AirDefence.SAM_SA_3_S_125_Goa_LN: 6,
|
||||
AirDefence.SAM_SA_11_Buk_Gadfly_Fire_Dome_TEL: 30,
|
||||
AirDefence.SAM_SA_11_Buk_Gadfly_C2: 25,
|
||||
AirDefence.SAM_SA_11_Buk_Gadfly_Snow_Drift_SR: 28,
|
||||
AirDefence.SAM_SA_8_Osa_Gecko_TEL: 28,
|
||||
AirDefence.SAM_SA_15_Tor_Gauntlet: 40,
|
||||
AirDefence.SAM_SA_13_Strela_10M3_Gopher_TEL: 16,
|
||||
AirDefence.SAM_SA_9_Strela_1_Gaskin_TEL: 12,
|
||||
AirDefence.SAM_SA_8_Osa_LD_9T217: 22,
|
||||
AirDefence.SAM_Patriot_CR__AMG_AN_MRC_137: 35,
|
||||
AirDefence.SAM_Patriot_ECS: 30,
|
||||
AirDefence.SPAAA_Gepard: 24,
|
||||
AirDefence.SAM_Hawk_Platoon_Command_Post__PCP: 14,
|
||||
AirDefence.SPAAA_Vulcan_M163: 10,
|
||||
AirDefence.SAM_Hawk_LN_M192: 8,
|
||||
AirDefence.SAM_Chaparral_M48: 16,
|
||||
AirDefence.SAM_Linebacker___Bradley_M6: 18,
|
||||
AirDefence.SAM_Patriot_LN: 15,
|
||||
AirDefence.SAM_Avenger__Stinger: 20,
|
||||
AirDefence.SAM_Patriot_EPP_III: 15,
|
||||
AirDefence.SAM_Patriot_C2_ICC: 18,
|
||||
AirDefence.SAM_Roland_ADS: 12,
|
||||
AirDefence.MANPADS_Stinger: 6,
|
||||
AirDefence.MANPADS_Stinger_C2_Desert: 4,
|
||||
AirDefence.MANPADS_Stinger_C2: 4,
|
||||
AirDefence.SPAAA_ZSU_23_4_Shilka_Gun_Dish: 10,
|
||||
AirDefence.SPAAA_ZSU_57_2: 12,
|
||||
AirDefence.AAA_ZU_23_Closed_Emplacement: 6,
|
||||
AirDefence.AAA_ZU_23_Emplacement: 6,
|
||||
AirDefence.SPAAA_ZU_23_2_Mounted_Ural_375: 7,
|
||||
AirDefence.AAA_ZU_23_Insurgent_Closed_Emplacement: 6,
|
||||
AirDefence.SPAAA_ZU_23_2_Insurgent_Mounted_Ural_375: 7,
|
||||
AirDefence.AAA_ZU_23_Insurgent_Emplacement: 6,
|
||||
AirDefence.MANPADS_SA_18_Igla_Grouse: 10,
|
||||
AirDefence.MANPADS_SA_18_Igla_Grouse_C2: 8,
|
||||
AirDefence.MANPADS_SA_18_Igla_S_Grouse: 12,
|
||||
AirDefence.MANPADS_SA_18_Igla_S_Grouse_C2: 8,
|
||||
AirDefence.EWR_1L13: 30,
|
||||
AirDefence.SAM_SA_6_Kub_Straight_Flush_STR: 22,
|
||||
AirDefence.EWR_55G6: 30,
|
||||
AirDefence.MCC_SR_Sborka_Dog_Ear_SR: 10,
|
||||
AirDefence.SAM_Hawk_TR__AN_MPQ_46: 14,
|
||||
AirDefence.SAM_Hawk_SR__AN_MPQ_50: 18,
|
||||
AirDefence.SAM_Patriot_STR: 22,
|
||||
AirDefence.SAM_Hawk_CWAR_AN_MPQ_55: 20,
|
||||
AirDefence.SAM_P19_Flat_Face_SR__SA_2_3: 14,
|
||||
AirDefence.SAM_Roland_EWR: 16,
|
||||
AirDefence.SAM_SA_3_S_125_Low_Blow_TR: 14,
|
||||
AirDefence.SAM_SA_2_S_75_Guideline_LN: 8,
|
||||
AirDefence.SAM_SA_2_S_75_Fan_Song_TR: 12,
|
||||
AirDefence.SAM_Rapier_LN: 6,
|
||||
AirDefence.SAM_Rapier_Tracker: 6,
|
||||
AirDefence.SAM_Rapier_Blindfire_TR: 8,
|
||||
AirDefence.HQ_7_Self_Propelled_LN: 20,
|
||||
AirDefence.HQ_7_Self_Propelled_STR: 24,
|
||||
AirDefence.AAA_8_8cm_Flak_18: 6,
|
||||
AirDefence.AAA_Flak_38_20mm: 6,
|
||||
AirDefence.AAA_8_8cm_Flak_36: 8,
|
||||
AirDefence.AAA_8_8cm_Flak_37: 9,
|
||||
AirDefence.AAA_Flak_Vierling_38_Quad_20mm: 5,
|
||||
AirDefence.AAA_SP_Kdo_G_40: 8,
|
||||
AirDefence.SL_Flakscheinwerfer_37: 4,
|
||||
AirDefence.PU_Maschinensatz_33: 10,
|
||||
AirDefence.AAA_8_8cm_Flak_41: 10,
|
||||
AirDefence.EWR_FuMG_401_Freya_LZ: 25,
|
||||
AirDefence.AAA_Bofors_40mm: 8,
|
||||
AirDefence.AAA_S_60_57mm: 8,
|
||||
AirDefence.AAA_M1_37mm: 7,
|
||||
AirDefence.AAA_M45_Quadmount_HB_12_7mm: 4,
|
||||
AirDefence.AAA_QF_3_7: 10,
|
||||
# FRENCH PACK MOD
|
||||
frenchpack.AMX_10RCR: 10,
|
||||
frenchpack.AMX_10RCR_SEPAR: 12,
|
||||
frenchpack.ERC_90: 12,
|
||||
frenchpack.MO_120_RT: 10,
|
||||
frenchpack._53T2: 4,
|
||||
frenchpack.TRM_2000: 4,
|
||||
frenchpack.TRM_2000_Fuel: 4,
|
||||
frenchpack.TRM_2000_53T2: 8,
|
||||
frenchpack.TRM_2000_PAMELA: 14,
|
||||
frenchpack.VAB_MEDICAL: 8,
|
||||
frenchpack.VAB: 6,
|
||||
frenchpack.VAB__50: 4,
|
||||
frenchpack.VAB_T20_13: 6,
|
||||
frenchpack.VAB_MEPHISTO: 8,
|
||||
frenchpack.VAB_MORTIER: 10,
|
||||
frenchpack.VBL__50: 4,
|
||||
frenchpack.VBL_AANF1: 2,
|
||||
frenchpack.VBL: 1,
|
||||
frenchpack.VBAE_CRAB: 8,
|
||||
frenchpack.VBAE_CRAB_MMP: 12,
|
||||
frenchpack.AMX_30B2: 18,
|
||||
frenchpack.Tracma_TD_1500: 2,
|
||||
frenchpack.Infantry_Soldier_JTAC: 1,
|
||||
frenchpack.Leclerc_Serie_XXI: 35,
|
||||
frenchpack.DIM__TOYOTA_BLUE: 2,
|
||||
frenchpack.DIM__TOYOTA_GREEN: 2,
|
||||
frenchpack.DIM__TOYOTA_DESERT: 2,
|
||||
frenchpack.DIM__KAMIKAZE: 6,
|
||||
# SA-10
|
||||
AirDefence.SAM_SA_10_S_300_Grumble_C2: 18,
|
||||
AirDefence.SAM_SA_10_S_300_Grumble_Flap_Lid_TR: 24,
|
||||
AirDefence.SAM_SA_10_S_300_Grumble_Clam_Shell_SR: 30,
|
||||
AirDefence.SAM_SA_10_S_300_Grumble_Big_Bird_SR: 30,
|
||||
AirDefence.SAM_SA_10_S_300_Grumble_TEL_C: 22,
|
||||
AirDefence.SAM_SA_10_S_300_Grumble_TEL_D: 22,
|
||||
# High digit sams mod
|
||||
highdigitsams.AAA_SON_9_Fire_Can: 8,
|
||||
highdigitsams.AAA_100mm_KS_19: 10,
|
||||
highdigitsams.SAM_SA_10B_S_300PS_54K6_CP: 20,
|
||||
highdigitsams.SAM_SA_10B_S_300PS_5P85SE_LN: 24,
|
||||
highdigitsams.SAM_SA_10B_S_300PS_5P85SU_LN: 24,
|
||||
highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85CE: 24,
|
||||
highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85DE: 24,
|
||||
highdigitsams.SAM_SA_10B_S_300PS_30N6_TR: 26,
|
||||
highdigitsams.SAM_SA_10B_S_300PS_40B6M_TR: 26,
|
||||
highdigitsams.SAM_SA_10B_S_300PS_40B6MD_SR: 32,
|
||||
highdigitsams.SAM_SA_10B_S_300PS_64H6E_SR: 32,
|
||||
highdigitsams.SAM_SA_12_S_300V_9S457_CP: 22,
|
||||
highdigitsams.SAM_SA_12_S_300V_9A82_LN: 26,
|
||||
highdigitsams.SAM_SA_12_S_300V_9A83_LN: 26,
|
||||
highdigitsams.SAM_SA_12_S_300V_9S15_SR: 34,
|
||||
highdigitsams.SAM_SA_12_S_300V_9S19_SR: 34,
|
||||
highdigitsams.SAM_SA_12_S_300V_9S32_TR: 28,
|
||||
highdigitsams.SAM_SA_20_S_300PMU1_CP_54K6: 26,
|
||||
highdigitsams.SAM_SA_20_S_300PMU1_TR_30N6E: 30,
|
||||
highdigitsams.SAM_SA_20_S_300PMU1_TR_30N6E_truck: 32,
|
||||
highdigitsams.SAM_SA_20_S_300PMU1_SR_5N66E: 38,
|
||||
highdigitsams.SAM_SA_20_S_300PMU1_SR_64N6E: 38,
|
||||
highdigitsams.SAM_SA_20_S_300PMU1_LN_5P85CE: 28,
|
||||
highdigitsams.SAM_SA_20_S_300PMU1_LN_5P85DE: 28,
|
||||
highdigitsams.SAM_SA_20B_S_300PMU2_CP_54K6E2: 27,
|
||||
highdigitsams.SAM_SA_20B_S_300PMU2_TR_92H6E_truck: 33,
|
||||
highdigitsams.SAM_SA_20B_S_300PMU2_SR_64N6E2: 40,
|
||||
highdigitsams.SAM_SA_20B_S_300PMU2_LN_5P85SE2: 30,
|
||||
highdigitsams.SAM_SA_23_S_300VM_9S457ME_CP: 30,
|
||||
highdigitsams.SAM_SA_23_S_300VM_9S15M2_SR: 45,
|
||||
highdigitsams.SAM_SA_23_S_300VM_9S19M2_SR: 45,
|
||||
highdigitsams.SAM_SA_23_S_300VM_9S32ME_TR: 35,
|
||||
highdigitsams.SAM_SA_23_S_300VM_9A83ME_LN: 32,
|
||||
highdigitsams.SAM_SA_23_S_300VM_9A82ME_LN: 32,
|
||||
highdigitsams.SAM_SA_17_Buk_M1_2_LN_9A310M1_2: 40,
|
||||
}
|
||||
|
||||
"""
|
||||
Units separated by country.
|
||||
country : DCS Country name
|
||||
@ -620,107 +343,6 @@ def upgrade_to_supercarrier(unit, name: str):
|
||||
return unit
|
||||
|
||||
|
||||
MANPADS: List[Type[VehicleType]] = [
|
||||
AirDefence.MANPADS_SA_18_Igla_Grouse,
|
||||
AirDefence.MANPADS_SA_18_Igla_S_Grouse,
|
||||
AirDefence.MANPADS_Stinger,
|
||||
]
|
||||
|
||||
INFANTRY: List[VehicleType] = [
|
||||
Infantry.Paratrooper_AKS,
|
||||
Infantry.Paratrooper_AKS,
|
||||
Infantry.Paratrooper_AKS,
|
||||
Infantry.Paratrooper_AKS,
|
||||
Infantry.Paratrooper_AKS,
|
||||
Infantry.Infantry_RPG,
|
||||
Infantry.Infantry_M4,
|
||||
Infantry.Infantry_M4,
|
||||
Infantry.Infantry_M4,
|
||||
Infantry.Infantry_M4,
|
||||
Infantry.Infantry_M4,
|
||||
Infantry.Infantry_M249,
|
||||
Artillery.Mortar_2B11_120mm,
|
||||
Infantry.Infantry_AK_74,
|
||||
Infantry.Infantry_AK_74,
|
||||
Infantry.Infantry_AK_74,
|
||||
Infantry.Infantry_AK_74,
|
||||
Infantry.Infantry_AK_74,
|
||||
Infantry.Paratrooper_RPG_16,
|
||||
Infantry.Infantry_M4_Georgia,
|
||||
Infantry.Infantry_M4_Georgia,
|
||||
Infantry.Infantry_M4_Georgia,
|
||||
Infantry.Infantry_M4_Georgia,
|
||||
Infantry.Infantry_AK_74_Rus,
|
||||
Infantry.Infantry_AK_74_Rus,
|
||||
Infantry.Infantry_AK_74_Rus,
|
||||
Infantry.Infantry_AK_74_Rus,
|
||||
Infantry.Infantry_SMLE_No_4_Mk_1,
|
||||
Infantry.Infantry_SMLE_No_4_Mk_1,
|
||||
Infantry.Infantry_SMLE_No_4_Mk_1,
|
||||
Infantry.Infantry_Mauser_98,
|
||||
Infantry.Infantry_Mauser_98,
|
||||
Infantry.Infantry_Mauser_98,
|
||||
Infantry.Infantry_Mauser_98,
|
||||
Infantry.Infantry_M1_Garand,
|
||||
Infantry.Infantry_M1_Garand,
|
||||
Infantry.Infantry_M1_Garand,
|
||||
Infantry.Insurgent_AK_74,
|
||||
Infantry.Insurgent_AK_74,
|
||||
Infantry.Insurgent_AK_74,
|
||||
]
|
||||
|
||||
|
||||
def find_manpad(country_name: str) -> List[VehicleType]:
|
||||
return [x for x in MANPADS if x in FACTIONS[country_name].infantry_units]
|
||||
|
||||
|
||||
def find_infantry(country_name: str, allow_manpad: bool = False) -> List[VehicleType]:
|
||||
if allow_manpad:
|
||||
inf = INFANTRY + MANPADS
|
||||
else:
|
||||
inf = INFANTRY
|
||||
return [x for x in inf if x in FACTIONS[country_name].infantry_units]
|
||||
|
||||
|
||||
def unit_type_name(unit_type) -> str:
|
||||
return unit_type.id and unit_type.id or unit_type.name
|
||||
|
||||
|
||||
def unit_type_name_2(unit_type) -> str:
|
||||
return unit_type.name and unit_type.name or unit_type.id
|
||||
|
||||
|
||||
def unit_get_expanded_info(
|
||||
country_name: str, unit_type: Type[UnitType], request_type: str
|
||||
) -> str:
|
||||
original_name = unit_type.name and unit_type.name or unit_type.id
|
||||
default_value = None
|
||||
faction_value = None
|
||||
with UNITINFOTEXT_PATH.open("r", encoding="utf-8") as fdata:
|
||||
data = json.load(fdata)
|
||||
type_exists = data.get(unit_type.id)
|
||||
if type_exists is not None:
|
||||
for faction in type_exists:
|
||||
if default_value is None:
|
||||
default_exists = faction.get("default")
|
||||
if default_exists is not None:
|
||||
default_value = default_exists.get(request_type)
|
||||
if faction_value is None:
|
||||
faction_exists = faction.get(country_name)
|
||||
if faction_exists is not None:
|
||||
faction_value = faction_exists.get(request_type)
|
||||
if default_value is None:
|
||||
if request_type == "text":
|
||||
return "WIP - This unit doesn't have any description text yet."
|
||||
if request_type == "name":
|
||||
return original_name
|
||||
else:
|
||||
return "Unknown"
|
||||
if faction_value is None:
|
||||
return default_value
|
||||
return faction_value
|
||||
|
||||
|
||||
def unit_type_from_name(name: str) -> Optional[Type[UnitType]]:
|
||||
if name in vehicle_map:
|
||||
return vehicle_map[name]
|
||||
@ -734,15 +356,6 @@ def unit_type_from_name(name: str) -> Optional[Type[UnitType]]:
|
||||
return None
|
||||
|
||||
|
||||
def unit_type_of(unit: Unit) -> UnitType:
|
||||
if isinstance(unit, Vehicle):
|
||||
return vehicle_map[unit.type]
|
||||
elif isinstance(unit, Ship):
|
||||
return ship_map[unit.type]
|
||||
else:
|
||||
return unit.type
|
||||
|
||||
|
||||
def country_id_from_name(name):
|
||||
for k, v in country_dict.items():
|
||||
if v.name == name:
|
||||
|
||||
@ -12,6 +12,7 @@ from dcs.helicopters import helicopter_map
|
||||
from dcs.planes import plane_map
|
||||
from dcs.unittype import FlyingType
|
||||
|
||||
from game.dcs.unittype import UnitType
|
||||
from game.radio.channels import (
|
||||
ChannelNamer,
|
||||
RadioChannelAllocator,
|
||||
@ -90,15 +91,7 @@ class RadioConfig:
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AircraftType:
|
||||
dcs_unit_type: Type[FlyingType]
|
||||
name: str
|
||||
description: str
|
||||
year_introduced: str
|
||||
country_of_origin: str
|
||||
manufacturer: str
|
||||
role: str
|
||||
price: int
|
||||
class AircraftType(UnitType[FlyingType]):
|
||||
carrier_capable: bool
|
||||
lha_capable: bool
|
||||
always_keeps_gun: bool
|
||||
|
||||
95
game/dcs/groundunittype.py
Normal file
95
game/dcs/groundunittype.py
Normal 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
21
game/dcs/unittype.py
Normal 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
|
||||
@ -14,14 +14,12 @@ from typing import (
|
||||
Dict,
|
||||
Iterator,
|
||||
List,
|
||||
Type,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
|
||||
from dcs.unittype import UnitType
|
||||
|
||||
from game import db
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from game.theater import Airfield, ControlPoint
|
||||
from game.transfers import CargoShip
|
||||
from game.unitmap import (
|
||||
@ -183,8 +181,8 @@ class Debriefing:
|
||||
def casualty_count(self, control_point: ControlPoint) -> int:
|
||||
return len([x for x in self.front_line_losses if x.origin == control_point])
|
||||
|
||||
def front_line_losses_by_type(self, player: bool) -> Dict[Type[UnitType], int]:
|
||||
losses_by_type: Dict[Type[UnitType], int] = defaultdict(int)
|
||||
def front_line_losses_by_type(self, player: bool) -> dict[GroundUnitType, int]:
|
||||
losses_by_type: dict[GroundUnitType, int] = defaultdict(int)
|
||||
if player:
|
||||
losses = self.ground_losses.player_front_line
|
||||
else:
|
||||
@ -193,8 +191,8 @@ class Debriefing:
|
||||
losses_by_type[loss.unit_type] += 1
|
||||
return losses_by_type
|
||||
|
||||
def convoy_losses_by_type(self, player: bool) -> Dict[Type[UnitType], int]:
|
||||
losses_by_type: Dict[Type[UnitType], int] = defaultdict(int)
|
||||
def convoy_losses_by_type(self, player: bool) -> dict[GroundUnitType, int]:
|
||||
losses_by_type: dict[GroundUnitType, int] = defaultdict(int)
|
||||
if player:
|
||||
losses = self.ground_losses.player_convoy
|
||||
else:
|
||||
@ -203,8 +201,8 @@ class Debriefing:
|
||||
losses_by_type[loss.unit_type] += 1
|
||||
return losses_by_type
|
||||
|
||||
def cargo_ship_losses_by_type(self, player: bool) -> Dict[Type[UnitType], int]:
|
||||
losses_by_type: Dict[Type[UnitType], int] = defaultdict(int)
|
||||
def cargo_ship_losses_by_type(self, player: bool) -> dict[GroundUnitType, int]:
|
||||
losses_by_type: dict[GroundUnitType, int] = defaultdict(int)
|
||||
if player:
|
||||
ships = self.ground_losses.player_cargo_ships
|
||||
else:
|
||||
@ -214,8 +212,8 @@ class Debriefing:
|
||||
losses_by_type[unit_type] += count
|
||||
return losses_by_type
|
||||
|
||||
def airlift_losses_by_type(self, player: bool) -> Dict[Type[UnitType], int]:
|
||||
losses_by_type: Dict[Type[UnitType], int] = defaultdict(int)
|
||||
def airlift_losses_by_type(self, player: bool) -> dict[GroundUnitType, int]:
|
||||
losses_by_type: dict[GroundUnitType, int] = defaultdict(int)
|
||||
if player:
|
||||
losses = self.ground_losses.player_airlifts
|
||||
else:
|
||||
|
||||
@ -14,6 +14,7 @@ from game.operation.operation import Operation
|
||||
from game.theater import ControlPoint
|
||||
from gen import AirTaskingOrder
|
||||
from gen.ground_forces.combat_stance import CombatStance
|
||||
from ..dcs.groundunittype import GroundUnitType
|
||||
from ..unitmap import UnitMap
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -439,7 +440,7 @@ class Event:
|
||||
|
||||
# Also transfer pending deliveries.
|
||||
for unit_type, count in source.pending_unit_deliveries.units.items():
|
||||
if not issubclass(unit_type, VehicleType):
|
||||
if not isinstance(unit_type, GroundUnitType):
|
||||
continue
|
||||
if count <= 0:
|
||||
# Don't transfer *sales*...
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional, Dict, Type, List, Any, cast, Iterator
|
||||
from typing import Optional, Dict, Type, List, Any, Iterator
|
||||
|
||||
import dcs
|
||||
from dcs.countries import country_dict
|
||||
from dcs.planes import plane_map
|
||||
from dcs.unittype import FlyingType, ShipType, VehicleType, UnitType
|
||||
from dcs.vehicles import Armor, Unarmed, Infantry, Artillery, AirDefence
|
||||
from dcs.unittype import ShipType, UnitType
|
||||
|
||||
from game.data.building_data import (
|
||||
WW2_ALLIES_BUILDINGS,
|
||||
@ -24,7 +23,7 @@ from game.data.doctrine import (
|
||||
)
|
||||
from game.data.groundunitclass import GroundUnitClass
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
from pydcs_extensions.mod_units import MODDED_VEHICLES
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -55,16 +54,16 @@ class Faction:
|
||||
tankers: List[AircraftType] = field(default_factory=list)
|
||||
|
||||
# Available frontline units
|
||||
frontline_units: List[Type[VehicleType]] = field(default_factory=list)
|
||||
frontline_units: List[GroundUnitType] = field(default_factory=list)
|
||||
|
||||
# Available artillery units
|
||||
artillery_units: List[Type[VehicleType]] = field(default_factory=list)
|
||||
artillery_units: List[GroundUnitType] = field(default_factory=list)
|
||||
|
||||
# Infantry units used
|
||||
infantry_units: List[Type[VehicleType]] = field(default_factory=list)
|
||||
infantry_units: List[GroundUnitType] = field(default_factory=list)
|
||||
|
||||
# Logistics units used
|
||||
logistics_units: List[Type[VehicleType]] = field(default_factory=list)
|
||||
logistics_units: List[GroundUnitType] = field(default_factory=list)
|
||||
|
||||
# Possible SAMS site generators for this faction
|
||||
air_defenses: List[str] = field(default_factory=list)
|
||||
@ -135,15 +134,11 @@ class Faction:
|
||||
#: both will use it.
|
||||
unrestricted_satnav: bool = False
|
||||
|
||||
def has_access_to_unittype(self, unitclass: GroundUnitClass) -> bool:
|
||||
has_access = False
|
||||
for vehicle in unitclass.unit_list:
|
||||
if vehicle in self.frontline_units:
|
||||
def has_access_to_unittype(self, unit_class: GroundUnitClass) -> bool:
|
||||
for vehicle in itertools.chain(self.frontline_units, self.artillery_units):
|
||||
if vehicle.unit_class is unit_class:
|
||||
return True
|
||||
if vehicle in self.artillery_units:
|
||||
return True
|
||||
|
||||
return has_access
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def from_json(cls: Type[Faction], json: Dict[str, Any]) -> Faction:
|
||||
@ -172,10 +167,18 @@ class Faction:
|
||||
set(faction.aircrafts + faction.awacs + faction.tankers)
|
||||
)
|
||||
|
||||
faction.frontline_units = load_all_vehicles(json.get("frontline_units", []))
|
||||
faction.artillery_units = load_all_vehicles(json.get("artillery_units", []))
|
||||
faction.infantry_units = load_all_vehicles(json.get("infantry_units", []))
|
||||
faction.logistics_units = load_all_vehicles(json.get("logistics_units", []))
|
||||
faction.frontline_units = [
|
||||
GroundUnitType.named(n) for n in json.get("frontline_units", [])
|
||||
]
|
||||
faction.artillery_units = [
|
||||
GroundUnitType.named(n) for n in json.get("artillery_units", [])
|
||||
]
|
||||
faction.infantry_units = [
|
||||
GroundUnitType.named(n) for n in json.get("infantry_units", [])
|
||||
]
|
||||
faction.logistics_units = [
|
||||
GroundUnitType.named(n) for n in json.get("logistics_units", [])
|
||||
]
|
||||
|
||||
faction.ewrs = json.get("ewrs", [])
|
||||
|
||||
@ -242,55 +245,24 @@ class Faction:
|
||||
return faction
|
||||
|
||||
@property
|
||||
def ground_units(self) -> Iterator[Type[VehicleType]]:
|
||||
def ground_units(self) -> Iterator[GroundUnitType]:
|
||||
yield from self.artillery_units
|
||||
yield from self.frontline_units
|
||||
yield from self.logistics_units
|
||||
|
||||
|
||||
def unit_loader(unit: str, class_repository: List[Any]) -> Optional[Type[UnitType]]:
|
||||
"""
|
||||
Find unit by name
|
||||
:param unit: Unit name as string
|
||||
:param class_repository: Repository of classes (Either a module, a class, or a list of classes)
|
||||
:return: The unit as a PyDCS type
|
||||
"""
|
||||
if unit is None:
|
||||
return None
|
||||
elif unit in plane_map.keys():
|
||||
return plane_map[unit]
|
||||
else:
|
||||
for mother_class in class_repository:
|
||||
if getattr(mother_class, unit, None) is not None:
|
||||
return getattr(mother_class, unit)
|
||||
if type(mother_class) is list:
|
||||
for m in mother_class:
|
||||
if m.__name__ == unit:
|
||||
return m
|
||||
logging.error(f"FACTION ERROR : Unable to find {unit} in pydcs")
|
||||
return None
|
||||
|
||||
|
||||
def load_vehicle(name: str) -> Optional[Type[VehicleType]]:
|
||||
return cast(
|
||||
Optional[FlyingType],
|
||||
unit_loader(
|
||||
name, [Infantry, Unarmed, Armor, AirDefence, Artillery, MODDED_VEHICLES]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def load_all_vehicles(data) -> List[Type[VehicleType]]:
|
||||
items = []
|
||||
for name in data:
|
||||
item = load_vehicle(name)
|
||||
if item is not None:
|
||||
items.append(item)
|
||||
return items
|
||||
def infantry_with_class(
|
||||
self, unit_class: GroundUnitClass
|
||||
) -> Iterator[GroundUnitType]:
|
||||
for unit in self.infantry_units:
|
||||
if unit.unit_class is unit_class:
|
||||
yield unit
|
||||
|
||||
|
||||
def load_ship(name: str) -> Optional[Type[ShipType]]:
|
||||
return cast(Optional[FlyingType], unit_loader(name, [dcs.ships]))
|
||||
if (ship := getattr(dcs.ships, name, None)) is not None:
|
||||
return ship
|
||||
logging.error(f"FACTION ERROR : Unable to find {name} in dcs.ships")
|
||||
return None
|
||||
|
||||
|
||||
def load_all_ships(data) -> List[Type[ShipType]]:
|
||||
|
||||
@ -3,13 +3,12 @@ from __future__ import annotations
|
||||
import math
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterator, List, Optional, TYPE_CHECKING, Tuple, Type
|
||||
|
||||
from dcs.unittype import FlyingType, VehicleType
|
||||
from typing import Iterator, List, Optional, TYPE_CHECKING, Tuple
|
||||
|
||||
from game import db
|
||||
from game.data.groundunitclass import GroundUnitClass
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from game.factions.faction import Faction
|
||||
from game.theater import ControlPoint, MissionTarget
|
||||
from game.utils import Distance
|
||||
@ -148,17 +147,17 @@ class ProcurementAi:
|
||||
|
||||
def affordable_ground_unit_of_class(
|
||||
self, budget: float, unit_class: GroundUnitClass
|
||||
) -> Optional[Type[VehicleType]]:
|
||||
) -> Optional[GroundUnitType]:
|
||||
faction_units = set(self.faction.frontline_units) | set(
|
||||
self.faction.artillery_units
|
||||
)
|
||||
of_class = set(unit_class.unit_list) & faction_units
|
||||
of_class = {u for u in faction_units if u.unit_class is unit_class}
|
||||
|
||||
# faction has no access to needed unit type, take a random unit
|
||||
if not of_class:
|
||||
of_class = faction_units
|
||||
|
||||
affordable_units = [u for u in of_class if db.PRICES[u] <= budget]
|
||||
affordable_units = [u for u in of_class if u.price <= budget]
|
||||
if not affordable_units:
|
||||
return None
|
||||
return random.choice(affordable_units)
|
||||
@ -180,7 +179,7 @@ class ProcurementAi:
|
||||
# Can't afford any more units.
|
||||
break
|
||||
|
||||
budget -= db.PRICES[unit]
|
||||
budget -= unit.price
|
||||
cp.pending_unit_deliveries.order({unit: 1})
|
||||
|
||||
return budget
|
||||
@ -361,9 +360,9 @@ class ProcurementAi:
|
||||
class_cost = 0
|
||||
total_cost = 0
|
||||
for unit_type, count in allocations.all.items():
|
||||
cost = db.PRICES[unit_type] * count
|
||||
cost = unit_type.price * count
|
||||
total_cost += cost
|
||||
if unit_type in unit_class:
|
||||
if unit_type.unit_class is unit_class:
|
||||
class_cost += cost
|
||||
if not total_cost:
|
||||
return 0
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
import itertools
|
||||
import logging
|
||||
import typing
|
||||
from typing import Dict, Type
|
||||
from typing import Any
|
||||
|
||||
from dcs.unittype import VehicleType
|
||||
|
||||
from game.db import PRICES
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from game.dcs.unittype import UnitType
|
||||
|
||||
BASE_MAX_STRENGTH = 1
|
||||
BASE_MIN_STRENGTH = 0
|
||||
@ -14,8 +12,8 @@ BASE_MIN_STRENGTH = 0
|
||||
|
||||
class Base:
|
||||
def __init__(self):
|
||||
self.aircraft: Dict[AircraftType, int] = {}
|
||||
self.armor: Dict[Type[VehicleType], int] = {}
|
||||
self.aircraft: dict[AircraftType, int] = {}
|
||||
self.armor: dict[GroundUnitType, int] = {}
|
||||
self.strength = 1
|
||||
|
||||
@property
|
||||
@ -30,13 +28,10 @@ class Base:
|
||||
def total_armor_value(self) -> int:
|
||||
total = 0
|
||||
for unit_type, count in self.armor.items():
|
||||
try:
|
||||
total += PRICES[unit_type] * count
|
||||
except KeyError:
|
||||
logging.exception(f"No price found for {unit_type.id}")
|
||||
total += unit_type.price * count
|
||||
return total
|
||||
|
||||
def total_units_of_type(self, unit_type: typing.Any) -> int:
|
||||
def total_units_of_type(self, unit_type: UnitType) -> int:
|
||||
return sum(
|
||||
[
|
||||
c
|
||||
@ -45,30 +40,25 @@ class Base:
|
||||
]
|
||||
)
|
||||
|
||||
def commission_units(self, units: typing.Dict[typing.Any, int]):
|
||||
def commission_units(self, units: dict[Any, int]):
|
||||
for unit_type, unit_count in units.items():
|
||||
if unit_count <= 0:
|
||||
continue
|
||||
|
||||
target_dict: dict[typing.Any, int]
|
||||
target_dict: dict[Any, int]
|
||||
if isinstance(unit_type, AircraftType):
|
||||
target_dict = self.aircraft
|
||||
elif issubclass(unit_type, VehicleType):
|
||||
elif isinstance(unit_type, GroundUnitType):
|
||||
target_dict = self.armor
|
||||
else:
|
||||
logging.error(
|
||||
f"Unexpected unit type of {unit_type}: "
|
||||
f"{unit_type.__module__}.{unit_type.__name__}"
|
||||
)
|
||||
logging.error(f"Unexpected unit type of {unit_type}")
|
||||
return
|
||||
|
||||
target_dict[unit_type] = target_dict.get(unit_type, 0) + unit_count
|
||||
|
||||
def commit_losses(self, units_lost: typing.Dict[typing.Any, int]):
|
||||
|
||||
def commit_losses(self, units_lost: dict[Any, int]):
|
||||
for unit_type, count in units_lost.items():
|
||||
|
||||
target_dict: dict[typing.Any, int]
|
||||
target_dict: dict[Any, int]
|
||||
if unit_type in self.aircraft:
|
||||
target_dict = self.aircraft
|
||||
elif unit_type in self.armor:
|
||||
|
||||
@ -16,7 +16,6 @@ from typing import (
|
||||
Optional,
|
||||
Set,
|
||||
TYPE_CHECKING,
|
||||
Type,
|
||||
Union,
|
||||
Sequence,
|
||||
Iterable,
|
||||
@ -32,7 +31,6 @@ from dcs.ships import (
|
||||
)
|
||||
from dcs.terrain.terrain import Airport, ParkingSlot
|
||||
from dcs.unit import Unit
|
||||
from dcs.unittype import VehicleType
|
||||
|
||||
from game import db
|
||||
from game.point_with_heading import PointWithHeading
|
||||
@ -46,8 +44,8 @@ from .theatergroundobject import (
|
||||
GenericCarrierGroundObject,
|
||||
TheaterGroundObject,
|
||||
)
|
||||
from ..db import PRICES
|
||||
from ..dcs.aircrafttype import AircraftType
|
||||
from ..dcs.groundunittype import GroundUnitType
|
||||
from ..utils import nautical_miles
|
||||
from ..weather import Conditions
|
||||
|
||||
@ -161,13 +159,13 @@ class AircraftAllocations:
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GroundUnitAllocations:
|
||||
present: dict[Type[VehicleType], int]
|
||||
ordered: dict[Type[VehicleType], int]
|
||||
transferring: dict[Type[VehicleType], int]
|
||||
present: dict[GroundUnitType, int]
|
||||
ordered: dict[GroundUnitType, int]
|
||||
transferring: dict[GroundUnitType, int]
|
||||
|
||||
@property
|
||||
def all(self) -> dict[Type[VehicleType], int]:
|
||||
combined: dict[Type[VehicleType], int] = defaultdict(int)
|
||||
def all(self) -> dict[GroundUnitType, int]:
|
||||
combined: dict[GroundUnitType, int] = defaultdict(int)
|
||||
for unit_type, count in itertools.chain(
|
||||
self.present.items(), self.ordered.items(), self.transferring.items()
|
||||
):
|
||||
@ -178,11 +176,11 @@ class GroundUnitAllocations:
|
||||
def total_value(self) -> int:
|
||||
total: int = 0
|
||||
for unit_type, count in self.present.items():
|
||||
total += PRICES[unit_type] * count
|
||||
total += unit_type.price * count
|
||||
for unit_type, count in self.ordered.items():
|
||||
total += PRICES[unit_type] * count
|
||||
total += unit_type.price * count
|
||||
for unit_type, count in self.transferring.items():
|
||||
total += PRICES[unit_type] * count
|
||||
total += unit_type.price * count
|
||||
|
||||
return total
|
||||
|
||||
@ -697,10 +695,10 @@ class ControlPoint(MissionTarget, ABC):
|
||||
) -> GroundUnitAllocations:
|
||||
on_order = {}
|
||||
for unit_bought, count in self.pending_unit_deliveries.units.items():
|
||||
if type(unit_bought) == type and issubclass(unit_bought, VehicleType):
|
||||
if isinstance(unit_bought, GroundUnitType):
|
||||
on_order[unit_bought] = count
|
||||
|
||||
transferring: dict[Type[VehicleType], int] = defaultdict(int)
|
||||
transferring: dict[GroundUnitType, int] = defaultdict(int)
|
||||
for transfer in transfers:
|
||||
if transfer.destination == self:
|
||||
for unit_type, count in transfer.units.items():
|
||||
|
||||
@ -12,15 +12,14 @@ from typing import (
|
||||
List,
|
||||
Optional,
|
||||
TYPE_CHECKING,
|
||||
Type,
|
||||
TypeVar,
|
||||
Sequence,
|
||||
)
|
||||
|
||||
from dcs.mapping import Point
|
||||
from dcs.unittype import VehicleType
|
||||
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from game.procurement import AircraftProcurementRequest
|
||||
from game.squadrons import Squadron
|
||||
from game.theater import ControlPoint, MissionTarget
|
||||
@ -73,7 +72,7 @@ class TransferOrder:
|
||||
player: bool = field(init=False)
|
||||
|
||||
#: The units being transferred.
|
||||
units: Dict[Type[VehicleType], int]
|
||||
units: Dict[GroundUnitType, int]
|
||||
|
||||
transport: Optional[Transport] = field(default=None)
|
||||
|
||||
@ -90,7 +89,7 @@ class TransferOrder:
|
||||
def kill_all(self) -> None:
|
||||
self.units.clear()
|
||||
|
||||
def kill_unit(self, unit_type: Type[VehicleType]) -> None:
|
||||
def kill_unit(self, unit_type: GroundUnitType) -> None:
|
||||
if unit_type not in self.units or not self.units[unit_type]:
|
||||
raise KeyError(f"{self.destination} has no {unit_type} remaining")
|
||||
self.units[unit_type] -= 1
|
||||
@ -99,7 +98,7 @@ class TransferOrder:
|
||||
def size(self) -> int:
|
||||
return sum(c for c in self.units.values())
|
||||
|
||||
def iter_units(self) -> Iterator[Type[VehicleType]]:
|
||||
def iter_units(self) -> Iterator[GroundUnitType]:
|
||||
for unit_type, count in self.units.items():
|
||||
for _ in range(count):
|
||||
yield unit_type
|
||||
@ -157,7 +156,7 @@ class Airlift(Transport):
|
||||
self.flight = flight
|
||||
|
||||
@property
|
||||
def units(self) -> Dict[Type[VehicleType], int]:
|
||||
def units(self) -> Dict[GroundUnitType, int]:
|
||||
return self.transfer.units
|
||||
|
||||
@property
|
||||
@ -315,7 +314,7 @@ class MultiGroupTransport(MissionTarget, Transport):
|
||||
transfer.transport = None
|
||||
self.transfers.remove(transfer)
|
||||
|
||||
def kill_unit(self, unit_type: Type[VehicleType]) -> None:
|
||||
def kill_unit(self, unit_type: GroundUnitType) -> None:
|
||||
for transfer in self.transfers:
|
||||
try:
|
||||
transfer.kill_unit(unit_type)
|
||||
@ -338,13 +337,18 @@ class MultiGroupTransport(MissionTarget, Transport):
|
||||
return sum(sum(t.units.values()) for t in self.transfers)
|
||||
|
||||
@property
|
||||
def units(self) -> Dict[Type[VehicleType], int]:
|
||||
units: Dict[Type[VehicleType], int] = defaultdict(int)
|
||||
def units(self) -> dict[GroundUnitType, int]:
|
||||
units: Dict[GroundUnitType, int] = defaultdict(int)
|
||||
for transfer in self.transfers:
|
||||
for unit_type, count in transfer.units.items():
|
||||
units[unit_type] += count
|
||||
return units
|
||||
|
||||
def iter_units(self) -> Iterator[GroundUnitType]:
|
||||
for unit_type, count in self.units.items():
|
||||
for _ in range(count):
|
||||
yield unit_type
|
||||
|
||||
@property
|
||||
def player_owned(self) -> bool:
|
||||
return self.origin.captured
|
||||
|
||||
@ -3,13 +3,11 @@ from __future__ import annotations
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Optional, TYPE_CHECKING, Type, Any
|
||||
|
||||
from dcs.unittype import UnitType, VehicleType
|
||||
from typing import Dict, Optional, TYPE_CHECKING, Any
|
||||
|
||||
from game.theater import ControlPoint
|
||||
from .db import PRICES
|
||||
from .dcs.aircrafttype import AircraftType
|
||||
from .dcs.groundunittype import GroundUnitType
|
||||
from .dcs.unittype import UnitType
|
||||
from .theater.transitnetwork import (
|
||||
NoPathError,
|
||||
TransitNetwork,
|
||||
@ -25,24 +23,21 @@ class GroundUnitSource:
|
||||
control_point: ControlPoint
|
||||
|
||||
|
||||
AircraftOrVehicleType = Any
|
||||
|
||||
|
||||
class PendingUnitDeliveries:
|
||||
def __init__(self, destination: ControlPoint) -> None:
|
||||
self.destination = destination
|
||||
|
||||
# Maps unit type to order quantity.
|
||||
self.units: Dict[AircraftOrVehicleType, int] = defaultdict(int)
|
||||
self.units: Dict[UnitType, int] = defaultdict(int)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Pending delivery to {self.destination}"
|
||||
|
||||
def order(self, units: Dict[AircraftOrVehicleType, int]) -> None:
|
||||
def order(self, units: Dict[UnitType, int]) -> None:
|
||||
for k, v in units.items():
|
||||
self.units[k] += v
|
||||
|
||||
def sell(self, units: Dict[AircraftOrVehicleType, int]) -> None:
|
||||
def sell(self, units: Dict[UnitType, int]) -> None:
|
||||
for k, v in units.items():
|
||||
self.units[k] -= v
|
||||
|
||||
@ -50,24 +45,20 @@ class PendingUnitDeliveries:
|
||||
self.refund(game, self.units)
|
||||
self.units = defaultdict(int)
|
||||
|
||||
def refund(self, game: Game, units: Dict[Type[UnitType], int]) -> None:
|
||||
def refund(self, game: Game, units: Dict[UnitType, int]) -> None:
|
||||
for unit_type, count in units.items():
|
||||
try:
|
||||
price = PRICES[unit_type]
|
||||
except KeyError:
|
||||
logging.error(f"Could not refund {unit_type.id}, price unknown")
|
||||
continue
|
||||
logging.info(f"Refunding {count} {unit_type} at {self.destination.name}")
|
||||
game.adjust_budget(
|
||||
unit_type.price * count, player=self.destination.captured
|
||||
)
|
||||
|
||||
logging.info(f"Refunding {count} {unit_type.id} at {self.destination.name}")
|
||||
game.adjust_budget(price * count, player=self.destination.captured)
|
||||
|
||||
def pending_orders(self, unit_type: AircraftOrVehicleType) -> int:
|
||||
def pending_orders(self, unit_type: UnitType) -> int:
|
||||
pending_units = self.units.get(unit_type)
|
||||
if pending_units is None:
|
||||
pending_units = 0
|
||||
return pending_units
|
||||
|
||||
def available_next_turn(self, unit_type: AircraftOrVehicleType) -> int:
|
||||
def available_next_turn(self, unit_type: UnitType) -> int:
|
||||
current_units = self.destination.base.total_units_of_type(unit_type)
|
||||
return self.pending_orders(unit_type) + current_units
|
||||
|
||||
@ -81,20 +72,14 @@ class PendingUnitDeliveries:
|
||||
self.refund_all(game)
|
||||
return
|
||||
|
||||
bought_units: Dict[AircraftOrVehicleType, int] = {}
|
||||
units_needing_transfer: Dict[Type[VehicleType], int] = {}
|
||||
sold_units: Dict[AircraftOrVehicleType, int] = {}
|
||||
bought_units: Dict[UnitType, int] = {}
|
||||
units_needing_transfer: Dict[GroundUnitType, int] = {}
|
||||
sold_units: Dict[UnitType, int] = {}
|
||||
for unit_type, count in self.units.items():
|
||||
coalition = "Ally" if self.destination.captured else "Enemy"
|
||||
|
||||
if isinstance(unit_type, AircraftType):
|
||||
name = unit_type.name
|
||||
else:
|
||||
name = unit_type.id
|
||||
|
||||
d: dict[Any, int]
|
||||
if (
|
||||
type(unit_type) == type
|
||||
and issubclass(unit_type, VehicleType)
|
||||
isinstance(unit_type, GroundUnitType)
|
||||
and self.destination != ground_unit_source
|
||||
):
|
||||
source = ground_unit_source
|
||||
@ -106,11 +91,11 @@ class PendingUnitDeliveries:
|
||||
if count >= 0:
|
||||
d[unit_type] = count
|
||||
game.message(
|
||||
f"{coalition} reinforcements: {name} x {count} at {source}"
|
||||
f"{coalition} reinforcements: {unit_type} x {count} at {source}"
|
||||
)
|
||||
else:
|
||||
sold_units[unit_type] = -count
|
||||
game.message(f"{coalition} sold: {name} x {-count} at {source}")
|
||||
game.message(f"{coalition} sold: {unit_type} x {-count} at {source}")
|
||||
|
||||
self.units = defaultdict(int)
|
||||
self.destination.base.commission_units(bought_units)
|
||||
@ -121,7 +106,7 @@ class PendingUnitDeliveries:
|
||||
self.create_transfer(game, ground_unit_source, units_needing_transfer)
|
||||
|
||||
def create_transfer(
|
||||
self, game: Game, source: ControlPoint, units: Dict[Type[VehicleType], int]
|
||||
self, game: Game, source: ControlPoint, units: Dict[GroundUnitType, int]
|
||||
) -> None:
|
||||
game.transfers.new_transfer(TransferOrder(source, self.destination, units))
|
||||
|
||||
|
||||
@ -2,13 +2,12 @@
|
||||
import itertools
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Optional, Type
|
||||
from typing import Dict, Optional
|
||||
|
||||
from dcs.unit import Unit
|
||||
from dcs.unitgroup import FlyingGroup, Group, VehicleGroup
|
||||
from dcs.unittype import VehicleType
|
||||
|
||||
from game import db
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from game.squadrons import Pilot
|
||||
from game.theater import Airfield, ControlPoint, TheaterGroundObject
|
||||
from game.theater.theatergroundobject import BuildingGroundObject, SceneryGroundObject
|
||||
@ -24,7 +23,7 @@ class FlyingUnit:
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FrontLineUnit:
|
||||
unit_type: Type[VehicleType]
|
||||
unit_type: GroundUnitType
|
||||
origin: ControlPoint
|
||||
|
||||
|
||||
@ -37,13 +36,13 @@ class GroundObjectUnit:
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ConvoyUnit:
|
||||
unit_type: Type[VehicleType]
|
||||
unit_type: GroundUnitType
|
||||
convoy: Convoy
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AirliftUnits:
|
||||
cargo: tuple[Type[VehicleType], ...]
|
||||
cargo: tuple[GroundUnitType, ...]
|
||||
transfer: TransferOrder
|
||||
|
||||
|
||||
@ -85,20 +84,15 @@ class UnitMap:
|
||||
def airfield(self, name: str) -> Optional[Airfield]:
|
||||
return self.airfields.get(name, None)
|
||||
|
||||
def add_front_line_units(self, group: Group, origin: ControlPoint) -> None:
|
||||
def add_front_line_units(
|
||||
self, group: Group, origin: ControlPoint, unit_type: GroundUnitType
|
||||
) -> None:
|
||||
for unit in group.units:
|
||||
# The actual name is a String (the pydcs translatable string), which
|
||||
# doesn't define __eq__.
|
||||
name = str(unit.name)
|
||||
if name in self.front_line_units:
|
||||
raise RuntimeError(f"Duplicate front line unit: {name}")
|
||||
unit_type = db.unit_type_from_name(unit.type)
|
||||
if unit_type is None:
|
||||
raise RuntimeError(f"Unknown unit type: {unit.type}")
|
||||
if not issubclass(unit_type, VehicleType):
|
||||
raise RuntimeError(
|
||||
f"{name} is a {unit_type.__name__}, expected a VehicleType"
|
||||
)
|
||||
self.front_line_units[name] = FrontLineUnit(unit_type, origin)
|
||||
|
||||
def front_line_unit(self, name: str) -> Optional[FrontLineUnit]:
|
||||
@ -141,19 +135,12 @@ class UnitMap:
|
||||
return self.ground_object_units.get(name, None)
|
||||
|
||||
def add_convoy_units(self, group: Group, convoy: Convoy) -> None:
|
||||
for unit in group.units:
|
||||
for unit, unit_type in zip(group.units, convoy.iter_units()):
|
||||
# The actual name is a String (the pydcs translatable string), which
|
||||
# doesn't define __eq__.
|
||||
name = str(unit.name)
|
||||
if name in self.convoys:
|
||||
raise RuntimeError(f"Duplicate convoy unit: {name}")
|
||||
unit_type = db.unit_type_from_name(unit.type)
|
||||
if unit_type is None:
|
||||
raise RuntimeError(f"Unknown unit type: {unit.type}")
|
||||
if not issubclass(unit_type, VehicleType):
|
||||
raise RuntimeError(
|
||||
f"{name} is a {unit_type.__name__}, expected a VehicleType"
|
||||
)
|
||||
self.convoys[name] = ConvoyUnit(unit_type, convoy)
|
||||
|
||||
def convoy_unit(self, name: str) -> Optional[ConvoyUnit]:
|
||||
|
||||
@ -110,7 +110,6 @@ class AirSupportConflictGenerator:
|
||||
):
|
||||
# TODO: Make loiter altitude a property of the unit type.
|
||||
alt, airspeed = self._get_tanker_params(tanker_unit_type.dcs_unit_type)
|
||||
variant = db.unit_type_name(tanker_unit_type)
|
||||
freq = self.radio_registry.alloc_uhf()
|
||||
tacan = self.tacan_registry.alloc_for_band(TacanBand.Y)
|
||||
tanker_heading = (
|
||||
@ -175,7 +174,7 @@ class AirSupportConflictGenerator:
|
||||
TankerInfo(
|
||||
str(tanker_group.name),
|
||||
callsign,
|
||||
variant,
|
||||
tanker_unit_type.name,
|
||||
freq,
|
||||
tacan,
|
||||
blue=True,
|
||||
|
||||
76
gen/armor.py
76
gen/armor.py
@ -10,7 +10,6 @@ from dcs.action import AITaskPush
|
||||
from dcs.condition import GroupLifeLess, Or, TimeAfter, UnitDamaged
|
||||
from dcs.country import Country
|
||||
from dcs.mapping import Point
|
||||
from dcs.planes import MQ_9_Reaper
|
||||
from dcs.point import PointAction
|
||||
from dcs.task import (
|
||||
EPLRS,
|
||||
@ -26,19 +25,18 @@ from dcs.task import (
|
||||
from dcs.triggers import Event, TriggerOnce
|
||||
from dcs.unit import Vehicle
|
||||
from dcs.unitgroup import VehicleGroup
|
||||
from dcs.unittype import VehicleType
|
||||
from game import db
|
||||
|
||||
from game.data.groundunitclass import GroundUnitClass
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from game.theater.controlpoint import ControlPoint
|
||||
from game.unitmap import UnitMap
|
||||
from game.utils import heading_sum, opposite_heading
|
||||
from game.theater.controlpoint import ControlPoint
|
||||
|
||||
from gen.ground_forces.ai_ground_planner import (
|
||||
DISTANCE_FROM_FRONTLINE,
|
||||
CombatGroup,
|
||||
CombatGroupRole,
|
||||
)
|
||||
|
||||
from .callsigns import callsign_for_support_unit
|
||||
from .conflictgen import Conflict
|
||||
from .ground_forces.combat_stance import CombatStance
|
||||
@ -226,19 +224,18 @@ class GroundConflictGenerator:
|
||||
else:
|
||||
cp = self.conflict.red_cp
|
||||
|
||||
if is_player:
|
||||
faction = self.game.player_name
|
||||
else:
|
||||
faction = self.game.enemy_name
|
||||
faction = self.game.faction_for(is_player)
|
||||
|
||||
# Disable infantry unit gen if disabled
|
||||
if not self.game.settings.perf_infantry:
|
||||
if self.game.settings.manpads:
|
||||
# 50% of armored units protected by manpad
|
||||
if random.choice([True, False]):
|
||||
manpads = db.find_manpad(faction)
|
||||
if len(manpads) > 0:
|
||||
u = random.choice(manpads)
|
||||
manpads = list(faction.infantry_with_class(GroundUnitClass.Manpads))
|
||||
if manpads:
|
||||
u = random.choices(
|
||||
manpads, weights=[m.spawn_weight for m in manpads]
|
||||
)[0]
|
||||
self.mission.vehicle_group(
|
||||
side,
|
||||
namegen.next_infantry_name(side, cp.id, u),
|
||||
@ -250,30 +247,38 @@ class GroundConflictGenerator:
|
||||
)
|
||||
return
|
||||
|
||||
possible_infantry_units = db.find_infantry(
|
||||
faction, allow_manpad=self.game.settings.manpads
|
||||
possible_infantry_units = set(
|
||||
faction.infantry_with_class(GroundUnitClass.Infantry)
|
||||
)
|
||||
if len(possible_infantry_units) == 0:
|
||||
if self.game.settings.manpads:
|
||||
possible_infantry_units |= set(
|
||||
faction.infantry_with_class(GroundUnitClass.Manpads)
|
||||
)
|
||||
if not possible_infantry_units:
|
||||
return
|
||||
|
||||
u = random.choice(possible_infantry_units)
|
||||
infantry_choices = list(possible_infantry_units)
|
||||
units = random.choices(
|
||||
infantry_choices,
|
||||
weights=[u.spawn_weight for u in infantry_choices],
|
||||
k=INFANTRY_GROUP_SIZE,
|
||||
)
|
||||
self.mission.vehicle_group(
|
||||
side,
|
||||
namegen.next_infantry_name(side, cp.id, u),
|
||||
u,
|
||||
namegen.next_infantry_name(side, cp.id, units[0]),
|
||||
units[0].dcs_unit_type,
|
||||
position=infantry_position,
|
||||
group_size=1,
|
||||
heading=forward_heading,
|
||||
move_formation=PointAction.OffRoad,
|
||||
)
|
||||
|
||||
for i in range(INFANTRY_GROUP_SIZE):
|
||||
u = random.choice(possible_infantry_units)
|
||||
for unit in units[1:]:
|
||||
position = infantry_position.random_point_within(55, 5)
|
||||
self.mission.vehicle_group(
|
||||
side,
|
||||
namegen.next_infantry_name(side, cp.id, u),
|
||||
u,
|
||||
namegen.next_infantry_name(side, cp.id, unit),
|
||||
unit.dcs_unit_type,
|
||||
position=position,
|
||||
group_size=1,
|
||||
heading=forward_heading,
|
||||
@ -313,7 +318,7 @@ class GroundConflictGenerator:
|
||||
)
|
||||
artillery_trigger.add_condition(TimeAfter(seconds=random.randint(1, 45) * 60))
|
||||
# TODO: Update to fire at group instead of point
|
||||
fire_task = FireAtPoint(target, len(gen_group.units) * 10, 100)
|
||||
fire_task = FireAtPoint(target, gen_group.size * 10, 100)
|
||||
fire_task.number = 2 if stance != CombatStance.RETREAT else 1
|
||||
dcs_group.add_trigger_action(fire_task)
|
||||
artillery_trigger.add_action(AITaskPush(dcs_group.id, len(dcs_group.tasks)))
|
||||
@ -503,7 +508,7 @@ class GroundConflictGenerator:
|
||||
return
|
||||
|
||||
for dcs_group, group in ally_groups:
|
||||
if hasattr(group.units[0], "eplrs") and group.units[0].eplrs:
|
||||
if getattr(group.unit_type.dcs_unit_type, "eplrs", False):
|
||||
dcs_group.points[0].tasks.append(EPLRS(dcs_group.id))
|
||||
|
||||
if group.role == CombatGroupRole.ARTILLERY:
|
||||
@ -674,7 +679,7 @@ class GroundConflictGenerator:
|
||||
Search the enemy groups for a potential target suitable to an artillery unit
|
||||
"""
|
||||
# TODO: Update to return a list of groups instead of a single point
|
||||
rng = group.units[0].threat_range
|
||||
rng = getattr(group.unit_type.dcs_unit_type, "threat_range", 0)
|
||||
if not enemy_groups:
|
||||
return None
|
||||
for _ in range(10):
|
||||
@ -691,7 +696,7 @@ class GroundConflictGenerator:
|
||||
"""
|
||||
For artilery group, decide the distance from frontline with the range of the unit
|
||||
"""
|
||||
rg = group.units[0].threat_range - 7500
|
||||
rg = getattr(group.unit_type.dcs_unit_type, "threat_range", 0) - 7500
|
||||
if rg > DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][1]:
|
||||
rg = random.randint(
|
||||
DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][0],
|
||||
@ -724,7 +729,7 @@ class GroundConflictGenerator:
|
||||
|
||||
def _generate_groups(
|
||||
self,
|
||||
groups: List[CombatGroup],
|
||||
groups: list[CombatGroup],
|
||||
frontline_vector: Tuple[Point, int, int],
|
||||
is_player: bool,
|
||||
) -> List[Tuple[VehicleGroup, CombatGroup]]:
|
||||
@ -755,10 +760,9 @@ class GroundConflictGenerator:
|
||||
if final_position is not None:
|
||||
g = self._generate_group(
|
||||
self.mission.country(country),
|
||||
group.units[0],
|
||||
len(group.units),
|
||||
group.unit_type,
|
||||
group.size,
|
||||
final_position,
|
||||
distance_from_frontline,
|
||||
heading=opposite_heading(spawn_heading),
|
||||
)
|
||||
if is_player:
|
||||
@ -782,10 +786,9 @@ class GroundConflictGenerator:
|
||||
def _generate_group(
|
||||
self,
|
||||
side: Country,
|
||||
unit: VehicleType,
|
||||
unit_type: GroundUnitType,
|
||||
count: int,
|
||||
at: Point,
|
||||
distance_from_frontline,
|
||||
move_formation: PointAction = PointAction.OffRoad,
|
||||
heading=0,
|
||||
) -> VehicleGroup:
|
||||
@ -795,18 +798,17 @@ class GroundConflictGenerator:
|
||||
else:
|
||||
cp = self.conflict.red_cp
|
||||
|
||||
logging.info("armorgen: {} for {}".format(unit, side.id))
|
||||
group = self.mission.vehicle_group(
|
||||
side,
|
||||
namegen.next_unit_name(side, cp.id, unit),
|
||||
unit,
|
||||
namegen.next_unit_name(side, cp.id, unit_type),
|
||||
unit_type.dcs_unit_type,
|
||||
position=at,
|
||||
group_size=count,
|
||||
heading=heading,
|
||||
move_formation=move_formation,
|
||||
)
|
||||
|
||||
self.unit_map.add_front_line_units(group, cp)
|
||||
self.unit_map.add_front_line_units(group, cp, unit_type)
|
||||
|
||||
for c in range(count):
|
||||
vehicle: Vehicle = group.units[c]
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
from typing import Dict, TYPE_CHECKING, Type
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dcs import Mission
|
||||
from dcs.mapping import Point
|
||||
from dcs.point import PointAction
|
||||
from dcs.unit import Vehicle
|
||||
from dcs.unitgroup import VehicleGroup
|
||||
from dcs.unittype import VehicleType
|
||||
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from game.transfers import Convoy
|
||||
from game.unitmap import UnitMap
|
||||
from game.utils import kph
|
||||
@ -50,7 +50,7 @@ class ConvoyGenerator:
|
||||
self,
|
||||
name: str,
|
||||
position: Point,
|
||||
units: Dict[Type[VehicleType], int],
|
||||
units: dict[GroundUnitType, int],
|
||||
for_player: bool,
|
||||
) -> VehicleGroup:
|
||||
country = self.mission.country(
|
||||
@ -63,7 +63,7 @@ class ConvoyGenerator:
|
||||
group = self.mission.vehicle_group(
|
||||
country,
|
||||
name,
|
||||
main_unit_type,
|
||||
main_unit_type.dcs_unit_type,
|
||||
position=position,
|
||||
group_size=main_unit_count,
|
||||
move_formation=PointAction.OnRoad,
|
||||
@ -76,7 +76,7 @@ class ConvoyGenerator:
|
||||
for unit_type, count in unit_types[1:]:
|
||||
for i in range(count):
|
||||
v = self.mission.vehicle(
|
||||
f"{name} Unit #{next(unit_name_counter)}", unit_type
|
||||
f"{name} Unit #{next(unit_name_counter)}", unit_type.dcs_unit_type
|
||||
)
|
||||
v.position.x = position.x
|
||||
v.position.y = next(y)
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
import random
|
||||
|
||||
from dcs.vehicles import Armor
|
||||
from dcs.unitgroup import VehicleGroup
|
||||
|
||||
from game import db
|
||||
from game import db, Game
|
||||
from game.data.groundunitclass import GroundUnitClass
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from game.theater.theatergroundobject import VehicleGroupGroundObject
|
||||
from gen.defenses.armored_group_generator import (
|
||||
ArmoredGroupGenerator,
|
||||
FixedSizeArmorGroupGenerator,
|
||||
@ -14,8 +17,14 @@ def generate_armor_group(faction: str, game, ground_object):
|
||||
This generate a group of ground units
|
||||
:return: Generated group
|
||||
"""
|
||||
armor_types = (
|
||||
GroundUnitClass.Apc,
|
||||
GroundUnitClass.Atgm,
|
||||
GroundUnitClass.Ifv,
|
||||
GroundUnitClass.Tank,
|
||||
)
|
||||
possible_unit = [
|
||||
u for u in db.FACTIONS[faction].frontline_units if u in Armor.__dict__.values()
|
||||
u for u in db.FACTIONS[faction].frontline_units if u.unit_class in armor_types
|
||||
]
|
||||
if len(possible_unit) > 0:
|
||||
unit_type = random.choice(possible_unit)
|
||||
@ -23,7 +32,9 @@ def generate_armor_group(faction: str, game, ground_object):
|
||||
return None
|
||||
|
||||
|
||||
def generate_armor_group_of_type(game, ground_object, unit_type):
|
||||
def generate_armor_group_of_type(
|
||||
game: Game, ground_object: VehicleGroupGroundObject, unit_type: GroundUnitType
|
||||
) -> VehicleGroup:
|
||||
"""
|
||||
This generate a group of ground units of given type
|
||||
:return: Generated group
|
||||
@ -33,7 +44,12 @@ def generate_armor_group_of_type(game, ground_object, unit_type):
|
||||
return generator.get_generated_group()
|
||||
|
||||
|
||||
def generate_armor_group_of_type_and_size(game, ground_object, unit_type, size: int):
|
||||
def generate_armor_group_of_type_and_size(
|
||||
game: Game,
|
||||
ground_object: VehicleGroupGroundObject,
|
||||
unit_type: GroundUnitType,
|
||||
size: int,
|
||||
) -> VehicleGroup:
|
||||
"""
|
||||
This generate a group of ground units of given type and size
|
||||
:return: Generated group
|
||||
|
||||
@ -1,15 +1,22 @@
|
||||
import random
|
||||
|
||||
from game import Game
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from game.theater.theatergroundobject import VehicleGroupGroundObject
|
||||
from gen.sam.group_generator import GroupGenerator
|
||||
|
||||
|
||||
class ArmoredGroupGenerator(GroupGenerator):
|
||||
def __init__(self, game, ground_object, unit_type):
|
||||
super(ArmoredGroupGenerator, self).__init__(game, ground_object)
|
||||
def __init__(
|
||||
self,
|
||||
game: Game,
|
||||
ground_object: VehicleGroupGroundObject,
|
||||
unit_type: GroundUnitType,
|
||||
) -> None:
|
||||
super().__init__(game, ground_object)
|
||||
self.unit_type = unit_type
|
||||
|
||||
def generate(self):
|
||||
|
||||
def generate(self) -> None:
|
||||
grid_x = random.randint(2, 3)
|
||||
grid_y = random.randint(1, 2)
|
||||
|
||||
@ -20,7 +27,7 @@ class ArmoredGroupGenerator(GroupGenerator):
|
||||
for j in range(grid_y):
|
||||
index = index + 1
|
||||
self.add_unit(
|
||||
self.unit_type,
|
||||
self.unit_type.dcs_unit_type,
|
||||
"Armor#" + str(index),
|
||||
self.position.x + spacing * i,
|
||||
self.position.y + spacing * j,
|
||||
@ -29,8 +36,14 @@ class ArmoredGroupGenerator(GroupGenerator):
|
||||
|
||||
|
||||
class FixedSizeArmorGroupGenerator(GroupGenerator):
|
||||
def __init__(self, game, ground_object, unit_type, size):
|
||||
super(FixedSizeArmorGroupGenerator, self).__init__(game, ground_object)
|
||||
def __init__(
|
||||
self,
|
||||
game: Game,
|
||||
ground_object: VehicleGroupGroundObject,
|
||||
unit_type: GroundUnitType,
|
||||
size: int,
|
||||
) -> None:
|
||||
super().__init__(game, ground_object)
|
||||
self.unit_type = unit_type
|
||||
self.size = size
|
||||
|
||||
@ -41,7 +54,7 @@ class FixedSizeArmorGroupGenerator(GroupGenerator):
|
||||
for i in range(self.size):
|
||||
index = index + 1
|
||||
self.add_unit(
|
||||
self.unit_type,
|
||||
self.unit_type.dcs_unit_type,
|
||||
"Armor#" + str(index),
|
||||
self.position.x + spacing * i,
|
||||
self.position.y,
|
||||
|
||||
@ -3,11 +3,9 @@ import random
|
||||
from enum import Enum
|
||||
from typing import Dict, List
|
||||
|
||||
from dcs.unittype import VehicleType
|
||||
|
||||
from game.theater import ControlPoint
|
||||
|
||||
from game.data.groundunitclass import GroundUnitClass
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from game.theater import ControlPoint
|
||||
from gen.ground_forces.combat_stance import CombatStance
|
||||
|
||||
MAX_COMBAT_GROUP_PER_CP = 10
|
||||
@ -48,17 +46,19 @@ GROUP_SIZES_BY_COMBAT_STANCE = {
|
||||
|
||||
|
||||
class CombatGroup:
|
||||
def __init__(self, role: CombatGroupRole):
|
||||
self.units: List[VehicleType] = []
|
||||
def __init__(
|
||||
self, role: CombatGroupRole, unit_type: GroundUnitType, size: int
|
||||
) -> None:
|
||||
self.unit_type = unit_type
|
||||
self.size = size
|
||||
self.role = role
|
||||
self.assigned_enemy_cp = None
|
||||
self.start_position = None
|
||||
|
||||
def __str__(self):
|
||||
s = ""
|
||||
s += "ROLE : " + str(self.role) + "\n"
|
||||
if len(self.units) > 0:
|
||||
s += "UNITS " + self.units[0].name + " * " + str(len(self.units))
|
||||
s = f"ROLE : {self.role}\n"
|
||||
if self.size:
|
||||
s += f"UNITS {self.unit_type} * {self.size}"
|
||||
return s
|
||||
|
||||
|
||||
@ -97,28 +97,29 @@ class GroundPlanner:
|
||||
|
||||
# Create combat groups and assign them randomly to each enemy CP
|
||||
for unit_type in self.cp.base.armor:
|
||||
if unit_type in GroundUnitClass.Tank:
|
||||
unit_class = unit_type.unit_class
|
||||
if unit_class is GroundUnitClass.Tank:
|
||||
collection = self.tank_groups
|
||||
role = CombatGroupRole.TANK
|
||||
elif unit_type in GroundUnitClass.Apc:
|
||||
elif unit_class is GroundUnitClass.Apc:
|
||||
collection = self.apc_group
|
||||
role = CombatGroupRole.APC
|
||||
elif unit_type in GroundUnitClass.Artillery:
|
||||
elif unit_class is GroundUnitClass.Artillery:
|
||||
collection = self.art_group
|
||||
role = CombatGroupRole.ARTILLERY
|
||||
elif unit_type in GroundUnitClass.Ifv:
|
||||
elif unit_class is GroundUnitClass.Ifv:
|
||||
collection = self.ifv_group
|
||||
role = CombatGroupRole.IFV
|
||||
elif unit_type in GroundUnitClass.Logistics:
|
||||
elif unit_class is GroundUnitClass.Logistics:
|
||||
collection = self.logi_groups
|
||||
role = CombatGroupRole.LOGI
|
||||
elif unit_type in GroundUnitClass.Atgm:
|
||||
elif unit_class is GroundUnitClass.Atgm:
|
||||
collection = self.atgm_group
|
||||
role = CombatGroupRole.ATGM
|
||||
elif unit_type in GroundUnitClass.Shorads:
|
||||
elif unit_class is GroundUnitClass.Shorads:
|
||||
collection = self.shorad_groups
|
||||
role = CombatGroupRole.SHORAD
|
||||
elif unit_type in GroundUnitClass.Recon:
|
||||
elif unit_class is GroundUnitClass.Recon:
|
||||
collection = self.recon_groups
|
||||
role = CombatGroupRole.RECON
|
||||
else:
|
||||
@ -137,17 +138,17 @@ class GroundPlanner:
|
||||
while available > 0:
|
||||
|
||||
if role == CombatGroupRole.SHORAD:
|
||||
n = 1
|
||||
count = 1
|
||||
else:
|
||||
n = random.choice(group_size_choice)
|
||||
if n > available:
|
||||
count = random.choice(group_size_choice)
|
||||
if count > available:
|
||||
if available >= 2:
|
||||
n = 2
|
||||
count = 2
|
||||
else:
|
||||
n = 1
|
||||
available -= n
|
||||
count = 1
|
||||
available -= count
|
||||
|
||||
group = CombatGroup(role)
|
||||
group = CombatGroup(role, unit_type, count)
|
||||
if len(self.connected_enemy_cp) > 0:
|
||||
enemy_cp = random.choice(self.connected_enemy_cp).id
|
||||
self.units_per_cp[enemy_cp].append(group)
|
||||
@ -155,9 +156,6 @@ class GroundPlanner:
|
||||
else:
|
||||
self.reserve.append(group)
|
||||
group.assigned_enemy_cp = "__reserve__"
|
||||
|
||||
for i in range(n):
|
||||
group.units.append(unit_type)
|
||||
collection.append(group)
|
||||
|
||||
if remaining_available_frontline_units == 0:
|
||||
|
||||
@ -3,11 +3,9 @@ import time
|
||||
from typing import List
|
||||
|
||||
from dcs.country import Country
|
||||
from dcs.unittype import UnitType
|
||||
|
||||
from game import db
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
|
||||
from game.dcs.unittype import UnitType
|
||||
from gen.flights.flight import Flight
|
||||
|
||||
ALPHA_MILITARY = [
|
||||
@ -298,7 +296,7 @@ class NameGenerator:
|
||||
def next_unit_name(cls, country: Country, parent_base_id: int, unit_type: UnitType):
|
||||
cls.number += 1
|
||||
return "unit|{}|{}|{}|{}|".format(
|
||||
country.id, cls.number, parent_base_id, db.unit_type_name(unit_type)
|
||||
country.id, cls.number, parent_base_id, unit_type.name
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@ -310,7 +308,7 @@ class NameGenerator:
|
||||
country.id,
|
||||
cls.infantry_number,
|
||||
parent_base_id,
|
||||
db.unit_type_name(unit_type),
|
||||
unit_type.name,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@ -10,7 +10,8 @@ from dcs.unit import Unit
|
||||
from dcs.vehicles import vehicle_map
|
||||
from shapely.geometry import LineString, Point as ShapelyPoint, Polygon, MultiPolygon
|
||||
|
||||
from game import Game, db
|
||||
from game import Game
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from game.navmesh import NavMesh
|
||||
from game.profiling import logged_duration
|
||||
from game.theater import (
|
||||
@ -191,12 +192,6 @@ class GroundObjectJs(QObject):
|
||||
self.game = game
|
||||
self.theater = game.theater
|
||||
self.buildings = self.theater.find_ground_objects_by_obj_name(self.tgo.obj_name)
|
||||
|
||||
if self.tgo.is_friendly(to_player=True):
|
||||
self.country = game.player_country
|
||||
else:
|
||||
self.country = game.enemy_country
|
||||
|
||||
self.dialog: Optional[QGroundObjectMenu] = None
|
||||
|
||||
@Slot()
|
||||
@ -223,14 +218,15 @@ class GroundObjectJs(QObject):
|
||||
def category(self) -> str:
|
||||
return self.tgo.category
|
||||
|
||||
def make_unit_name(self, unit: Unit, dead: bool) -> str:
|
||||
@staticmethod
|
||||
def make_unit_name(unit: Unit, dead: bool) -> str:
|
||||
dead_label = " [DEAD]" if dead else ""
|
||||
unit_display_name = unit.type
|
||||
unit_type = vehicle_map.get(unit.type)
|
||||
if unit_type is not None:
|
||||
unit_display_name = db.unit_get_expanded_info(
|
||||
self.country, unit_type, "name"
|
||||
)
|
||||
dcs_unit_type = vehicle_map.get(unit.type)
|
||||
if dcs_unit_type is not None:
|
||||
# TODO: Make the TGO contain GroundUnitType instead of the pydcs Group.
|
||||
# This is a hack because we can't know which variant was used.
|
||||
unit_display_name = next(GroundUnitType.for_dcs_type(dcs_unit_type)).name
|
||||
return f"Unit #{unit.id} - {unit_display_name}{dead_label}"
|
||||
|
||||
@Property(list, notify=unitsChanged)
|
||||
|
||||
@ -11,7 +11,6 @@ from PySide2.QtWidgets import (
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
from game import db
|
||||
from game.debriefing import Debriefing
|
||||
|
||||
|
||||
@ -24,25 +23,19 @@ class LossGrid(QGridLayout):
|
||||
|
||||
self.add_loss_rows(debriefing.air_losses.by_type(player), lambda u: u.name)
|
||||
self.add_loss_rows(
|
||||
debriefing.front_line_losses_by_type(player),
|
||||
lambda u: db.unit_type_name(u),
|
||||
debriefing.front_line_losses_by_type(player), lambda u: str(u)
|
||||
)
|
||||
self.add_loss_rows(
|
||||
debriefing.convoy_losses_by_type(player),
|
||||
lambda u: f"{db.unit_type_name(u)} from convoy",
|
||||
debriefing.convoy_losses_by_type(player), lambda u: f"{u} from convoy"
|
||||
)
|
||||
self.add_loss_rows(
|
||||
debriefing.cargo_ship_losses_by_type(player),
|
||||
lambda u: f"{db.unit_type_name(u)} from cargo ship",
|
||||
lambda u: f"{u} from cargo ship",
|
||||
)
|
||||
self.add_loss_rows(
|
||||
debriefing.airlift_losses_by_type(player),
|
||||
lambda u: f"{db.unit_type_name(u)} from airlift",
|
||||
)
|
||||
self.add_loss_rows(
|
||||
debriefing.building_losses_by_type(player),
|
||||
lambda u: u,
|
||||
debriefing.airlift_losses_by_type(player), lambda u: f"{u} from airlift"
|
||||
)
|
||||
self.add_loss_rows(debriefing.building_losses_by_type(player), lambda u: u)
|
||||
|
||||
# TODO: Display dead ground object units and runways.
|
||||
|
||||
|
||||
@ -1,9 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Type, Union
|
||||
|
||||
import dcs
|
||||
from PySide2.QtCore import Qt
|
||||
from PySide2.QtGui import QIcon
|
||||
from PySide2.QtWidgets import (
|
||||
@ -13,79 +9,23 @@ from PySide2.QtWidgets import (
|
||||
QTextBrowser,
|
||||
QFrame,
|
||||
)
|
||||
from dcs.unittype import VehicleType
|
||||
|
||||
import gen.flights.ai_flight_planner_db
|
||||
from game import db
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from game.dcs.unittype import UnitType
|
||||
from game.game import Game
|
||||
from gen.flights.flight import FlightType
|
||||
from qt_ui.uiconstants import AIRCRAFT_BANNERS, VEHICLE_BANNERS
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class UnitInfo:
|
||||
name: str
|
||||
description: str
|
||||
introduction_year: str
|
||||
origin: str
|
||||
manufacturer: str
|
||||
role: str
|
||||
|
||||
@classmethod
|
||||
def from_unit_type(
|
||||
cls, country: str, unit_type: Union[AircraftType, Type[VehicleType]]
|
||||
) -> UnitInfo:
|
||||
if isinstance(unit_type, AircraftType):
|
||||
return cls.from_aircraft(unit_type)
|
||||
else:
|
||||
return cls.from_vehicle_type(country, unit_type)
|
||||
|
||||
@classmethod
|
||||
def from_aircraft(cls, aircraft: AircraftType) -> UnitInfo:
|
||||
return UnitInfo(
|
||||
aircraft.name,
|
||||
aircraft.description,
|
||||
aircraft.year_introduced,
|
||||
aircraft.country_of_origin,
|
||||
aircraft.manufacturer,
|
||||
aircraft.role,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_vehicle_type(cls, country: str, unit_type: Type[VehicleType]) -> UnitInfo:
|
||||
name = db.unit_get_expanded_info(country, unit_type, "name")
|
||||
manufacturer = db.unit_get_expanded_info(country, unit_type, "manufacturer")
|
||||
origin = db.unit_get_expanded_info(country, unit_type, "country-of-origin")
|
||||
role = db.unit_get_expanded_info(country, unit_type, "role")
|
||||
introduction = db.unit_get_expanded_info(
|
||||
country, unit_type, "year-of-variant-introduction"
|
||||
)
|
||||
description = db.unit_get_expanded_info(country, unit_type, "text")
|
||||
return UnitInfo(
|
||||
name,
|
||||
description,
|
||||
introduction,
|
||||
origin,
|
||||
manufacturer,
|
||||
role,
|
||||
)
|
||||
|
||||
|
||||
class QUnitInfoWindow(QDialog):
|
||||
def __init__(
|
||||
self, game: Game, unit_type: Union[AircraftType, Type[VehicleType]]
|
||||
) -> None:
|
||||
def __init__(self, game: Game, unit_type: UnitType) -> None:
|
||||
super().__init__()
|
||||
self.setModal(True)
|
||||
self.game = game
|
||||
self.unit_type = unit_type
|
||||
if isinstance(unit_type, AircraftType):
|
||||
self.name = unit_type.name
|
||||
else:
|
||||
self.name = db.unit_get_expanded_info(
|
||||
self.game.player_country, self.unit_type, "name"
|
||||
)
|
||||
self.name = unit_type.name
|
||||
self.setWindowTitle(f"Unit Info: {self.name}")
|
||||
self.setWindowIcon(QIcon("./resources/icon.png"))
|
||||
self.setMinimumHeight(570)
|
||||
@ -101,8 +41,8 @@ class QUnitInfoWindow(QDialog):
|
||||
|
||||
if isinstance(self.unit_type, AircraftType):
|
||||
pixmap = AIRCRAFT_BANNERS.get(self.unit_type.dcs_id)
|
||||
elif dcs.vehicles.vehicle_map.get(self.unit_type.id) is not None:
|
||||
pixmap = VEHICLE_BANNERS.get(self.unit_type.id)
|
||||
elif isinstance(self.unit_type, GroundUnitType):
|
||||
pixmap = VEHICLE_BANNERS.get(self.unit_type.dcs_id)
|
||||
if pixmap is None:
|
||||
pixmap = AIRCRAFT_BANNERS.get("Missing")
|
||||
header.setPixmap(pixmap.scaled(header.width(), header.height()))
|
||||
@ -115,20 +55,21 @@ class QUnitInfoWindow(QDialog):
|
||||
self.details_grid_layout = QGridLayout()
|
||||
self.details_grid_layout.setMargin(0)
|
||||
|
||||
unit_info = UnitInfo.from_unit_type(self.game.player_country, self.unit_type)
|
||||
self.name_box = QLabel(
|
||||
f"<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.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.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.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")
|
||||
|
||||
@ -152,7 +93,7 @@ class QUnitInfoWindow(QDialog):
|
||||
# Finally, add the description box.
|
||||
self.details_text = QTextBrowser()
|
||||
self.details_text.setProperty("style", "info-desc")
|
||||
self.details_text.setText(unit_info.description)
|
||||
self.details_text.setText(unit_type.description)
|
||||
self.gridLayout.addWidget(self.details_text, 3, 0)
|
||||
|
||||
self.layout.addLayout(self.gridLayout, 1, 0)
|
||||
|
||||
@ -10,7 +10,6 @@ from PySide2.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from game import db
|
||||
from game.theater import ControlPoint
|
||||
from game.transfers import MultiGroupTransport
|
||||
from qt_ui.dialogs import Dialog
|
||||
@ -19,7 +18,7 @@ from qt_ui.uiconstants import VEHICLES_ICONS
|
||||
|
||||
|
||||
class DepartingConvoyInfo(QGroupBox):
|
||||
def __init__(self, convoy: MultiGroupTransport, game_model: GameModel) -> None:
|
||||
def __init__(self, convoy: MultiGroupTransport) -> None:
|
||||
super().__init__(f"{convoy.name} to {convoy.destination}")
|
||||
self.convoy = convoy
|
||||
|
||||
@ -31,17 +30,14 @@ class DepartingConvoyInfo(QGroupBox):
|
||||
|
||||
for idx, (unit_type, count) in enumerate(convoy.units.items()):
|
||||
icon = QLabel()
|
||||
if unit_type.id in VEHICLES_ICONS.keys():
|
||||
icon.setPixmap(VEHICLES_ICONS[unit_type.id])
|
||||
if unit_type.dcs_id in VEHICLES_ICONS.keys():
|
||||
icon.setPixmap(VEHICLES_ICONS[unit_type.dcs_id])
|
||||
else:
|
||||
icon.setText("<b>" + unit_type.id[:8] + "</b>")
|
||||
icon.setProperty("style", "icon-armor")
|
||||
unit_layout.addWidget(icon, idx, 0)
|
||||
unit_display_name = db.unit_get_expanded_info(
|
||||
game_model.game.enemy_country, unit_type, "name"
|
||||
)
|
||||
unit_layout.addWidget(
|
||||
QLabel(f"{count} x <strong>{unit_display_name}</strong>"),
|
||||
QLabel(f"{count} x <strong>{unit_type.name}</strong>"),
|
||||
idx,
|
||||
1,
|
||||
)
|
||||
@ -68,7 +64,6 @@ class DepartingConvoysList(QFrame):
|
||||
def __init__(self, cp: ControlPoint, game_model: GameModel):
|
||||
super().__init__()
|
||||
self.cp = cp
|
||||
self.game_model = game_model
|
||||
self.setMinimumWidth(500)
|
||||
|
||||
layout = QVBoxLayout()
|
||||
@ -79,11 +74,11 @@ class DepartingConvoysList(QFrame):
|
||||
scroll_content.setLayout(task_box_layout)
|
||||
|
||||
for convoy in game_model.game.transfers.convoys.departing_from(cp):
|
||||
group_info = DepartingConvoyInfo(convoy, game_model)
|
||||
group_info = DepartingConvoyInfo(convoy)
|
||||
task_box_layout.addWidget(group_info)
|
||||
|
||||
for cargo_ship in game_model.game.transfers.cargo_ships.departing_from(cp):
|
||||
group_info = DepartingConvoyInfo(cargo_ship, game_model)
|
||||
group_info = DepartingConvoyInfo(cargo_ship)
|
||||
task_box_layout.addWidget(group_info)
|
||||
|
||||
scroll_content.setLayout(task_box_layout)
|
||||
|
||||
@ -20,10 +20,10 @@ from PySide2.QtWidgets import (
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
from dcs.task import PinpointStrike
|
||||
from dcs.unittype import UnitType
|
||||
|
||||
from game import Game, db
|
||||
from game import Game
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from game.theater import ControlPoint
|
||||
from game.transfers import TransferOrder
|
||||
from qt_ui.models import GameModel
|
||||
@ -63,12 +63,7 @@ class UnitTransferList(QFrame):
|
||||
task_box_layout = QGridLayout()
|
||||
scroll_content.setLayout(task_box_layout)
|
||||
|
||||
units_column = sorted(
|
||||
cp.base.armor,
|
||||
key=lambda u: db.unit_get_expanded_info(
|
||||
self.game_model.game.player_country, u, "name"
|
||||
),
|
||||
)
|
||||
units_column = sorted(cp.base.armor, key=lambda u: u.name)
|
||||
|
||||
count = 0
|
||||
for count, unit_type in enumerate(units_column):
|
||||
@ -169,9 +164,7 @@ class ScrollingUnitTransferGrid(QFrame):
|
||||
unit_types = set(self.game_model.game.faction_for(player=True).ground_units)
|
||||
sorted_units = sorted(
|
||||
{u for u in unit_types if self.cp.base.total_units_of_type(u)},
|
||||
key=lambda u: db.unit_get_expanded_info(
|
||||
self.game_model.game.player_country, u, "name"
|
||||
),
|
||||
key=lambda u: u.name,
|
||||
)
|
||||
for row, unit_type in enumerate(sorted_units):
|
||||
self.add_unit_row(unit_type, task_box_layout, row)
|
||||
@ -190,7 +183,7 @@ class ScrollingUnitTransferGrid(QFrame):
|
||||
|
||||
def add_unit_row(
|
||||
self,
|
||||
unit_type: Type[UnitType],
|
||||
unit_type: GroundUnitType,
|
||||
layout: QGridLayout,
|
||||
row: int,
|
||||
) -> None:
|
||||
@ -203,13 +196,7 @@ class ScrollingUnitTransferGrid(QFrame):
|
||||
|
||||
origin_inventory = self.cp.base.total_units_of_type(unit_type)
|
||||
|
||||
unit_name = QLabel(
|
||||
"<b>"
|
||||
+ db.unit_get_expanded_info(
|
||||
self.game_model.game.player_country, unit_type, "name"
|
||||
)
|
||||
+ "</b>"
|
||||
)
|
||||
unit_name = QLabel(f"<b>{unit_type.name}</b>")
|
||||
unit_name.setSizePolicy(
|
||||
QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
)
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import logging
|
||||
from typing import Type, Union
|
||||
|
||||
from PySide2.QtWidgets import (
|
||||
QGroupBox,
|
||||
@ -10,9 +9,8 @@ from PySide2.QtWidgets import (
|
||||
QSizePolicy,
|
||||
QSpacerItem,
|
||||
)
|
||||
from dcs.unittype import VehicleType
|
||||
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
from game.dcs.unittype import UnitType
|
||||
from game.theater import ControlPoint
|
||||
from game.unitdelivery import PendingUnitDeliveries
|
||||
from qt_ui.models import GameModel
|
||||
@ -38,7 +36,7 @@ class QRecruitBehaviour:
|
||||
return self.cp.pending_unit_deliveries
|
||||
|
||||
@property
|
||||
def budget(self) -> int:
|
||||
def budget(self) -> float:
|
||||
return self.game_model.game.budget
|
||||
|
||||
@budget.setter
|
||||
@ -47,7 +45,7 @@ class QRecruitBehaviour:
|
||||
|
||||
def add_purchase_row(
|
||||
self,
|
||||
unit_type: Union[AircraftType, Type[VehicleType]],
|
||||
unit_type: UnitType,
|
||||
layout: QLayout,
|
||||
row: int,
|
||||
) -> int:
|
||||
@ -61,7 +59,7 @@ class QRecruitBehaviour:
|
||||
existing_units = self.cp.base.total_units_of_type(unit_type)
|
||||
scheduled_units = self.pending_deliveries.units.get(unit_type, 0)
|
||||
|
||||
unitName = QLabel(f"<b>{self.name_of(unit_type)}</b>")
|
||||
unitName = QLabel(f"<b>{unit_type.name}</b>")
|
||||
unitName.setSizePolicy(
|
||||
QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
)
|
||||
@ -75,7 +73,7 @@ class QRecruitBehaviour:
|
||||
self.existing_units_labels[unit_type] = existing_units
|
||||
self.bought_amount_labels[unit_type] = amount_bought
|
||||
|
||||
price = QLabel(f"<b>$ {self.price_of(unit_type)}</b> M")
|
||||
price = QLabel(f"<b>$ {unit_type.price}</b> M")
|
||||
price.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
|
||||
|
||||
buysell = QGroupBox()
|
||||
@ -149,8 +147,7 @@ class QRecruitBehaviour:
|
||||
|
||||
return row + 1
|
||||
|
||||
def _update_count_label(self, unit_type: Union[AircraftType, Type[VehicleType]]):
|
||||
|
||||
def _update_count_label(self, unit_type: UnitType) -> None:
|
||||
self.bought_amount_labels[unit_type].setText(
|
||||
"<b>{}</b>".format(
|
||||
unit_type in self.pending_deliveries.units
|
||||
@ -166,34 +163,32 @@ class QRecruitBehaviour:
|
||||
def update_available_budget(self) -> None:
|
||||
GameUpdateSignal.get_instance().updateBudget(self.game_model.game)
|
||||
|
||||
def buy(self, unit_type: Union[AircraftType, Type[VehicleType]]):
|
||||
def buy(self, unit_type: UnitType) -> None:
|
||||
if not self.enable_purchase(unit_type):
|
||||
logging.error(f"Purchase of {unit_type.id} not allowed at {self.cp.name}")
|
||||
logging.error(f"Purchase of {unit_type} not allowed at {self.cp.name}")
|
||||
return
|
||||
|
||||
self.pending_deliveries.order({unit_type: 1})
|
||||
self.budget -= self.price_of(unit_type)
|
||||
self.budget -= unit_type.price
|
||||
self._update_count_label(unit_type)
|
||||
self.update_available_budget()
|
||||
|
||||
def sell(self, unit_type):
|
||||
def sell(self, unit_type: UnitType) -> None:
|
||||
if self.pending_deliveries.available_next_turn(unit_type) > 0:
|
||||
self.budget += self.price_of(unit_type)
|
||||
self.budget += unit_type.price
|
||||
self.pending_deliveries.sell({unit_type: 1})
|
||||
if self.pending_deliveries.units[unit_type] == 0:
|
||||
del self.pending_deliveries.units[unit_type]
|
||||
self._update_count_label(unit_type)
|
||||
self.update_available_budget()
|
||||
|
||||
def enable_purchase(
|
||||
self, unit_type: Union[AircraftType, Type[VehicleType]]
|
||||
) -> bool:
|
||||
return self.budget >= self.price_of(unit_type)
|
||||
def enable_purchase(self, unit_type: UnitType) -> bool:
|
||||
return self.budget >= unit_type.price
|
||||
|
||||
def enable_sale(self, unit_type: Union[AircraftType, Type[VehicleType]]) -> bool:
|
||||
def enable_sale(self, unit_type: UnitType) -> bool:
|
||||
return True
|
||||
|
||||
def info(self, unit_type):
|
||||
def info(self, unit_type: UnitType) -> None:
|
||||
self.info_window = QUnitInfoWindow(self.game_model.game, unit_type)
|
||||
self.info_window.show()
|
||||
|
||||
@ -202,9 +197,3 @@ class QRecruitBehaviour:
|
||||
Set the maximum number of units that can be bought
|
||||
"""
|
||||
self.maximum_units = maximum_units
|
||||
|
||||
def name_of(self, unit_type: Union[AircraftType, Type[VehicleType]]) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def price_of(self, unit_type: Union[AircraftType, Type[VehicleType]]) -> int:
|
||||
raise NotImplementedError
|
||||
|
||||
@ -92,12 +92,6 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour):
|
||||
return False
|
||||
return True
|
||||
|
||||
def name_of(self, unit_type: AircraftType) -> str:
|
||||
return unit_type.name
|
||||
|
||||
def price_of(self, unit_type: AircraftType) -> int:
|
||||
return unit_type.price
|
||||
|
||||
def buy(self, unit_type: AircraftType) -> None:
|
||||
if self.maximum_units > 0:
|
||||
if self.cp.unclaimed_parking(self.game_model.game) <= 0:
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
from typing import Type
|
||||
|
||||
from PySide2.QtCore import Qt
|
||||
from PySide2.QtWidgets import (
|
||||
QFrame,
|
||||
@ -8,10 +6,8 @@ from PySide2.QtWidgets import (
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
from dcs.unittype import UnitType, VehicleType
|
||||
|
||||
from game import db
|
||||
from game.db import PRICES
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from game.theater import ControlPoint
|
||||
from qt_ui.models import GameModel
|
||||
from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour
|
||||
@ -39,11 +35,7 @@ class QArmorRecruitmentMenu(QFrame, QRecruitBehaviour):
|
||||
unit_types = list(
|
||||
set(self.game_model.game.faction_for(player=True).ground_units)
|
||||
)
|
||||
unit_types.sort(
|
||||
key=lambda u: db.unit_get_expanded_info(
|
||||
self.game_model.game.player_country, u, "name"
|
||||
)
|
||||
)
|
||||
unit_types.sort(key=lambda u: u.name)
|
||||
for unit_type in unit_types:
|
||||
row = self.add_purchase_row(unit_type, task_box_layout, row)
|
||||
stretch = QVBoxLayout()
|
||||
@ -59,18 +51,10 @@ class QArmorRecruitmentMenu(QFrame, QRecruitBehaviour):
|
||||
main_layout.addWidget(scroll)
|
||||
self.setLayout(main_layout)
|
||||
|
||||
def enable_purchase(self, unit_type: Type[UnitType]) -> bool:
|
||||
def enable_purchase(self, unit_type: GroundUnitType) -> bool:
|
||||
if not super().enable_purchase(unit_type):
|
||||
return False
|
||||
return self.cp.has_ground_unit_source(self.game_model.game)
|
||||
|
||||
def enable_sale(self, unit_type: Type[UnitType]) -> bool:
|
||||
def enable_sale(self, unit_type: GroundUnitType) -> bool:
|
||||
return self.pending_deliveries.pending_orders(unit_type) > 0
|
||||
|
||||
def name_of(self, unit_type: Type[VehicleType]) -> str:
|
||||
return db.unit_get_expanded_info(
|
||||
self.game_model.game.player_country, unit_type, "name"
|
||||
)
|
||||
|
||||
def price_of(self, unit_type: Type[VehicleType]) -> int:
|
||||
return PRICES[unit_type]
|
||||
|
||||
@ -11,7 +11,7 @@ from PySide2.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from game import Game, db
|
||||
from game import Game
|
||||
from game.theater import ControlPoint
|
||||
|
||||
|
||||
@ -38,10 +38,7 @@ class QIntelInfo(QFrame):
|
||||
front_line_units = defaultdict(int)
|
||||
for unit_type, count in self.cp.base.armor.items():
|
||||
if count:
|
||||
name = db.unit_get_expanded_info(
|
||||
self.game.enemy_country, unit_type, "name"
|
||||
)
|
||||
front_line_units[name] += count
|
||||
front_line_units[unit_type.name] += count
|
||||
|
||||
units_by_task["Front line units"] = front_line_units
|
||||
for task, unit_types in units_by_task.items():
|
||||
|
||||
@ -20,7 +20,8 @@ from dcs import vehicles
|
||||
|
||||
from game import Game, db
|
||||
from game.data.building_data import FORTIFICATION_BUILDINGS
|
||||
from game.db import PRICES, REWARDS, unit_type_of
|
||||
from game.db import REWARDS
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from game.theater import ControlPoint, TheaterGroundObject
|
||||
from game.theater.theatergroundobject import (
|
||||
VehicleGroupGroundObject,
|
||||
@ -108,17 +109,18 @@ class QGroundObjectMenu(QDialog):
|
||||
for g in self.ground_object.groups:
|
||||
if not hasattr(g, "units_losts"):
|
||||
g.units_losts = []
|
||||
for u in g.units:
|
||||
unit_display_name = u.type
|
||||
unit_type = vehicles.vehicle_map.get(u.type)
|
||||
if unit_type is not None:
|
||||
unit_display_name = db.unit_get_expanded_info(
|
||||
self.game.enemy_country, unit_type, "name"
|
||||
)
|
||||
for unit in g.units:
|
||||
unit_display_name = unit.type
|
||||
dcs_unit_type = vehicles.vehicle_map.get(unit.type)
|
||||
if dcs_unit_type is not None:
|
||||
# Hack: Don't know which variant is used.
|
||||
unit_display_name = next(
|
||||
GroundUnitType.for_dcs_type(dcs_unit_type)
|
||||
).name
|
||||
self.intelLayout.addWidget(
|
||||
QLabel(
|
||||
"<b>Unit #"
|
||||
+ str(u.id)
|
||||
+ str(unit.id)
|
||||
+ " - "
|
||||
+ str(unit_display_name)
|
||||
+ "</b>"
|
||||
@ -128,26 +130,30 @@ class QGroundObjectMenu(QDialog):
|
||||
)
|
||||
i = i + 1
|
||||
|
||||
for u in g.units_losts:
|
||||
for unit in g.units_losts:
|
||||
dcs_unit_type = vehicles.vehicle_map.get(unit.type)
|
||||
if dcs_unit_type is None:
|
||||
continue
|
||||
|
||||
utype = unit_type_of(u)
|
||||
if utype in PRICES:
|
||||
price = PRICES[utype]
|
||||
else:
|
||||
price = 6
|
||||
# Hack: Don't know which variant is used.
|
||||
unit_type = next(GroundUnitType.for_dcs_type(dcs_unit_type))
|
||||
|
||||
self.intelLayout.addWidget(
|
||||
QLabel(
|
||||
"<b>Unit #" + str(u.id) + " - " + str(u.type) + "</b> [DEAD]"
|
||||
"<b>Unit #"
|
||||
+ str(unit.id)
|
||||
+ " - "
|
||||
+ str(unit_type)
|
||||
+ "</b> [DEAD]"
|
||||
),
|
||||
i,
|
||||
0,
|
||||
)
|
||||
if self.cp.captured:
|
||||
repair = QPushButton("Repair [" + str(price) + "M]")
|
||||
repair = QPushButton(f"Repair [{unit_type.price}M]")
|
||||
repair.setProperty("style", "btn-success")
|
||||
repair.clicked.connect(
|
||||
lambda u=u, g=g, p=price: self.repair_unit(g, u, p)
|
||||
lambda u=unit, g=g, p=unit_type.price: self.repair_unit(g, u, p)
|
||||
)
|
||||
self.intelLayout.addWidget(repair, i, 1)
|
||||
i = i + 1
|
||||
@ -217,13 +223,12 @@ class QGroundObjectMenu(QDialog):
|
||||
|
||||
def update_total_value(self):
|
||||
total_value = 0
|
||||
for group in self.ground_object.groups:
|
||||
for u in group.units:
|
||||
utype = unit_type_of(u)
|
||||
if utype in PRICES:
|
||||
total_value = total_value + PRICES[utype]
|
||||
else:
|
||||
total_value = total_value + 1
|
||||
if not self.ground_object.purchasable:
|
||||
return
|
||||
for u in self.ground_object.units:
|
||||
# Hack: Unknown variant.
|
||||
unit_type = next(GroundUnitType.for_dcs_type(vehicles.vehicle_map[u.type]))
|
||||
total_value += unit_type.price
|
||||
if self.sell_all_button is not None:
|
||||
self.sell_all_button.setText("Disband (+$" + str(self.total_value) + "M)")
|
||||
self.total_value = total_value
|
||||
@ -340,10 +345,7 @@ class QBuyGroupForGroundObjectDialog(QDialog):
|
||||
|
||||
# Armored units
|
||||
for unit in set(faction.ground_units):
|
||||
self.buyArmorCombo.addItem(
|
||||
db.unit_type_name_2(unit) + " [$" + str(db.PRICES[unit]) + "M]",
|
||||
userData=unit,
|
||||
)
|
||||
self.buyArmorCombo.addItem(f"{unit} [${unit.price}M]", userData=unit)
|
||||
self.buyArmorCombo.currentIndexChanged.connect(self.armorComboChanged)
|
||||
|
||||
self.amount.setMinimum(2)
|
||||
@ -404,33 +406,19 @@ class QBuyGroupForGroundObjectDialog(QDialog):
|
||||
)
|
||||
|
||||
def armorComboChanged(self, index):
|
||||
self.buyArmorButton.setText(
|
||||
"Buy [$"
|
||||
+ str(db.PRICES[self.buyArmorCombo.itemData(index)] * self.amount.value())
|
||||
+ "M][-$"
|
||||
+ str(self.current_group_value)
|
||||
+ "M]"
|
||||
)
|
||||
unit_type = self.buyArmorCombo.itemData(self.buyArmorCombo.currentIndex())
|
||||
price = unit_type.price * self.amount.value()
|
||||
self.buyArmorButton.setText(f"Buy [${price}M][-${self.current_group_value}M]")
|
||||
|
||||
def amountComboChanged(self):
|
||||
self.buyArmorButton.setText(
|
||||
"Buy [$"
|
||||
+ str(
|
||||
db.PRICES[
|
||||
self.buyArmorCombo.itemData(self.buyArmorCombo.currentIndex())
|
||||
]
|
||||
* self.amount.value()
|
||||
)
|
||||
+ "M][-$"
|
||||
+ str(self.current_group_value)
|
||||
+ "M]"
|
||||
)
|
||||
unit_type = self.buyArmorCombo.itemData(self.buyArmorCombo.currentIndex())
|
||||
price = unit_type.price * self.amount.value()
|
||||
self.buyArmorButton.setText(f"Buy [${price}M][-${self.current_group_value}M]")
|
||||
|
||||
def buyArmor(self):
|
||||
logging.info("Buying Armor ")
|
||||
utype = self.buyArmorCombo.itemData(self.buyArmorCombo.currentIndex())
|
||||
logging.info(utype)
|
||||
price = db.PRICES[utype] * self.amount.value() - self.current_group_value
|
||||
price = utype.price * self.amount.value() - self.current_group_value
|
||||
if price > self.game.budget:
|
||||
self.error_money()
|
||||
self.close()
|
||||
|
||||
@ -15,7 +15,7 @@ from PySide2.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from game.game import Game, db
|
||||
from game.game import Game
|
||||
from qt_ui.uiconstants import ICONS
|
||||
from qt_ui.windows.finances.QFinancesMenu import FinancesLayout
|
||||
|
||||
@ -111,7 +111,7 @@ class ArmyIntelLayout(IntelTableLayout):
|
||||
for vehicle, count in base.armor.items():
|
||||
if not count:
|
||||
continue
|
||||
self.add_row(vehicle.id, count)
|
||||
self.add_row(vehicle.name, count)
|
||||
|
||||
self.add_spacer()
|
||||
self.add_row("<b>Total</b>", total)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user