mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Merge branch 'develop' into helipads
This commit is contained in:
commit
e00ca5d096
@ -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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Downloads
|
## Downloads
|
||||||
|
|
||||||
|
|||||||
613
game/db.py
613
game/db.py
@ -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 = ""
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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]]:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -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(),
|
|
||||||
)
|
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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 = []
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
11
resources/campaigns/caen_to_evreux.json
Normal file
11
resources/campaigns/caen_to_evreux.json
Normal 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"
|
||||||
|
}
|
||||||
BIN
resources/campaigns/caen_to_evreux.miz
Normal file
BIN
resources/campaigns/caen_to_evreux.miz
Normal file
Binary file not shown.
@ -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
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user