Merge branch 'develop' into helipads

This commit is contained in:
Khopa 2021-06-08 13:09:11 +02:00
commit e00ca5d096
18 changed files with 133 additions and 901 deletions

View File

@ -14,7 +14,7 @@
DCS Liberation is a [DCS World](https://www.digitalcombatsimulator.com/en/products/world/) turn based single-player or co-op dynamic campaign.
It is an external program that generates full and complex DCS missions and manage a persistent combat environment.
![Logo](https://i.imgur.com/4hq0rLq.png)
![Screenshot](https://user-images.githubusercontent.com/315852/120939254-0b4a9f80-c6cc-11eb-82f5-ce3f8d714bfe.png)
## Downloads

View File

@ -2,7 +2,7 @@ import json
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Dict, List, Optional, Tuple, Type, Union
from typing import List, Optional, Type, Union
from dcs.countries import country_dict
from dcs.helicopters import (
@ -11,7 +11,6 @@ from dcs.helicopters import (
AH_64D,
CH_47D,
CH_53E,
HelicopterType,
Ka_50,
Mi_24V,
Mi_26,
@ -132,29 +131,21 @@ from dcs.ships import (
)
from dcs.task import (
AWACS,
AntishipStrike,
CAP,
CAS,
CargoTransportation,
Embarking,
Escort,
FighterSweep,
GroundAttack,
Intercept,
MainTask,
Nothing,
PinpointStrike,
Reconnaissance,
Refueling,
SEAD,
Task,
Transport,
RunwayAttack,
)
from dcs.terrain.terrain import Airport
from dcs.unit import Ship, Unit, Vehicle
from dcs.unitgroup import ShipGroup, StaticGroup
from dcs.unittype import FlyingType, ShipType, UnitType, VehicleType
from dcs.unittype import FlyingType, UnitType, VehicleType
from dcs.vehicles import (
AirDefence,
Armor,
@ -724,468 +715,12 @@ PRICES = {
highdigitsams.SAM_SA_17_Buk_M1_2_LN_9A310M1_2: 40,
}
"""
Units separated by tasks. This will include units for both countries. Be advised that unit could only belong to single task!
Following tasks are present:
* CAP - figther aircraft for CAP/Escort/Intercept
* CAS - CAS aircraft
* Transport - transport aircraft (used as targets in intercept operations)
* AWACS - awacs
* AntishipStrike - units that will engage shipping
* PinpointStrike - armor that will engage in ground war
* AirDefense - AA units
* Reconnaissance - units that will be used as targets in destroy insurgents operations
* Nothing - troops that will be used for helicopter transport operations
* Embarking - helicopters that will be used for helicopter transport operations
* Carriage - aircraft carriers
* CargoTransportation - ships that will be used as targets for ship intercept operations
"""
UNIT_BY_TASK = {
CAP: [
A_4E_C,
Bf_109K_4,
C_101CC,
FA_18C_hornet,
FW_190A8,
FW_190D9,
F_14A_135_GR,
F_14B,
F_15C,
F_16A,
F_16C_50,
F_22A,
F_4E,
F_5E_3,
I_16,
JF_17,
J_11A,
M_2000C,
MiG_19P,
MiG_21Bis,
MiG_23MLD,
MiG_25PD,
MiG_29A,
MiG_29G,
MiG_29S,
MiG_31,
Mirage_2000_5,
P_51D,
P_51D_30_NA,
SA342Mistral,
SpitfireLFMkIX,
SpitfireLFMkIXCW,
Su_27,
Su_30,
Su_33,
Su_57,
],
CAS: [
AH_1W,
AH_64A,
AH_64D,
AJS37,
AV8BNA,
A_10A,
A_10C,
A_10C_2,
A_20G,
B_17G,
B_1B,
B_52H,
F_117A,
F_15E,
F_86F_Sabre,
Hercules,
Ju_88A4,
Ka_50,
L_39ZA,
MB_339PAN,
MQ_9_Reaper,
MiG_15bis,
MiG_27K,
Mi_24V,
Mi_28N,
Mi_8MT,
OH_58D,
P_47D_30,
P_47D_30bl1,
P_47D_40,
RQ_1A_Predator,
SA342L,
SA342M,
SA342Minigun,
SH_60B,
S_3B,
Su_17M4,
Su_24M,
Su_24MR,
Su_25,
Su_25T,
Su_34,
Tornado_GR4,
Tornado_IDS,
Tu_160,
Tu_22M3,
Tu_95MS,
UH_1H,
WingLoong_I,
],
Transport: [
An_26B,
An_30M,
CH_47D,
CH_53E,
C_130,
C_17A,
IL_76MD,
Mi_26,
UH_60A,
Yak_40,
],
Refueling: [
IL_78M,
KC130,
KC135MPRS,
KC_135,
S_3B_Tanker,
],
AWACS: [
A_50,
E_2C,
E_3A,
KJ_2000,
],
PinpointStrike: [
Armor.APC_MTLB,
Armor.APC_MTLB,
Armor.APC_MTLB,
Armor.APC_MTLB,
Armor.APC_MTLB,
Artillery.Grad_MRL_FDDM__FC,
Artillery.Grad_MRL_FDDM__FC,
Artillery.Grad_MRL_FDDM__FC,
Artillery.Grad_MRL_FDDM__FC,
Artillery.Grad_MRL_FDDM__FC,
Armor.Scout_BRDM_2,
Armor.Scout_BRDM_2,
Armor.Scout_BRDM_2,
Armor.APC_BTR_RD,
Armor.APC_BTR_RD,
Armor.APC_BTR_RD,
Armor.APC_BTR_RD,
Armor.APC_BTR_80,
Armor.APC_BTR_80,
Armor.APC_BTR_80,
Armor.APC_BTR_80,
Armor.APC_BTR_80,
Armor.IFV_BTR_82A,
Armor.IFV_BTR_82A,
Armor.IFV_BMP_1,
Armor.IFV_BMP_1,
Armor.IFV_BMP_1,
Armor.IFV_BMP_2,
Armor.IFV_BMP_2,
Armor.IFV_BMP_3,
Armor.IFV_BMP_3,
Armor.IFV_BMD_1,
Armor.LT_PT_76,
Armor.ZBD_04A,
Armor.ZBD_04A,
Armor.ZBD_04A,
Armor.MBT_T_55,
Armor.MBT_T_55,
Armor.MBT_T_55,
Armor.MBT_T_72B,
Armor.MBT_T_72B,
Armor.MBT_T_72B3,
Armor.MBT_T_72B3,
Armor.MBT_T_80U,
Armor.MBT_T_80U,
Armor.MBT_T_90,
Armor.ZTZ_96B,
Armor.Scout_Cobra,
Armor.Scout_Cobra,
Armor.Scout_Cobra,
Armor.Scout_Cobra,
Armor.APC_M113,
Armor.APC_M113,
Armor.APC_M113,
Armor.APC_M113,
Armor.APC_TPz_Fuchs,
Armor.APC_TPz_Fuchs,
Armor.APC_TPz_Fuchs,
Armor.APC_TPz_Fuchs,
Armor.ATGM_HMMWV,
Armor.ATGM_HMMWV,
Armor.ATGM_VAB_Mephisto,
Armor.ATGM_VAB_Mephisto,
Armor.Scout_HMMWV,
Armor.Scout_HMMWV,
Armor.IFV_M2A2_Bradley,
Armor.IFV_M2A2_Bradley,
Armor.ATGM_Stryker,
Armor.ATGM_Stryker,
Armor.IFV_M1126_Stryker_ICV,
Armor.IFV_M1126_Stryker_ICV,
Armor.IFV_M1126_Stryker_ICV,
Armor.SPG_Stryker_MGS,
Armor.IFV_Warrior,
Armor.IFV_Warrior,
Armor.IFV_Warrior,
Armor.IFV_LAV_25,
Armor.IFV_LAV_25,
Armor.IFV_Marder,
Armor.IFV_Marder,
Armor.IFV_Marder,
Armor.IFV_Marder,
Armor.MBT_M60A3_Patton,
Armor.MBT_M60A3_Patton,
Armor.MBT_M60A3_Patton,
Armor.MBT_Leopard_1A3,
Armor.MBT_Leopard_1A3,
Armor.MBT_M1A2_Abrams,
Armor.MBT_Leclerc,
Armor.MBT_Leopard_2A6M,
Armor.MBT_Challenger_II,
Armor.MBT_Chieftain_Mk_3,
Armor.MBT_Merkava_IV,
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G,
Armor.Tk_PzIV_H,
Armor.HT_Pz_Kpfw_VI_Tiger_I,
Armor.HT_Pz_Kpfw_VI_Ausf__B_Tiger_II,
Armor.APC_Sd_Kfz_251_Halftrack,
Armor.APC_Sd_Kfz_251_Halftrack,
Armor.APC_Sd_Kfz_251_Halftrack,
Armor.APC_Sd_Kfz_251_Halftrack,
Armor.IFV_Sd_Kfz_234_2_Puma,
Armor.IFV_Sd_Kfz_234_2_Puma,
Armor.Tk_M4_Sherman,
Armor.MT_M4A4_Sherman_Firefly,
Armor.CT_Cromwell_IV,
Unarmed.Carrier_M30_Cargo,
Unarmed.Carrier_M30_Cargo,
Armor.APC_M2A1_Halftrack,
Armor.APC_M2A1_Halftrack,
Armor.APC_M2A1_Halftrack,
Armor.APC_M2A1_Halftrack,
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G,
Armor.Tk_PzIV_H,
Armor.HT_Pz_Kpfw_VI_Tiger_I,
Armor.HT_Pz_Kpfw_VI_Ausf__B_Tiger_II,
Armor.SPG_Jagdpanther_G1,
Armor.SPG_Jagdpanzer_IV,
Armor.SPG_Sd_Kfz_184_Elefant,
Armor.APC_Sd_Kfz_251_Halftrack,
Armor.IFV_Sd_Kfz_234_2_Puma,
Armor.Tk_M4_Sherman,
Armor.MT_M4A4_Sherman_Firefly,
Armor.CT_Cromwell_IV,
Unarmed.Carrier_M30_Cargo,
Unarmed.Carrier_M30_Cargo,
Unarmed.Carrier_M30_Cargo,
Armor.APC_M2A1_Halftrack,
Armor.APC_M2A1_Halftrack,
Armor.CT_Centaur_IV,
Armor.CT_Centaur_IV,
Armor.HIT_Churchill_VII,
Armor.Car_M8_Greyhound_Armored,
Armor.Car_M8_Greyhound_Armored,
Armor.SPG_M10_GMC,
Armor.SPG_M10_GMC,
Armor.SPG_StuG_III_Ausf__G,
Armor.SPG_StuG_IV,
Artillery.SPG_M12_GMC_155mm,
Armor.SPG_Sturmpanzer_IV_Brummbar,
Armor.Car_Daimler_Armored,
Armor.LT_Mk_VII_Tetrarch,
Artillery.MLRS_M270_227mm,
Artillery.SPH_M109_Paladin_155mm,
Artillery.SPM_2S9_Nona_120mm_M,
Artillery.SPH_2S1_Gvozdika_122mm,
Artillery.SPH_2S3_Akatsia_152mm,
Artillery.SPH_2S19_Msta_152mm,
Artillery.MLRS_BM_21_Grad_122mm,
Artillery.MLRS_BM_21_Grad_122mm,
Artillery.MLRS_9K57_Uragan_BM_27_220mm,
Artillery.MLRS_9A52_Smerch_HE_300mm,
Artillery.SPH_Dana_vz77_152mm,
Artillery.SPH_T155_Firtina_155mm,
Artillery.PLZ_05,
Artillery.SPG_M12_GMC_155mm,
Armor.SPG_Sturmpanzer_IV_Brummbar,
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,
frenchpack.DIM__TOYOTA_BLUE,
frenchpack.DIM__TOYOTA_DESERT,
frenchpack.DIM__TOYOTA_GREEN,
frenchpack.DIM__KAMIKAZE,
frenchpack.AMX_10RCR,
frenchpack.AMX_10RCR_SEPAR,
frenchpack.ERC_90,
frenchpack.TRM_2000_PAMELA,
frenchpack.VAB__50,
frenchpack.VAB_MEPHISTO,
frenchpack.VAB_T20_13,
frenchpack.VBL__50,
frenchpack.VBL_AANF1,
frenchpack.VBAE_CRAB,
frenchpack.VBAE_CRAB_MMP,
frenchpack.AMX_30B2,
frenchpack.Leclerc_Serie_XXI,
frenchpack.DIM__TOYOTA_BLUE,
frenchpack.DIM__TOYOTA_GREEN,
frenchpack.DIM__TOYOTA_DESERT,
frenchpack.DIM__KAMIKAZE,
],
AirDefence: [],
Reconnaissance: [
Unarmed.Truck_M818_6x6,
Unarmed.Truck_Ural_375,
Unarmed.LUV_UAZ_469_Jeep,
],
Nothing: [
Infantry.Infantry_M4,
Infantry.Infantry_AK_74,
],
Embarking: [],
Carriage: [
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
CV_1143_5_Admiral_Kuznetsov,
],
CargoTransportation: [
Cargo_Ivanov,
Bulker_Yakushev,
Tanker_Elnya_160,
Boat_Armed_Hi_speed,
],
}
"""
Units from AirDefense category of UNIT_BY_TASK that will be removed from use if "No SAM" option is checked at the start of the game
"""
SAM_BAN = [
AirDefence.SAM_Linebacker___Bradley_M6,
AirDefence.SAM_SA_9_Strela_1_Gaskin_TEL,
AirDefence.SAM_SA_8_Osa_Gecko_TEL,
AirDefence.SAM_SA_19_Tunguska_Grison,
AirDefence.SAM_SA_6_Kub_Gainful_TEL,
AirDefence.SAM_SA_8_Osa_Gecko_TEL,
AirDefence.SAM_SA_3_S_125_Goa_LN,
AirDefence.SAM_Hawk_Platoon_Command_Post__PCP,
AirDefence.SAM_SA_2_S_75_Guideline_LN,
AirDefence.SAM_SA_11_Buk_Gadfly_Fire_Dome_TEL,
]
"""
Used to convert SAM site parts to the corresponding site
"""
SAM_CONVERT = {
AirDefence.SAM_P19_Flat_Face_SR__SA_2_3: AirDefence.SAM_SA_3_S_125_Goa_LN,
AirDefence.SAM_SA_3_S_125_Low_Blow_TR: AirDefence.SAM_SA_3_S_125_Goa_LN,
AirDefence.SAM_SA_3_S_125_Goa_LN: AirDefence.SAM_SA_3_S_125_Goa_LN,
AirDefence.SAM_SA_6_Kub_Gainful_TEL: AirDefence.SAM_SA_6_Kub_Gainful_TEL,
AirDefence.SAM_SA_6_Kub_Straight_Flush_STR: AirDefence.SAM_SA_6_Kub_Gainful_TEL,
AirDefence.SAM_SA_10_S_300_Grumble_TEL_C: AirDefence.SAM_SA_10_S_300_Grumble_TEL_C,
AirDefence.SAM_SA_10_S_300_Grumble_Clam_Shell_SR: AirDefence.SAM_SA_10_S_300_Grumble_TEL_C,
AirDefence.SAM_SA_10_S_300_Grumble_Flap_Lid_TR: AirDefence.SAM_SA_10_S_300_Grumble_TEL_C,
AirDefence.SAM_SA_10_S_300_Grumble_C2: AirDefence.SAM_SA_10_S_300_Grumble_TEL_C,
AirDefence.SAM_SA_10_S_300_Grumble_Big_Bird_SR: AirDefence.SAM_SA_10_S_300_Grumble_C2,
AirDefence.SAM_Hawk_TR__AN_MPQ_46: AirDefence.SAM_Hawk_Platoon_Command_Post__PCP,
AirDefence.SAM_Hawk_SR__AN_MPQ_50: AirDefence.SAM_Hawk_Platoon_Command_Post__PCP,
AirDefence.SAM_Hawk_LN_M192: AirDefence.SAM_Hawk_Platoon_Command_Post__PCP,
"except": {
# this radar is shared between the two S300's. if we attempt to find a SAM site at a base and can't find one
# model, we can safely assume the other was deployed
# well, perhaps not safely, but we'll make the assumption anyway :p
AirDefence.SAM_SA_10_S_300_Grumble_Flap_Lid_TR: AirDefence.SAM_SA_10_S_300_Grumble_C2,
AirDefence.SAM_P19_Flat_Face_SR__SA_2_3: AirDefence.SAM_SA_2_S_75_Guideline_LN,
},
}
"""
Units that will always be spawned in the air
"""
TAKEOFF_BAN: List[Type[FlyingType]] = []
"""
Units that will be always spawned in the air if launched from the carrier
"""
CARRIER_TAKEOFF_BAN: List[Type[FlyingType]] = [
Su_33, # Kuznecow is bugged in a way that only 2 aircraft could be spawned
]
"""
Units separated by country.
country : DCS Country name
"""
FACTIONS = FactionLoader()
CARRIER_TYPE_BY_PLANE = {
FA_18C_hornet: CVN_74_John_C__Stennis,
F_14A_135_GR: CVN_74_John_C__Stennis,
F_14B: CVN_74_John_C__Stennis,
Ka_50: LHA_1_Tarawa,
SA342M: LHA_1_Tarawa,
UH_1H: LHA_1_Tarawa,
Mi_8MT: LHA_1_Tarawa,
AV8BNA: LHA_1_Tarawa,
}
"""
Aircraft payload overrides. Usually default loadout for the task is loaded during the mission generation.
Syntax goes as follows:
`AircraftIdentifier`: {
"Category": "PayloadName",
},
where:
* `AircraftIdentifier`: identifier of aircraft (the same that is used troughout the file)
* "Category": (in double quotes) is one of the tasks: CAS, CAP, Intercept, Escort or "*"
* "PayloadName": payload as found in resources/payloads/UNIT_TYPE.lua file. Sometimes this will match payload names
in the mission editor, sometimes it doesn't
Payload will be used for operation of following type, "*" category will be used always, no matter the operation.
"""
COMMON_OVERRIDE = {
CAP: "CAP",
Intercept: "CAP",
CAS: "CAS",
PinpointStrike: "STRIKE",
SEAD: "SEAD",
AntishipStrike: "ANTISHIP",
GroundAttack: "STRIKE",
Escort: "CAP",
RunwayAttack: "RUNWAY_ATTACK",
FighterSweep: "CAP",
AWACS: "AEW&C",
}
"""
Aircraft livery overrides. Syntax as follows:
@ -1299,16 +834,6 @@ LHA_CAPABLE = [
---------- END OF CONFIGURATION SECTION
"""
UnitsDict = Dict[UnitType, int]
PlaneDict = Dict[FlyingType, int]
HeliDict = Dict[HelicopterType, int]
ArmorDict = Dict[VehicleType, int]
ShipDict = Dict[ShipType, int]
AirDefenseDict = Dict[AirDefence, int]
AssignedUnitsDict = Dict[Type[UnitType], Tuple[int, int]]
TaskForceDict = Dict[Type[MainTask], AssignedUnitsDict]
StartingPosition = Union[ShipGroup, StaticGroup, Airport, Point]
@ -1332,22 +857,6 @@ def upgrade_to_supercarrier(unit, name: str):
return unit
def unit_task(unit: UnitType) -> Optional[Task]:
for task, units in UNIT_BY_TASK.items():
if unit in units:
return task
if unit in SAM_CONVERT:
return unit_task(SAM_CONVERT[unit])
print(unit.name + " cause issue")
return None
def find_unittype(for_task: Type[MainTask], country_name: str) -> List[Type[UnitType]]:
return [x for x in UNIT_BY_TASK[for_task] if x in FACTIONS[country_name].units]
MANPADS: List[Type[VehicleType]] = [
AirDefence.MANPADS_SA_18_Igla_Grouse,
AirDefence.MANPADS_SA_18_Igla_S_Grouse,
@ -1476,106 +985,6 @@ def unit_type_of(unit: Unit) -> UnitType:
return unit.type
def task_name(task) -> str:
if task == AirDefence:
return "AirDefence"
elif task == Embarking:
return "Transportation"
elif task == PinpointStrike:
return "Frontline units"
else:
return task.name
def choose_units(
for_task: Task, factor: float, count: int, country: str
) -> List[UnitType]:
suitable_unittypes = find_unittype(for_task, country)
suitable_unittypes = [
x for x in suitable_unittypes if x not in helicopter_map.values()
]
suitable_unittypes.sort(key=lambda x: PRICES[x])
idx = int(len(suitable_unittypes) * factor)
variety = int(count + count * factor / 2)
index_start = min(idx, len(suitable_unittypes) - variety)
index_end = min(idx + variety, len(suitable_unittypes))
return list(set(suitable_unittypes[index_start:index_end]))
def unitdict_append(unit_dict: UnitsDict, unit_type: UnitType, count: int):
unit_dict[unit_type] = unit_dict.get(unit_type, 0) + 1
def unitdict_merge(a: UnitsDict, b: UnitsDict) -> UnitsDict:
b = b.copy()
for k, v in a.items():
b[k] = b.get(k, 0) + v
return b
def unitdict_split(unit_dict: UnitsDict, count: int):
buffer_dict: Dict[UnitType, int] = {}
for unit_type, unit_count in unit_dict.items():
for _ in range(unit_count):
unitdict_append(buffer_dict, unit_type, 1)
if sum(buffer_dict.values()) >= count:
yield buffer_dict
buffer_dict = {}
if len(buffer_dict):
yield buffer_dict
def unitdict_restrict_count(unit_dict: UnitsDict, total_count: int) -> UnitsDict:
if total_count == 0:
return {}
groups = list(unitdict_split(unit_dict, total_count))
if len(groups) > 0:
return groups[0]
else:
return {}
def assigned_units_split(fd: AssignedUnitsDict) -> Tuple[PlaneDict, PlaneDict]:
return (
{k: v1 for k, (v1, v2) in fd.items()},
{k: v2 for k, (v1, v2) in fd.items()},
)
def assigned_units_from(d: PlaneDict) -> AssignedUnitsDict:
return {k: (v, 0) for k, v in d.items()}
def assignedunits_split_to_count(dict: AssignedUnitsDict, count: int):
buffer_dict: Dict[Type[UnitType], Tuple[int, int]] = {}
for unit_type, (unit_count, client_count) in dict.items():
for _ in range(unit_count):
new_count, new_client_count = buffer_dict.get(unit_type, (0, 0))
new_count += 1
if client_count > 0:
new_client_count += 1
client_count -= 1
buffer_dict[unit_type] = new_count, new_client_count
if new_count >= count:
yield buffer_dict
buffer_dict = {}
if len(buffer_dict):
yield buffer_dict
def unitdict_from(fd: AssignedUnitsDict) -> Dict:
return {k: v1 for k, (v1, v2) in fd.items()}
def country_id_from_name(name):
for k, v in country_dict.items():
if v.name == name:
@ -1583,24 +992,6 @@ def country_id_from_name(name):
return -1
def _validate_db():
# check unit by task uniquity
total_set = set()
for t, unit_collection in UNIT_BY_TASK.items():
for unit_type in set(unit_collection):
assert unit_type not in total_set, "{} is duplicate for task {}".format(
unit_type, t
)
total_set.add(unit_type)
# check prices
for unit_type in total_set:
assert unit_type in PRICES, "{} not in prices".format(unit_type)
_validate_db()
class DefaultLiveries:
class Default(Enum):
af_standard = ""

View File

@ -434,7 +434,7 @@ class Event:
moved_units[frontline_unit] = int(count * move_factor)
total_units_redeployed = total_units_redeployed + int(count * move_factor)
destination.base.commision_units(moved_units)
destination.base.commission_units(moved_units)
source.base.commit_losses(moved_units)
# Also transfer pending deliveries.

View File

@ -3,7 +3,7 @@ from game.data.groundunitclass import GroundUnitClass
import logging
from dataclasses import dataclass, field
from typing import Optional, Dict, Type, List, Any, cast
from typing import Optional, Dict, Type, List, Any, cast, Iterator
import dcs
from dcs.countries import country_dict
@ -240,7 +240,7 @@ class Faction:
return faction
@property
def units(self) -> List[Type[UnitType]]:
def all_units(self) -> List[Type[UnitType]]:
return (
self.infantry_units
+ self.aircrafts
@ -251,6 +251,12 @@ class Faction:
+ self.logistics_units
)
@property
def ground_units(self) -> Iterator[Type[VehicleType]]:
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]]:
"""

View File

@ -1,21 +1,12 @@
import itertools
import logging
import math
import typing
from typing import Dict, Type
from dcs.task import AWACS, CAP, CAS, Embarking, PinpointStrike, Task, Transport
from dcs.unittype import FlyingType, UnitType, VehicleType
from dcs.vehicles import AirDefence, Armor
from dcs.unittype import FlyingType, VehicleType, UnitType
from game import db
from game.db import PRICES
STRENGTH_AA_ASSEMBLE_MIN = 0.2
PLANES_SCRAMBLE_MIN_BASE = 2
PLANES_SCRAMBLE_MAX_BASE = 8
PLANES_SCRAMBLE_FACTOR = 0.3
BASE_MAX_STRENGTH = 1
BASE_MIN_STRENGTH = 0
@ -24,9 +15,6 @@ class Base:
def __init__(self):
self.aircraft: Dict[Type[FlyingType], int] = {}
self.armor: Dict[Type[VehicleType], int] = {}
# TODO: Appears unused.
self.aa: Dict[AirDefence, int] = {}
self.commision_points: Dict[Type, float] = {}
self.strength = 1
@property
@ -47,119 +35,32 @@ class Base:
logging.exception(f"No price found for {unit_type.id}")
return total
@property
def total_aa(self) -> int:
return sum(self.aa.values())
def total_units(self, task: Task) -> int:
return sum(
[
c
for t, c in itertools.chain(
self.aircraft.items(), self.armor.items(), self.aa.items()
)
if t in db.UNIT_BY_TASK[task]
]
)
def total_units_of_type(self, unit_type) -> int:
return sum(
[
c
for t, c in itertools.chain(
self.aircraft.items(), self.armor.items(), self.aa.items()
)
for t, c in itertools.chain(self.aircraft.items(), self.armor.items())
if t == unit_type
]
)
@property
def all_units(self):
return itertools.chain(
self.aircraft.items(), self.armor.items(), self.aa.items()
)
def _find_best_unit(
self, available_units: Dict[UnitType, int], for_type: Task, count: int
) -> Dict[UnitType, int]:
if count <= 0:
logging.warning("{}: no units for {}".format(self, for_type))
return {}
sorted_units = [
key for key in available_units if key in db.UNIT_BY_TASK[for_type]
]
sorted_units.sort(key=lambda x: db.PRICES[x], reverse=True)
result: Dict[UnitType, int] = {}
for unit_type in sorted_units:
existing_count = available_units[unit_type] # type: int
if not existing_count:
continue
if count <= 0:
break
result_unit_count = min(count, existing_count)
count -= result_unit_count
assert result_unit_count > 0
result[unit_type] = result.get(unit_type, 0) + result_unit_count
logging.info("{} for {} ({}): {}".format(self, for_type, count, result))
return result
def _find_best_planes(
self, for_type: Task, count: int
) -> typing.Dict[FlyingType, int]:
return self._find_best_unit(self.aircraft, for_type, count)
def _find_best_armor(self, for_type: Task, count: int) -> typing.Dict[Armor, int]:
return self._find_best_unit(self.armor, for_type, count)
def append_commision_points(self, for_type, points: float) -> int:
self.commision_points[for_type] = (
self.commision_points.get(for_type, 0) + points
)
points = self.commision_points[for_type]
if points >= 1:
self.commision_points[for_type] = points - math.floor(points)
return int(math.floor(points))
return 0
def filter_units(self, applicable_units: typing.Collection):
self.aircraft = {
k: v for k, v in self.aircraft.items() if k in applicable_units
}
self.armor = {k: v for k, v in self.armor.items() if k in applicable_units}
def commision_units(self, units: typing.Dict[typing.Any, int]):
def commission_units(self, units: typing.Dict[typing.Type[UnitType], int]):
for unit_type, unit_count in units.items():
if unit_count <= 0:
continue
for_task = db.unit_task(unit_type)
target_dict = None
if (
for_task == AWACS
or for_task == CAS
or for_task == CAP
or for_task == Embarking
or for_task == Transport
):
target_dict = self.aircraft
elif for_task == PinpointStrike:
if issubclass(unit_type, VehicleType):
target_dict = self.armor
elif for_task == AirDefence:
target_dict = self.aa
if target_dict is not None:
target_dict[unit_type] = target_dict.get(unit_type, 0) + unit_count
elif issubclass(unit_type, FlyingType):
target_dict = self.aircraft
else:
logging.error("Unable to determine target dict for " + str(unit_type))
logging.error(
f"Unexpected unit type of {unit_type}: "
f"{unit_type.__module__}.{unit_type.__name__}"
)
return
target_dict[unit_type] = target_dict.get(unit_type, 0) + unit_count
def commit_losses(self, units_lost: typing.Dict[typing.Any, int]):
@ -190,55 +91,3 @@ class Base:
def set_strength_to_minimum(self) -> None:
self.strength = BASE_MIN_STRENGTH
def scramble_count(self, multiplier: float, task: Task = None) -> int:
if task:
count = sum(
[v for k, v in self.aircraft.items() if db.unit_task(k) == task]
)
else:
count = self.total_aircraft
count = int(math.ceil(count * PLANES_SCRAMBLE_FACTOR * self.strength))
return min(
min(
max(count, PLANES_SCRAMBLE_MIN_BASE),
int(PLANES_SCRAMBLE_MAX_BASE * multiplier),
),
count,
)
def assemble_count(self):
return int(self.total_armor * 0.5)
def assemble_aa_count(self) -> int:
# previous logic removed because we always want the full air defense capabilities.
return self.total_aa
def scramble_sweep(self, multiplier: float) -> typing.Dict[FlyingType, int]:
return self._find_best_planes(CAP, self.scramble_count(multiplier, CAP))
def scramble_last_defense(self):
# return as many CAP-capable aircraft as we can since this is the last defense of the base
# (but not more than 20 - that's just nuts)
return self._find_best_planes(CAP, min(self.total_aircraft, 20))
def scramble_cas(self, multiplier: float) -> typing.Dict[FlyingType, int]:
return self._find_best_planes(CAS, self.scramble_count(multiplier, CAS))
def scramble_interceptors(self, multiplier: float) -> typing.Dict[FlyingType, int]:
return self._find_best_planes(CAP, self.scramble_count(multiplier, CAP))
def assemble_attack(self) -> typing.Dict[Armor, int]:
return self._find_best_armor(PinpointStrike, self.assemble_count())
def assemble_defense(self) -> typing.Dict[Armor, int]:
count = int(self.total_armor * min(self.strength + 0.5, 1))
return self._find_best_armor(PinpointStrike, count)
def assemble_aa(self, count=None) -> typing.Dict[AirDefence, int]:
return self._find_best_unit(
self.aa,
AirDefence,
count and min(count, self.total_aa) or self.assemble_aa_count(),
)

View File

@ -565,7 +565,7 @@ class ControlPoint(MissionTarget, ABC):
while self.base.armor:
unit_type, count = self.base.armor.popitem()
for _ in range(count):
destination.control_point.base.commision_units({unit_type: 1})
destination.control_point.base.commission_units({unit_type: 1})
destination = heapq.heappushpop(destinations, destination)
def capture_aircraft(
@ -613,7 +613,7 @@ class ControlPoint(MissionTarget, ABC):
return
parking = destination.unclaimed_parking(game)
transfer_amount = min([parking, count])
destination.base.commision_units({airframe: transfer_amount})
destination.base.commission_units({airframe: transfer_amount})
count -= transfer_amount
def retreat_air_units(self, game: Game) -> None:

View File

@ -109,7 +109,7 @@ class TransferOrder:
def disband_at(self, location: ControlPoint) -> None:
logging.info(f"Units halting at {location}.")
location.base.commision_units(self.units)
location.base.commission_units(self.units)
self.units.clear()
@property
@ -562,7 +562,7 @@ class PendingTransfers:
if transfer.transport is not None:
self.cancel_transport(transfer.transport, transfer)
self.pending_transfers.remove(transfer)
transfer.origin.base.commision_units(transfer.units)
transfer.origin.base.commission_units(transfer.units)
def perform_transfers(self) -> None:
incomplete = []

View File

@ -104,11 +104,11 @@ class PendingUnitDeliveries:
game.message(f"{coalition} sold: {name} x {-count} at {source}")
self.units = defaultdict(int)
self.destination.base.commision_units(bought_units)
self.destination.base.commission_units(bought_units)
self.destination.base.commit_losses(sold_units)
if units_needing_transfer:
ground_unit_source.base.commision_units(units_needing_transfer)
ground_unit_source.base.commission_units(units_needing_transfer)
self.create_transfer(game, ground_unit_source, units_needing_transfer)
def create_transfer(

View File

@ -16,6 +16,7 @@ from dcs.task import (
)
from game import db
from .flights.ai_flight_planner_db import AEWC_CAPABLE
from .naming import namegen
from .callsigns import callsign_for_support_unit
from .conflictgen import Conflict
@ -102,7 +103,7 @@ class AirSupportConflictGenerator:
fallback_tanker_number = 0
for i, tanker_unit_type in enumerate(
db.find_unittype(Refueling, self.conflict.attackers_side)
self.game.faction_for(player=True).tankers
):
alt, airspeed = self._get_tanker_params(tanker_unit_type)
variant = db.unit_type_name(tanker_unit_type)
@ -178,9 +179,16 @@ class AirSupportConflictGenerator:
)
if not self.game.settings.disable_legacy_aewc:
possible_awacs = db.find_unittype(AWACS, self.conflict.attackers_side)
possible_awacs = [
a
for a in self.game.faction_for(player=True).aircrafts
if a in AEWC_CAPABLE
]
if not possible_awacs:
logging.warning("No AWACS for faction")
return
if len(possible_awacs) > 0:
awacs_unit = possible_awacs[0]
freq = self.radio_registry.alloc_uhf()
@ -214,5 +222,3 @@ class AirSupportConflictGenerator:
blue=True,
)
)
else:
logging.warning("No AWACS for faction")

View File

@ -166,9 +166,7 @@ class ScrollingUnitTransferGrid(QFrame):
scroll_content = QWidget()
task_box_layout = QGridLayout()
unit_types = set(
db.find_unittype(PinpointStrike, self.game_model.game.player_name)
)
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(

View File

@ -167,7 +167,7 @@ class QHangarStatus(QHBoxLayout):
next_turn = self.control_point.allocated_aircraft(self.game_model.game)
max_amount = self.control_point.total_aircraft_parking
components = [f"{next_turn.present} present"]
components = [f"{next_turn.total_present} present"]
if next_turn.total_ordered > 0:
components.append(f"{next_turn.total_ordered} purchased")
elif next_turn.total_ordered < 0:

View File

@ -7,10 +7,8 @@ from PySide2.QtWidgets import (
QScrollArea,
QVBoxLayout,
QWidget,
QMessageBox,
)
from dcs.task import PinpointStrike
from dcs.unittype import FlyingType, UnitType
from dcs.unittype import UnitType
from game import db
from game.theater import ControlPoint
@ -32,27 +30,20 @@ class QArmorRecruitmentMenu(QFrame, QRecruitBehaviour):
def init_ui(self):
main_layout = QVBoxLayout()
units = {
PinpointStrike: db.find_unittype(
PinpointStrike, self.game_model.game.player_name
),
}
scroll_content = QWidget()
task_box_layout = QGridLayout()
scroll_content.setLayout(task_box_layout)
row = 0
for task_type in units.keys():
units_column = list(set(units[task_type]))
if len(units_column) == 0:
continue
units_column.sort(
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"
)
)
for unit_type in units_column:
for unit_type in unit_types:
row = self.add_purchase_row(unit_type, task_box_layout, row)
stretch = QVBoxLayout()
stretch.addStretch()

View File

@ -1,3 +1,6 @@
from collections import defaultdict
from PySide2.QtCore import Qt
from PySide2.QtWidgets import (
QFrame,
QGridLayout,
@ -7,8 +10,6 @@ from PySide2.QtWidgets import (
QScrollArea,
QWidget,
)
from PySide2.QtCore import Qt
from dcs.task import CAP, CAS, Embarking, PinpointStrike
from game import Game, db
from game.theater import ControlPoint
@ -19,51 +20,44 @@ class QIntelInfo(QFrame):
super(QIntelInfo, self).__init__()
self.cp = cp
self.game = game
self.init_ui()
def init_ui(self):
layout = QVBoxLayout()
scroll_content = QWidget()
intelLayout = QVBoxLayout()
intel_layout = QVBoxLayout()
units = {
CAP: db.find_unittype(CAP, self.game.enemy_name),
Embarking: db.find_unittype(Embarking, self.game.enemy_name),
CAS: db.find_unittype(CAS, self.game.enemy_name),
PinpointStrike: db.find_unittype(PinpointStrike, self.game.enemy_name),
}
for task_type in units.keys():
units_column = list(set(units[task_type]))
if sum([self.cp.base.total_units_of_type(u) for u in units_column]) > 0:
group = QGroupBox(db.task_name(task_type))
groupLayout = QGridLayout()
group.setLayout(groupLayout)
row = 0
for unit_type in units_column:
existing_units = self.cp.base.total_units_of_type(unit_type)
if existing_units == 0:
continue
groupLayout.addWidget(
QLabel(
"<b>"
+ db.unit_get_expanded_info(
units_by_task: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
for unit_type, count in self.cp.base.aircraft.items():
if count:
name = db.unit_get_expanded_info(
self.game.enemy_country, unit_type, "name"
)
+ "</b>"
),
row,
0,
units_by_task[unit_type.task_default.name][name] += count
units_by_task = {
task: units_by_task[task] for task in sorted(units_by_task.keys())
}
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"
)
groupLayout.addWidget(QLabel(str(existing_units)), row, 1)
row += 1
front_line_units[name] += count
intelLayout.addWidget(group)
units_by_task["Front line units"] = front_line_units
for task, unit_types in units_by_task.items():
task_group = QGroupBox(task)
task_layout = QGridLayout()
task_group.setLayout(task_layout)
scroll_content.setLayout(intelLayout)
for row, (name, count) in enumerate(unit_types.items()):
task_layout.addWidget(QLabel(f"<b>{name}</b>"), row, 0)
task_layout.addWidget(QLabel(str(count)), row, 1)
intel_layout.addWidget(task_group)
scroll_content.setLayout(intel_layout)
scroll = QScrollArea()
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)

View File

@ -340,11 +340,7 @@ class QBuyGroupForGroundObjectDialog(QDialog):
buy_ewr_layout.addLayout(stretch, 2, 0)
# Armored units
armored_units = db.find_unittype(
PinpointStrike, faction.name
) # Todo : refactor this legacy nonsense
for unit in set(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,

View File

@ -90,7 +90,7 @@ class PilotControls(QHBoxLayout):
self.selector.currentIndexChanged.connect(self.on_pilot_changed)
self.addWidget(self.selector)
self.player_checkbox = QCheckBox()
self.player_checkbox = QCheckBox(text="Player")
self.player_checkbox.setToolTip("Checked if this pilot is a player.")
self.on_pilot_changed(self.selector.currentIndex())
self.addWidget(self.player_checkbox)

View File

@ -0,0 +1,11 @@
{
"name": "Normandy - From Caen to Evreux",
"theater": "Normandy",
"authors": "Khopa",
"recommended_player_faction": "Allies 1944",
"recommended_enemy_faction": "Germany 1944",
"description": "<p>This is a light scenario on the Normandy map.</p><p>August 1944, allied forces are pushing from Caen/Carpiquet to the cities of Lisieux and Evreux.<p>Lisieux is an important logistic hub for the Werhmacht, and Evreux airbase is hosting most of the Luftwaffe forces in the region.</p>",
"miz": "caen_to_evreux.miz",
"performance": 1,
"version": "6.0"
}

Binary file not shown.

View File

@ -1,10 +0,0 @@
{
"name": "Normandy - Normandy Small",
"theater": "Normandy",
"authors": "Khopa",
"recommended_player_faction": "Allies 1944",
"recommended_enemy_faction": "Germany 1944",
"description": "<p>A lighter version of the Normandy 1944 D-Day scenario.</p>",
"miz": "normandy_small.miz",
"performance": 2
}