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. 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. 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 ## Downloads

View File

@ -2,7 +2,7 @@ import json
from datetime import datetime from datetime import datetime
from enum import Enum from enum import Enum
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional, Tuple, Type, Union from typing import List, Optional, Type, Union
from dcs.countries import country_dict from dcs.countries import country_dict
from dcs.helicopters import ( from dcs.helicopters import (
@ -11,7 +11,6 @@ from dcs.helicopters import (
AH_64D, AH_64D,
CH_47D, CH_47D,
CH_53E, CH_53E,
HelicopterType,
Ka_50, Ka_50,
Mi_24V, Mi_24V,
Mi_26, Mi_26,
@ -132,29 +131,21 @@ from dcs.ships import (
) )
from dcs.task import ( from dcs.task import (
AWACS, AWACS,
AntishipStrike,
CAP, CAP,
CAS, CAS,
CargoTransportation, CargoTransportation,
Embarking, Embarking,
Escort,
FighterSweep,
GroundAttack,
Intercept,
MainTask, MainTask,
Nothing, Nothing,
PinpointStrike, PinpointStrike,
Reconnaissance, Reconnaissance,
Refueling, Refueling,
SEAD,
Task,
Transport, Transport,
RunwayAttack,
) )
from dcs.terrain.terrain import Airport from dcs.terrain.terrain import Airport
from dcs.unit import Ship, Unit, Vehicle from dcs.unit import Ship, Unit, Vehicle
from dcs.unitgroup import ShipGroup, StaticGroup 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 ( from dcs.vehicles import (
AirDefence, AirDefence,
Armor, Armor,
@ -724,468 +715,12 @@ PRICES = {
highdigitsams.SAM_SA_17_Buk_M1_2_LN_9A310M1_2: 40, 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. Units separated by country.
country : DCS Country name country : DCS Country name
""" """
FACTIONS = FactionLoader() 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: Aircraft livery overrides. Syntax as follows:
@ -1299,16 +834,6 @@ LHA_CAPABLE = [
---------- END OF CONFIGURATION SECTION ---------- 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] StartingPosition = Union[ShipGroup, StaticGroup, Airport, Point]
@ -1332,22 +857,6 @@ def upgrade_to_supercarrier(unit, name: str):
return unit 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]] = [ MANPADS: List[Type[VehicleType]] = [
AirDefence.MANPADS_SA_18_Igla_Grouse, AirDefence.MANPADS_SA_18_Igla_Grouse,
AirDefence.MANPADS_SA_18_Igla_S_Grouse, AirDefence.MANPADS_SA_18_Igla_S_Grouse,
@ -1476,106 +985,6 @@ def unit_type_of(unit: Unit) -> UnitType:
return unit.type 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): def country_id_from_name(name):
for k, v in country_dict.items(): for k, v in country_dict.items():
if v.name == name: if v.name == name:
@ -1583,24 +992,6 @@ def country_id_from_name(name):
return -1 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 DefaultLiveries:
class Default(Enum): class Default(Enum):
af_standard = "" af_standard = ""

View File

@ -434,7 +434,7 @@ class Event:
moved_units[frontline_unit] = int(count * move_factor) moved_units[frontline_unit] = int(count * move_factor)
total_units_redeployed = total_units_redeployed + 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) source.base.commit_losses(moved_units)
# Also transfer pending deliveries. # Also transfer pending deliveries.

View File

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

View File

@ -1,21 +1,12 @@
import itertools import itertools
import logging import logging
import math
import typing import typing
from typing import Dict, Type from typing import Dict, Type
from dcs.task import AWACS, CAP, CAS, Embarking, PinpointStrike, Task, Transport from dcs.unittype import FlyingType, VehicleType, UnitType
from dcs.unittype import FlyingType, UnitType, VehicleType
from dcs.vehicles import AirDefence, Armor
from game import db
from game.db import PRICES 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_MAX_STRENGTH = 1
BASE_MIN_STRENGTH = 0 BASE_MIN_STRENGTH = 0
@ -24,9 +15,6 @@ class Base:
def __init__(self): def __init__(self):
self.aircraft: Dict[Type[FlyingType], int] = {} self.aircraft: Dict[Type[FlyingType], int] = {}
self.armor: Dict[Type[VehicleType], int] = {} self.armor: Dict[Type[VehicleType], int] = {}
# TODO: Appears unused.
self.aa: Dict[AirDefence, int] = {}
self.commision_points: Dict[Type, float] = {}
self.strength = 1 self.strength = 1
@property @property
@ -47,119 +35,32 @@ class Base:
logging.exception(f"No price found for {unit_type.id}") logging.exception(f"No price found for {unit_type.id}")
return total 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: def total_units_of_type(self, unit_type) -> int:
return sum( return sum(
[ [
c c
for t, c in itertools.chain( for t, c in itertools.chain(self.aircraft.items(), self.armor.items())
self.aircraft.items(), self.armor.items(), self.aa.items()
)
if t == unit_type if t == unit_type
] ]
) )
@property def commission_units(self, units: typing.Dict[typing.Type[UnitType], int]):
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]):
for unit_type, unit_count in units.items(): for unit_type, unit_count in units.items():
if unit_count <= 0: if unit_count <= 0:
continue continue
for_task = db.unit_task(unit_type) if issubclass(unit_type, VehicleType):
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:
target_dict = self.armor target_dict = self.armor
elif for_task == AirDefence: elif issubclass(unit_type, FlyingType):
target_dict = self.aa target_dict = self.aircraft
if target_dict is not None:
target_dict[unit_type] = target_dict.get(unit_type, 0) + unit_count
else: 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]): def commit_losses(self, units_lost: typing.Dict[typing.Any, int]):
@ -190,55 +91,3 @@ class Base:
def set_strength_to_minimum(self) -> None: def set_strength_to_minimum(self) -> None:
self.strength = BASE_MIN_STRENGTH 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: while self.base.armor:
unit_type, count = self.base.armor.popitem() unit_type, count = self.base.armor.popitem()
for _ in range(count): 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) destination = heapq.heappushpop(destinations, destination)
def capture_aircraft( def capture_aircraft(
@ -613,7 +613,7 @@ class ControlPoint(MissionTarget, ABC):
return return
parking = destination.unclaimed_parking(game) parking = destination.unclaimed_parking(game)
transfer_amount = min([parking, count]) transfer_amount = min([parking, count])
destination.base.commision_units({airframe: transfer_amount}) destination.base.commission_units({airframe: transfer_amount})
count -= transfer_amount count -= transfer_amount
def retreat_air_units(self, game: Game) -> None: def retreat_air_units(self, game: Game) -> None:

View File

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

View File

@ -104,11 +104,11 @@ class PendingUnitDeliveries:
game.message(f"{coalition} sold: {name} x {-count} at {source}") game.message(f"{coalition} sold: {name} x {-count} at {source}")
self.units = defaultdict(int) 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) self.destination.base.commit_losses(sold_units)
if units_needing_transfer: 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) self.create_transfer(game, ground_unit_source, units_needing_transfer)
def create_transfer( def create_transfer(

View File

@ -16,6 +16,7 @@ from dcs.task import (
) )
from game import db from game import db
from .flights.ai_flight_planner_db import AEWC_CAPABLE
from .naming import namegen from .naming import namegen
from .callsigns import callsign_for_support_unit from .callsigns import callsign_for_support_unit
from .conflictgen import Conflict from .conflictgen import Conflict
@ -102,7 +103,7 @@ class AirSupportConflictGenerator:
fallback_tanker_number = 0 fallback_tanker_number = 0
for i, tanker_unit_type in enumerate( 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) alt, airspeed = self._get_tanker_params(tanker_unit_type)
variant = db.unit_type_name(tanker_unit_type) variant = db.unit_type_name(tanker_unit_type)
@ -178,41 +179,46 @@ class AirSupportConflictGenerator:
) )
if not self.game.settings.disable_legacy_aewc: 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 len(possible_awacs) > 0: if not possible_awacs:
awacs_unit = possible_awacs[0]
freq = self.radio_registry.alloc_uhf()
awacs_flight = self.mission.awacs_flight(
country=self.mission.country(self.game.player_country),
name=namegen.next_awacs_name(
self.mission.country(self.game.player_country)
),
plane_type=awacs_unit,
altitude=AWACS_ALT,
airport=None,
position=self.conflict.position.random_point_within(
AWACS_DISTANCE, AWACS_DISTANCE
),
frequency=freq.mhz,
start_type=StartType.Warm,
)
awacs_flight.set_frequency(freq.mhz)
awacs_flight.points[0].tasks.append(SetInvisibleCommand(True))
awacs_flight.points[0].tasks.append(SetImmortalCommand(True))
self.air_support.awacs.append(
AwacsInfo(
group_name=str(awacs_flight.name),
callsign=callsign_for_support_unit(awacs_flight),
freq=freq,
depature_location=None,
start_time=None,
end_time=None,
blue=True,
)
)
else:
logging.warning("No AWACS for faction") logging.warning("No AWACS for faction")
return
awacs_unit = possible_awacs[0]
freq = self.radio_registry.alloc_uhf()
awacs_flight = self.mission.awacs_flight(
country=self.mission.country(self.game.player_country),
name=namegen.next_awacs_name(
self.mission.country(self.game.player_country)
),
plane_type=awacs_unit,
altitude=AWACS_ALT,
airport=None,
position=self.conflict.position.random_point_within(
AWACS_DISTANCE, AWACS_DISTANCE
),
frequency=freq.mhz,
start_type=StartType.Warm,
)
awacs_flight.set_frequency(freq.mhz)
awacs_flight.points[0].tasks.append(SetInvisibleCommand(True))
awacs_flight.points[0].tasks.append(SetImmortalCommand(True))
self.air_support.awacs.append(
AwacsInfo(
group_name=str(awacs_flight.name),
callsign=callsign_for_support_unit(awacs_flight),
freq=freq,
depature_location=None,
start_time=None,
end_time=None,
blue=True,
)
)

View File

@ -166,9 +166,7 @@ class ScrollingUnitTransferGrid(QFrame):
scroll_content = QWidget() scroll_content = QWidget()
task_box_layout = QGridLayout() task_box_layout = QGridLayout()
unit_types = set( unit_types = set(self.game_model.game.faction_for(player=True).ground_units)
db.find_unittype(PinpointStrike, self.game_model.game.player_name)
)
sorted_units = sorted( sorted_units = sorted(
{u for u in unit_types if self.cp.base.total_units_of_type(u)}, {u for u in unit_types if self.cp.base.total_units_of_type(u)},
key=lambda u: db.unit_get_expanded_info( key=lambda u: 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) next_turn = self.control_point.allocated_aircraft(self.game_model.game)
max_amount = self.control_point.total_aircraft_parking 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: if next_turn.total_ordered > 0:
components.append(f"{next_turn.total_ordered} purchased") components.append(f"{next_turn.total_ordered} purchased")
elif next_turn.total_ordered < 0: elif next_turn.total_ordered < 0:

View File

@ -7,10 +7,8 @@ from PySide2.QtWidgets import (
QScrollArea, QScrollArea,
QVBoxLayout, QVBoxLayout,
QWidget, QWidget,
QMessageBox,
) )
from dcs.task import PinpointStrike from dcs.unittype import UnitType
from dcs.unittype import FlyingType, UnitType
from game import db from game import db
from game.theater import ControlPoint from game.theater import ControlPoint
@ -32,31 +30,24 @@ class QArmorRecruitmentMenu(QFrame, QRecruitBehaviour):
def init_ui(self): def init_ui(self):
main_layout = QVBoxLayout() main_layout = QVBoxLayout()
units = {
PinpointStrike: db.find_unittype(
PinpointStrike, self.game_model.game.player_name
),
}
scroll_content = QWidget() scroll_content = QWidget()
task_box_layout = QGridLayout() task_box_layout = QGridLayout()
scroll_content.setLayout(task_box_layout) scroll_content.setLayout(task_box_layout)
row = 0 row = 0
for task_type in units.keys(): unit_types = list(
units_column = list(set(units[task_type])) set(self.game_model.game.faction_for(player=True).ground_units)
if len(units_column) == 0: )
continue unit_types.sort(
units_column.sort( key=lambda u: db.unit_get_expanded_info(
key=lambda u: db.unit_get_expanded_info( self.game_model.game.player_country, u, "name"
self.game_model.game.player_country, u, "name"
)
) )
for unit_type in units_column: )
row = self.add_purchase_row(unit_type, task_box_layout, row) for unit_type in unit_types:
stretch = QVBoxLayout() row = self.add_purchase_row(unit_type, task_box_layout, row)
stretch.addStretch() stretch = QVBoxLayout()
task_box_layout.addLayout(stretch, row, 0) stretch.addStretch()
task_box_layout.addLayout(stretch, row, 0)
scroll_content.setLayout(task_box_layout) scroll_content.setLayout(task_box_layout)
scroll = QScrollArea() scroll = QScrollArea()

View File

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

View File

@ -340,11 +340,7 @@ class QBuyGroupForGroundObjectDialog(QDialog):
buy_ewr_layout.addLayout(stretch, 2, 0) buy_ewr_layout.addLayout(stretch, 2, 0)
# Armored units # Armored units
for unit in set(faction.ground_units):
armored_units = db.find_unittype(
PinpointStrike, faction.name
) # Todo : refactor this legacy nonsense
for unit in set(armored_units):
self.buyArmorCombo.addItem( self.buyArmorCombo.addItem(
db.unit_type_name_2(unit) + " [$" + str(db.PRICES[unit]) + "M]", db.unit_type_name_2(unit) + " [$" + str(db.PRICES[unit]) + "M]",
userData=unit, userData=unit,

View File

@ -90,7 +90,7 @@ class PilotControls(QHBoxLayout):
self.selector.currentIndexChanged.connect(self.on_pilot_changed) self.selector.currentIndexChanged.connect(self.on_pilot_changed)
self.addWidget(self.selector) 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.player_checkbox.setToolTip("Checked if this pilot is a player.")
self.on_pilot_changed(self.selector.currentIndex()) self.on_pilot_changed(self.selector.currentIndex())
self.addWidget(self.player_checkbox) 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
}