mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Merge remote-tracking branch 'upstream/develop' into develop
This commit is contained in:
commit
59d6cc7625
@ -1,4 +1,7 @@
|
|||||||
# 2.1.3
|
# 2.1.4
|
||||||
|
|
||||||
|
## Fixes :
|
||||||
|
* **[UI]** Fixed an issue that prevented generating the mission (take off button no working) on old savegames.
|
||||||
|
|
||||||
## Features/Improvements :
|
## Features/Improvements :
|
||||||
* **[Units/Factions]** Added A-10C_2 to USA 2005 and Bluefor modern factions
|
* **[Units/Factions]** Added A-10C_2 to USA 2005 and Bluefor modern factions
|
||||||
|
|||||||
@ -1,4 +1,22 @@
|
|||||||
from dcs.planes import *
|
from dcs.planes import (
|
||||||
|
Bf_109K_4,
|
||||||
|
C_101CC,
|
||||||
|
FW_190A8,
|
||||||
|
FW_190D9,
|
||||||
|
F_5E_3,
|
||||||
|
F_86F_Sabre,
|
||||||
|
I_16,
|
||||||
|
L_39ZA,
|
||||||
|
MiG_15bis,
|
||||||
|
MiG_19P,
|
||||||
|
MiG_21Bis,
|
||||||
|
P_47D_30,
|
||||||
|
P_51D,
|
||||||
|
P_51D_30_NA,
|
||||||
|
SpitfireLFMkIX,
|
||||||
|
SpitfireLFMkIXCW,
|
||||||
|
)
|
||||||
|
|
||||||
from pydcs_extensions.a4ec.a4ec import A_4E_C
|
from pydcs_extensions.a4ec.a4ec import A_4E_C
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -1,95 +1,101 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from game.utils import nm_to_meter, feet_to_meter
|
from game.utils import nm_to_meter, feet_to_meter
|
||||||
|
|
||||||
MODERN_DOCTRINE = {
|
|
||||||
|
|
||||||
"GENERATORS": {
|
@dataclass(frozen=True)
|
||||||
"CAS": True,
|
class Doctrine:
|
||||||
"CAP": True,
|
cas: bool
|
||||||
"SEAD": True,
|
cap: bool
|
||||||
"STRIKE": True,
|
sead: bool
|
||||||
"ANTISHIP": True,
|
strike: bool
|
||||||
},
|
antiship: bool
|
||||||
|
|
||||||
"STRIKE_MAX_RANGE": 1500000,
|
strike_max_range: int
|
||||||
"SEAD_MAX_RANGE": 1500000,
|
sead_max_range: int
|
||||||
|
|
||||||
"CAP_EVERY_X_MINUTES": 20,
|
rendezvous_altitude: int
|
||||||
"CAS_EVERY_X_MINUTES": 30,
|
join_distance: int
|
||||||
"SEAD_EVERY_X_MINUTES": 40,
|
split_distance: int
|
||||||
"STRIKE_EVERY_X_MINUTES": 40,
|
ingress_egress_distance: int
|
||||||
|
ingress_altitude: int
|
||||||
|
egress_altitude: int
|
||||||
|
|
||||||
"INGRESS_EGRESS_DISTANCE": nm_to_meter(45),
|
min_patrol_altitude: int
|
||||||
"INGRESS_ALT": feet_to_meter(20000),
|
max_patrol_altitude: int
|
||||||
"EGRESS_ALT": feet_to_meter(20000),
|
pattern_altitude: int
|
||||||
"PATROL_ALT_RANGE": (feet_to_meter(15000), feet_to_meter(33000)),
|
|
||||||
"PATTERN_ALTITUDE": feet_to_meter(5000),
|
|
||||||
|
|
||||||
"CAP_PATTERN_LENGTH": (nm_to_meter(15), nm_to_meter(40)),
|
cap_min_track_length: int
|
||||||
"FRONTLINE_CAP_DISTANCE_FROM_FRONTLINE": (nm_to_meter(6), nm_to_meter(15)),
|
cap_max_track_length: int
|
||||||
"CAP_DISTANCE_FROM_CP": (nm_to_meter(10), nm_to_meter(40)),
|
cap_min_distance_from_cp: int
|
||||||
|
cap_max_distance_from_cp: int
|
||||||
|
|
||||||
"MAX_NUMBER_OF_INTERCEPTION_GROUP": 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
COLDWAR_DOCTRINE = {
|
MODERN_DOCTRINE = Doctrine(
|
||||||
|
cap=True,
|
||||||
|
cas=True,
|
||||||
|
sead=True,
|
||||||
|
strike=True,
|
||||||
|
antiship=True,
|
||||||
|
strike_max_range=1500000,
|
||||||
|
sead_max_range=1500000,
|
||||||
|
rendezvous_altitude=feet_to_meter(25000),
|
||||||
|
join_distance=nm_to_meter(20),
|
||||||
|
split_distance=nm_to_meter(20),
|
||||||
|
ingress_egress_distance=nm_to_meter(45),
|
||||||
|
ingress_altitude=feet_to_meter(20000),
|
||||||
|
egress_altitude=feet_to_meter(20000),
|
||||||
|
min_patrol_altitude=feet_to_meter(15000),
|
||||||
|
max_patrol_altitude=feet_to_meter(33000),
|
||||||
|
pattern_altitude=feet_to_meter(5000),
|
||||||
|
cap_min_track_length=nm_to_meter(15),
|
||||||
|
cap_max_track_length=nm_to_meter(40),
|
||||||
|
cap_min_distance_from_cp=nm_to_meter(10),
|
||||||
|
cap_max_distance_from_cp=nm_to_meter(40),
|
||||||
|
)
|
||||||
|
|
||||||
"GENERATORS": {
|
COLDWAR_DOCTRINE = Doctrine(
|
||||||
"CAS": True,
|
cap=True,
|
||||||
"CAP": True,
|
cas=True,
|
||||||
"SEAD": True,
|
sead=True,
|
||||||
"STRIKE": True,
|
strike=True,
|
||||||
"ANTISHIP": True,
|
antiship=True,
|
||||||
},
|
strike_max_range=1500000,
|
||||||
|
sead_max_range=1500000,
|
||||||
|
rendezvous_altitude=feet_to_meter(22000),
|
||||||
|
join_distance=nm_to_meter(10),
|
||||||
|
split_distance=nm_to_meter(10),
|
||||||
|
ingress_egress_distance=nm_to_meter(30),
|
||||||
|
ingress_altitude=feet_to_meter(18000),
|
||||||
|
egress_altitude=feet_to_meter(18000),
|
||||||
|
min_patrol_altitude=feet_to_meter(10000),
|
||||||
|
max_patrol_altitude=feet_to_meter(24000),
|
||||||
|
pattern_altitude=feet_to_meter(5000),
|
||||||
|
cap_min_track_length=nm_to_meter(12),
|
||||||
|
cap_max_track_length=nm_to_meter(24),
|
||||||
|
cap_min_distance_from_cp=nm_to_meter(8),
|
||||||
|
cap_max_distance_from_cp=nm_to_meter(25),
|
||||||
|
)
|
||||||
|
|
||||||
"STRIKE_MAX_RANGE": 1500000,
|
WWII_DOCTRINE = Doctrine(
|
||||||
"SEAD_MAX_RANGE": 1500000,
|
cap=True,
|
||||||
|
cas=True,
|
||||||
"CAP_EVERY_X_MINUTES": 20,
|
sead=False,
|
||||||
"CAS_EVERY_X_MINUTES": 30,
|
strike=True,
|
||||||
"SEAD_EVERY_X_MINUTES": 40,
|
antiship=True,
|
||||||
"STRIKE_EVERY_X_MINUTES": 40,
|
strike_max_range=1500000,
|
||||||
|
sead_max_range=1500000,
|
||||||
"INGRESS_EGRESS_DISTANCE": nm_to_meter(30),
|
join_distance=nm_to_meter(5),
|
||||||
"INGRESS_ALT": feet_to_meter(18000),
|
split_distance=nm_to_meter(5),
|
||||||
"EGRESS_ALT": feet_to_meter(18000),
|
rendezvous_altitude=feet_to_meter(10000),
|
||||||
"PATROL_ALT_RANGE": (feet_to_meter(10000), feet_to_meter(24000)),
|
ingress_egress_distance=nm_to_meter(7),
|
||||||
"PATTERN_ALTITUDE": feet_to_meter(5000),
|
ingress_altitude=feet_to_meter(8000),
|
||||||
|
egress_altitude=feet_to_meter(8000),
|
||||||
"CAP_PATTERN_LENGTH": (nm_to_meter(12), nm_to_meter(24)),
|
min_patrol_altitude=feet_to_meter(4000),
|
||||||
"FRONTLINE_CAP_DISTANCE_FROM_FRONTLINE": (nm_to_meter(2), nm_to_meter(8)),
|
max_patrol_altitude=feet_to_meter(15000),
|
||||||
"CAP_DISTANCE_FROM_CP": (nm_to_meter(8), nm_to_meter(25)),
|
pattern_altitude=feet_to_meter(5000),
|
||||||
|
cap_min_track_length=nm_to_meter(8),
|
||||||
"MAX_NUMBER_OF_INTERCEPTION_GROUP": 3,
|
cap_max_track_length=nm_to_meter(18),
|
||||||
}
|
cap_min_distance_from_cp=nm_to_meter(0),
|
||||||
|
cap_max_distance_from_cp=nm_to_meter(5),
|
||||||
WWII_DOCTRINE = {
|
)
|
||||||
|
|
||||||
"GENERATORS": {
|
|
||||||
"CAS": True,
|
|
||||||
"CAP": True,
|
|
||||||
"SEAD": False,
|
|
||||||
"STRIKE": True,
|
|
||||||
"ANTISHIP": True,
|
|
||||||
},
|
|
||||||
|
|
||||||
"STRIKE_MAX_RANGE": 1500000,
|
|
||||||
"SEAD_MAX_RANGE": 1500000,
|
|
||||||
|
|
||||||
"CAP_EVERY_X_MINUTES": 20,
|
|
||||||
"CAS_EVERY_X_MINUTES": 30,
|
|
||||||
"SEAD_EVERY_X_MINUTES": 40,
|
|
||||||
"STRIKE_EVERY_X_MINUTES": 40,
|
|
||||||
|
|
||||||
"INGRESS_EGRESS_DISTANCE": nm_to_meter(7),
|
|
||||||
"INGRESS_ALT": feet_to_meter(8000),
|
|
||||||
"EGRESS_ALT": feet_to_meter(8000),
|
|
||||||
"PATROL_ALT_RANGE": (feet_to_meter(4000), feet_to_meter(15000)),
|
|
||||||
"PATTERN_ALTITUDE": feet_to_meter(5000),
|
|
||||||
|
|
||||||
"CAP_PATTERN_LENGTH": (nm_to_meter(8), nm_to_meter(18)),
|
|
||||||
"FRONTLINE_CAP_DISTANCE_FROM_FRONTLINE": (nm_to_meter(1), nm_to_meter(6)),
|
|
||||||
"CAP_DISTANCE_FROM_CP": (nm_to_meter(0), nm_to_meter(5)),
|
|
||||||
|
|
||||||
"MAX_NUMBER_OF_INTERCEPTION_GROUP": 3,
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,5 +1,26 @@
|
|||||||
|
from dcs.ships import (
|
||||||
|
CGN_1144_2_Pyotr_Velikiy,
|
||||||
|
CG_1164_Moskva,
|
||||||
|
CVN_70_Carl_Vinson,
|
||||||
|
CVN_71_Theodore_Roosevelt,
|
||||||
|
CVN_72_Abraham_Lincoln,
|
||||||
|
CVN_73_George_Washington,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
CV_1143_5_Admiral_Kuznetsov,
|
||||||
|
CV_1143_5_Admiral_Kuznetsov_2017,
|
||||||
|
FFG_11540_Neustrashimy,
|
||||||
|
FFL_1124_4_Grisha,
|
||||||
|
FF_1135M_Rezky,
|
||||||
|
FSG_1241_1MP_Molniya,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
Oliver_Hazzard_Perry_class,
|
||||||
|
Ticonderoga_class,
|
||||||
|
Type_052B_Destroyer,
|
||||||
|
Type_052C_Destroyer,
|
||||||
|
Type_054A_Frigate,
|
||||||
|
USS_Arleigh_Burke_IIa,
|
||||||
|
)
|
||||||
from dcs.vehicles import AirDefence
|
from dcs.vehicles import AirDefence
|
||||||
from dcs.ships import *
|
|
||||||
|
|
||||||
UNITS_WITH_RADAR = [
|
UNITS_WITH_RADAR = [
|
||||||
|
|
||||||
|
|||||||
244
game/db.py
244
game/db.py
@ -1,34 +1,177 @@
|
|||||||
import typing
|
|
||||||
import enum
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Any, Dict, List, Optional, Tuple, Type, Union
|
||||||
|
|
||||||
from dcs.countries import get_by_id, country_dict
|
from dcs.countries import country_dict
|
||||||
from dcs.vehicles import *
|
from dcs.helicopters import (
|
||||||
from dcs.ships import *
|
AH_1W,
|
||||||
from dcs.planes import *
|
AH_64A,
|
||||||
from dcs.helicopters import *
|
AH_64D,
|
||||||
|
HelicopterType,
|
||||||
from dcs.task import *
|
Ka_50,
|
||||||
from dcs.unit import *
|
Mi_24V,
|
||||||
from dcs.unittype import *
|
Mi_28N,
|
||||||
from dcs.unitgroup import *
|
Mi_8MT,
|
||||||
|
OH_58D,
|
||||||
|
SA342L,
|
||||||
|
SA342M,
|
||||||
|
SA342Minigun,
|
||||||
|
SA342Mistral,
|
||||||
|
UH_1H,
|
||||||
|
UH_60A,
|
||||||
|
helicopter_map,
|
||||||
|
)
|
||||||
|
from dcs.mapping import Point
|
||||||
|
# mypy can't resolve these if they're wildcard imports for some reason.
|
||||||
|
from dcs.planes import (
|
||||||
|
AJS37,
|
||||||
|
AV8BNA,
|
||||||
|
A_10A,
|
||||||
|
A_10C,
|
||||||
|
A_10C_2,
|
||||||
|
A_20G,
|
||||||
|
A_50,
|
||||||
|
An_26B,
|
||||||
|
An_30M,
|
||||||
|
B_17G,
|
||||||
|
B_1B,
|
||||||
|
B_52H,
|
||||||
|
Bf_109K_4,
|
||||||
|
C_101CC,
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
FA_18C_hornet,
|
||||||
|
FW_190A8,
|
||||||
|
FW_190D9,
|
||||||
|
F_14B,
|
||||||
|
F_15C,
|
||||||
|
F_15E,
|
||||||
|
F_16A,
|
||||||
|
F_16C_50,
|
||||||
|
F_4E,
|
||||||
|
F_5E_3,
|
||||||
|
F_86F_Sabre,
|
||||||
|
F_A_18C,
|
||||||
|
IL_76MD,
|
||||||
|
IL_78M,
|
||||||
|
JF_17,
|
||||||
|
J_11A,
|
||||||
|
Ju_88A4,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
KJ_2000,
|
||||||
|
L_39C,
|
||||||
|
L_39ZA,
|
||||||
|
MQ_9_Reaper,
|
||||||
|
M_2000C,
|
||||||
|
MiG_15bis,
|
||||||
|
MiG_19P,
|
||||||
|
MiG_21Bis,
|
||||||
|
MiG_23MLD,
|
||||||
|
MiG_25PD,
|
||||||
|
MiG_27K,
|
||||||
|
MiG_29A,
|
||||||
|
MiG_29G,
|
||||||
|
MiG_29S,
|
||||||
|
MiG_31,
|
||||||
|
Mirage_2000_5,
|
||||||
|
P_47D_30,
|
||||||
|
P_47D_30bl1,
|
||||||
|
P_47D_40,
|
||||||
|
P_51D,
|
||||||
|
P_51D_30_NA,
|
||||||
|
PlaneType,
|
||||||
|
RQ_1A_Predator,
|
||||||
|
S_3B_Tanker,
|
||||||
|
SpitfireLFMkIX,
|
||||||
|
SpitfireLFMkIXCW,
|
||||||
|
Su_17M4,
|
||||||
|
Su_24M,
|
||||||
|
Su_24MR,
|
||||||
|
Su_25,
|
||||||
|
Su_25T,
|
||||||
|
Su_25TM,
|
||||||
|
Su_27,
|
||||||
|
Su_30,
|
||||||
|
Su_33,
|
||||||
|
Su_34,
|
||||||
|
Tornado_GR4,
|
||||||
|
Tornado_IDS,
|
||||||
|
WingLoong_I,
|
||||||
|
Yak_40,
|
||||||
|
plane_map,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
Bulk_cargo_ship_Yakushev,
|
||||||
|
CVN_71_Theodore_Roosevelt,
|
||||||
|
CVN_72_Abraham_Lincoln,
|
||||||
|
CVN_73_George_Washington,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
CV_1143_5_Admiral_Kuznetsov,
|
||||||
|
CV_1143_5_Admiral_Kuznetsov_2017,
|
||||||
|
Dry_cargo_ship_Ivanov,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
Tanker_Elnya_160,
|
||||||
|
ship_map,
|
||||||
|
)
|
||||||
|
from dcs.task import (
|
||||||
|
AWACS,
|
||||||
|
AntishipStrike,
|
||||||
|
CAP,
|
||||||
|
CAS,
|
||||||
|
CargoTransportation,
|
||||||
|
Embarking,
|
||||||
|
Escort,
|
||||||
|
GroundAttack,
|
||||||
|
Intercept,
|
||||||
|
MainTask,
|
||||||
|
Nothing,
|
||||||
|
PinpointStrike,
|
||||||
|
Reconnaissance,
|
||||||
|
Refueling,
|
||||||
|
SEAD,
|
||||||
|
Task,
|
||||||
|
Transport,
|
||||||
|
)
|
||||||
|
from dcs.terrain.terrain import Airport
|
||||||
|
from dcs.unit import Ship, Unit, Vehicle
|
||||||
|
from dcs.unitgroup import ShipGroup, StaticGroup
|
||||||
|
from dcs.unittype import FlyingType, ShipType, UnitType, VehicleType
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Artillery,
|
||||||
|
Carriage,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
vehicle_map,
|
||||||
|
)
|
||||||
|
|
||||||
|
import pydcs_extensions.frenchpack.frenchpack as frenchpack
|
||||||
from game.factions.australia_2005 import Australia_2005
|
from game.factions.australia_2005 import Australia_2005
|
||||||
from game.factions.bluefor_coldwar import BLUEFOR_COLDWAR
|
from game.factions.bluefor_coldwar import BLUEFOR_COLDWAR
|
||||||
from game.factions.bluefor_coldwar_a4 import BLUEFOR_COLDWAR_A4
|
from game.factions.bluefor_coldwar_a4 import BLUEFOR_COLDWAR_A4
|
||||||
from game.factions.bluefor_coldwar_mods import BLUEFOR_COLDWAR_MODS
|
from game.factions.bluefor_coldwar_mods import BLUEFOR_COLDWAR_MODS
|
||||||
|
from game.factions.bluefor_modern import BLUEFOR_MODERN
|
||||||
from game.factions.canada_2005 import Canada_2005
|
from game.factions.canada_2005 import Canada_2005
|
||||||
from game.factions.china_2010 import China_2010
|
from game.factions.china_2010 import China_2010
|
||||||
from game.factions.france_1995 import France_1995
|
from game.factions.france_1995 import France_1995
|
||||||
from game.factions.france_2005 import France_2005
|
from game.factions.france_2005 import France_2005
|
||||||
from game.factions.france_modded import France_2005_Modded
|
from game.factions.france_modded import France_2005_Modded
|
||||||
|
from game.factions.germany_1944 import Germany_1944
|
||||||
from game.factions.germany_1944_easy import Germany_1944_Easy
|
from game.factions.germany_1944_easy import Germany_1944_Easy
|
||||||
from game.factions.germany_1990 import Germany_1990
|
from game.factions.germany_1990 import Germany_1990
|
||||||
|
from game.factions.india_2010 import India_2010
|
||||||
from game.factions.insurgent import Insurgent
|
from game.factions.insurgent import Insurgent
|
||||||
from game.factions.insurgent_modded import Insurgent_modded
|
from game.factions.insurgent_modded import Insurgent_modded
|
||||||
from game.factions.iran_2015 import Iran_2015
|
from game.factions.iran_2015 import Iran_2015
|
||||||
from game.factions.israel_1948 import Israel_1948
|
from game.factions.israel_1948 import Israel_1948
|
||||||
from game.factions.israel_1973 import Israel_1973, Israel_1973_NO_WW2_UNITS, Israel_1982
|
from game.factions.israel_1973 import (
|
||||||
|
Israel_1973,
|
||||||
|
Israel_1973_NO_WW2_UNITS,
|
||||||
|
Israel_1982,
|
||||||
|
)
|
||||||
from game.factions.israel_2000 import Israel_2000
|
from game.factions.israel_2000 import Israel_2000
|
||||||
from game.factions.italy_1990 import Italy_1990
|
from game.factions.italy_1990 import Italy_1990
|
||||||
from game.factions.italy_1990_mb339 import Italy_1990_MB339
|
from game.factions.italy_1990_mb339 import Italy_1990_MB339
|
||||||
@ -37,35 +180,41 @@ from game.factions.libya_2011 import Libya_2011
|
|||||||
from game.factions.netherlands_1990 import Netherlands_1990
|
from game.factions.netherlands_1990 import Netherlands_1990
|
||||||
from game.factions.north_korea_2000 import NorthKorea_2000
|
from game.factions.north_korea_2000 import NorthKorea_2000
|
||||||
from game.factions.pakistan_2015 import Pakistan_2015
|
from game.factions.pakistan_2015 import Pakistan_2015
|
||||||
from game.factions.private_miltary_companies import PMC_WESTERN_B, PMC_RUSSIAN, PMC_WESTERN_A
|
from game.factions.private_miltary_companies import (
|
||||||
from game.factions.russia_1975 import Russia_1975
|
PMC_RUSSIAN,
|
||||||
from game.factions.germany_1944 import Germany_1944
|
PMC_WESTERN_A,
|
||||||
from game.factions.india_2010 import India_2010
|
PMC_WESTERN_B,
|
||||||
|
)
|
||||||
from game.factions.russia_1955 import Russia_1955
|
from game.factions.russia_1955 import Russia_1955
|
||||||
from game.factions.russia_1965 import Russia_1965
|
from game.factions.russia_1965 import Russia_1965
|
||||||
|
from game.factions.russia_1975 import Russia_1975
|
||||||
from game.factions.russia_1990 import Russia_1990
|
from game.factions.russia_1990 import Russia_1990
|
||||||
from game.factions.russia_2010 import Russia_2010
|
from game.factions.russia_2010 import Russia_2010
|
||||||
from game.factions.spain_1990 import Spain_1990
|
from game.factions.spain_1990 import Spain_1990
|
||||||
from game.factions.sweden_1990 import Sweden_1990
|
from game.factions.sweden_1990 import Sweden_1990
|
||||||
from game.factions.syria import Syria_2011, Syria_1967, Syria_1967_WW2_Weapons, Syria_1973, Arab_Armies_1948, Syria_1982
|
from game.factions.syria import (
|
||||||
|
Arab_Armies_1948,
|
||||||
|
Syria_1967,
|
||||||
|
Syria_1967_WW2_Weapons,
|
||||||
|
Syria_1973,
|
||||||
|
Syria_1982,
|
||||||
|
Syria_2011,
|
||||||
|
)
|
||||||
from game.factions.turkey_2005 import Turkey_2005
|
from game.factions.turkey_2005 import Turkey_2005
|
||||||
from game.factions.uae_2005 import UAE_2005
|
from game.factions.uae_2005 import UAE_2005
|
||||||
from game.factions.uk_1944 import UK_1944
|
from game.factions.uk_1944 import UK_1944
|
||||||
from game.factions.uk_1990 import UnitedKingdom_1990
|
from game.factions.uk_1990 import UnitedKingdom_1990
|
||||||
from game.factions.ukraine_2010 import Ukraine_2010
|
from game.factions.ukraine_2010 import Ukraine_2010
|
||||||
from game.factions.us_aggressors import US_Aggressors
|
from game.factions.us_aggressors import US_Aggressors
|
||||||
from game.factions.usa_1944 import USA_1944, ALLIES_1944
|
from game.factions.usa_1944 import ALLIES_1944, USA_1944
|
||||||
from game.factions.usa_1955 import USA_1955
|
from game.factions.usa_1955 import USA_1955
|
||||||
from game.factions.usa_1960 import USA_1960
|
from game.factions.usa_1960 import USA_1960
|
||||||
from game.factions.usa_1965 import USA_1965
|
from game.factions.usa_1965 import USA_1965
|
||||||
from game.factions.usa_1990 import USA_1990
|
from game.factions.usa_1990 import USA_1990
|
||||||
from game.factions.usa_2005 import USA_2005
|
from game.factions.usa_2005 import USA_2005
|
||||||
from game.factions.bluefor_modern import BLUEFOR_MODERN
|
|
||||||
|
|
||||||
# PATCH pydcs data with MODS
|
# PATCH pydcs data with MODS
|
||||||
from pydcs_extensions.a4ec.a4ec import A_4E_C
|
from pydcs_extensions.a4ec.a4ec import A_4E_C
|
||||||
from pydcs_extensions.mb339.mb339 import MB_339PAN
|
from pydcs_extensions.mb339.mb339 import MB_339PAN
|
||||||
import pydcs_extensions.frenchpack.frenchpack as frenchpack
|
|
||||||
from pydcs_extensions.rafale.rafale import Rafale_A_S, Rafale_M
|
from pydcs_extensions.rafale.rafale import Rafale_A_S, Rafale_M
|
||||||
|
|
||||||
plane_map["A-4E-C"] = A_4E_C
|
plane_map["A-4E-C"] = A_4E_C
|
||||||
@ -793,13 +942,13 @@ SAM_CONVERT = {
|
|||||||
"""
|
"""
|
||||||
Units that will always be spawned in the air
|
Units that will always be spawned in the air
|
||||||
"""
|
"""
|
||||||
TAKEOFF_BAN = [
|
TAKEOFF_BAN: List[Type[FlyingType]] = [
|
||||||
]
|
]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Units that will be always spawned in the air if launched from the carrier
|
Units that will be always spawned in the air if launched from the carrier
|
||||||
"""
|
"""
|
||||||
CARRIER_TAKEOFF_BAN = [
|
CARRIER_TAKEOFF_BAN: List[Type[FlyingType]] = [
|
||||||
Su_33, # Kuznecow is bugged in a way that only 2 aircraft could be spawned
|
Su_33, # Kuznecow is bugged in a way that only 2 aircraft could be spawned
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -807,7 +956,7 @@ CARRIER_TAKEOFF_BAN = [
|
|||||||
Units separated by country.
|
Units separated by country.
|
||||||
country : DCS Country name
|
country : DCS Country name
|
||||||
"""
|
"""
|
||||||
FACTIONS = {
|
FACTIONS: Dict[str, Dict[str, Any]] = {
|
||||||
|
|
||||||
"Bluefor Modern": BLUEFOR_MODERN,
|
"Bluefor Modern": BLUEFOR_MODERN,
|
||||||
"Bluefor Cold War 1970s": BLUEFOR_COLDWAR,
|
"Bluefor Cold War 1970s": BLUEFOR_COLDWAR,
|
||||||
@ -934,10 +1083,11 @@ COMMON_OVERRIDE = {
|
|||||||
PinpointStrike: "STRIKE",
|
PinpointStrike: "STRIKE",
|
||||||
SEAD: "SEAD",
|
SEAD: "SEAD",
|
||||||
AntishipStrike: "ANTISHIP",
|
AntishipStrike: "ANTISHIP",
|
||||||
GroundAttack: "STRIKE"
|
GroundAttack: "STRIKE",
|
||||||
|
Escort: "CAP",
|
||||||
}
|
}
|
||||||
|
|
||||||
PLANE_PAYLOAD_OVERRIDES = {
|
PLANE_PAYLOAD_OVERRIDES: Dict[Type[PlaneType], Dict[Type[Task], str]] = {
|
||||||
|
|
||||||
FA_18C_hornet: {
|
FA_18C_hornet: {
|
||||||
CAP: "CAP HEAVY",
|
CAP: "CAP HEAVY",
|
||||||
@ -946,7 +1096,8 @@ PLANE_PAYLOAD_OVERRIDES = {
|
|||||||
PinpointStrike: "STRIKE",
|
PinpointStrike: "STRIKE",
|
||||||
SEAD: "SEAD",
|
SEAD: "SEAD",
|
||||||
AntishipStrike: "ANTISHIP",
|
AntishipStrike: "ANTISHIP",
|
||||||
GroundAttack: "STRIKE"
|
GroundAttack: "STRIKE",
|
||||||
|
Escort: "CAP HEAVY",
|
||||||
},
|
},
|
||||||
F_A_18C: {
|
F_A_18C: {
|
||||||
CAP: "CAP HEAVY",
|
CAP: "CAP HEAVY",
|
||||||
@ -955,7 +1106,8 @@ PLANE_PAYLOAD_OVERRIDES = {
|
|||||||
PinpointStrike: "STRIKE",
|
PinpointStrike: "STRIKE",
|
||||||
SEAD: "SEAD",
|
SEAD: "SEAD",
|
||||||
AntishipStrike: "ANTISHIP",
|
AntishipStrike: "ANTISHIP",
|
||||||
GroundAttack: "STRIKE"
|
GroundAttack: "STRIKE",
|
||||||
|
Escort: "CAP HEAVY",
|
||||||
},
|
},
|
||||||
A_10A: COMMON_OVERRIDE,
|
A_10A: COMMON_OVERRIDE,
|
||||||
A_10C: COMMON_OVERRIDE,
|
A_10C: COMMON_OVERRIDE,
|
||||||
@ -1134,17 +1286,17 @@ LHA_CAPABLE = [
|
|||||||
---------- END OF CONFIGURATION SECTION
|
---------- END OF CONFIGURATION SECTION
|
||||||
"""
|
"""
|
||||||
|
|
||||||
UnitsDict = typing.Dict[UnitType, int]
|
UnitsDict = Dict[UnitType, int]
|
||||||
PlaneDict = typing.Dict[FlyingType, int]
|
PlaneDict = Dict[FlyingType, int]
|
||||||
HeliDict = typing.Dict[HelicopterType, int]
|
HeliDict = Dict[HelicopterType, int]
|
||||||
ArmorDict = typing.Dict[VehicleType, int]
|
ArmorDict = Dict[VehicleType, int]
|
||||||
ShipDict = typing.Dict[ShipType, int]
|
ShipDict = Dict[ShipType, int]
|
||||||
AirDefenseDict = typing.Dict[AirDefence, int]
|
AirDefenseDict = Dict[AirDefence, int]
|
||||||
|
|
||||||
AssignedUnitsDict = typing.Dict[typing.Type[UnitType], typing.Tuple[int, int]]
|
AssignedUnitsDict = Dict[Type[UnitType], Tuple[int, int]]
|
||||||
TaskForceDict = typing.Dict[typing.Type[Task], AssignedUnitsDict]
|
TaskForceDict = Dict[Type[MainTask], AssignedUnitsDict]
|
||||||
|
|
||||||
StartingPosition = typing.Optional[typing.Union[ShipGroup, StaticGroup, Airport, Point]]
|
StartingPosition = Union[ShipGroup, StaticGroup, Airport, Point]
|
||||||
|
|
||||||
|
|
||||||
def upgrade_to_supercarrier(unit, name: str):
|
def upgrade_to_supercarrier(unit, name: str):
|
||||||
@ -1162,7 +1314,7 @@ def upgrade_to_supercarrier(unit, name: str):
|
|||||||
else:
|
else:
|
||||||
return unit
|
return unit
|
||||||
|
|
||||||
def unit_task(unit: UnitType) -> Task:
|
def unit_task(unit: UnitType) -> Optional[Task]:
|
||||||
for task, units in UNIT_BY_TASK.items():
|
for task, units in UNIT_BY_TASK.items():
|
||||||
if unit in units:
|
if unit in units:
|
||||||
return task
|
return task
|
||||||
@ -1173,10 +1325,10 @@ def unit_task(unit: UnitType) -> Task:
|
|||||||
print(unit.name + " cause issue")
|
print(unit.name + " cause issue")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def find_unittype(for_task: Task, country_name: str) -> typing.List[UnitType]:
|
def find_unittype(for_task: Task, country_name: str) -> List[UnitType]:
|
||||||
return [x for x in UNIT_BY_TASK[for_task] if x in FACTIONS[country_name]["units"]]
|
return [x for x in UNIT_BY_TASK[for_task] if x in FACTIONS[country_name]["units"]]
|
||||||
|
|
||||||
def find_infantry(country_name: str) -> typing.List[UnitType]:
|
def find_infantry(country_name: str) -> List[UnitType]:
|
||||||
inf = [
|
inf = [
|
||||||
Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS,
|
Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS,
|
||||||
Infantry.Soldier_RPG,
|
Infantry.Soldier_RPG,
|
||||||
@ -1199,7 +1351,7 @@ def unit_type_name(unit_type) -> str:
|
|||||||
def unit_type_name_2(unit_type) -> str:
|
def unit_type_name_2(unit_type) -> str:
|
||||||
return unit_type.name and unit_type.name or unit_type.id
|
return unit_type.name and unit_type.name or unit_type.id
|
||||||
|
|
||||||
def unit_type_from_name(name: str) -> UnitType:
|
def unit_type_from_name(name: str) -> Optional[UnitType]:
|
||||||
if name in vehicle_map:
|
if name in vehicle_map:
|
||||||
return vehicle_map[name]
|
return vehicle_map[name]
|
||||||
elif name in plane_map:
|
elif name in plane_map:
|
||||||
@ -1232,7 +1384,7 @@ def task_name(task) -> str:
|
|||||||
return task.name
|
return task.name
|
||||||
|
|
||||||
|
|
||||||
def choose_units(for_task: Task, factor: float, count: int, country: str) -> typing.Collection[UnitType]:
|
def choose_units(for_task: Task, factor: float, count: int, country: str) -> List[UnitType]:
|
||||||
suitable_unittypes = find_unittype(for_task, country)
|
suitable_unittypes = find_unittype(for_task, country)
|
||||||
suitable_unittypes = [x for x in suitable_unittypes if x not in helicopter_map.values()]
|
suitable_unittypes = [x for x in suitable_unittypes if x not in helicopter_map.values()]
|
||||||
suitable_unittypes.sort(key=lambda x: PRICES[x])
|
suitable_unittypes.sort(key=lambda x: PRICES[x])
|
||||||
@ -1258,7 +1410,7 @@ def unitdict_merge(a: UnitsDict, b: UnitsDict) -> UnitsDict:
|
|||||||
|
|
||||||
|
|
||||||
def unitdict_split(unit_dict: UnitsDict, count: int):
|
def unitdict_split(unit_dict: UnitsDict, count: int):
|
||||||
buffer_dict = {}
|
buffer_dict: Dict[UnitType, int] = {}
|
||||||
for unit_type, unit_count in unit_dict.items():
|
for unit_type, unit_count in unit_dict.items():
|
||||||
for _ in range(unit_count):
|
for _ in range(unit_count):
|
||||||
unitdict_append(buffer_dict, unit_type, 1)
|
unitdict_append(buffer_dict, unit_type, 1)
|
||||||
@ -1281,7 +1433,7 @@ def unitdict_restrict_count(unit_dict: UnitsDict, total_count: int) -> UnitsDict
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def assigned_units_split(fd: AssignedUnitsDict) -> typing.Tuple[PlaneDict, PlaneDict]:
|
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()},
|
return {k: v1 for k, (v1, v2) in fd.items()}, {k: v2 for k, (v1, v2) in fd.items()},
|
||||||
|
|
||||||
|
|
||||||
@ -1290,7 +1442,7 @@ def assigned_units_from(d: PlaneDict) -> AssignedUnitsDict:
|
|||||||
|
|
||||||
|
|
||||||
def assignedunits_split_to_count(dict: AssignedUnitsDict, count: int):
|
def assignedunits_split_to_count(dict: AssignedUnitsDict, count: int):
|
||||||
buffer_dict = {}
|
buffer_dict: Dict[Type[UnitType], Tuple[int, int]] = {}
|
||||||
for unit_type, (unit_count, client_count) in dict.items():
|
for unit_type, (unit_count, client_count) in dict.items():
|
||||||
for _ in range(unit_count):
|
for _ in range(unit_count):
|
||||||
new_count, new_client_count = buffer_dict.get(unit_type, (0, 0))
|
new_count, new_client_count = buffer_dict.get(unit_type, (0, 0))
|
||||||
|
|||||||
@ -1,24 +1,24 @@
|
|||||||
import typing
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
|
from typing import Dict, List, Optional, Type, TYPE_CHECKING
|
||||||
|
|
||||||
from dcs.action import Coalition
|
from dcs.mapping import Point
|
||||||
from dcs.unittype import UnitType
|
from dcs.task import Task
|
||||||
from dcs.task import *
|
|
||||||
from dcs.vehicles import AirDefence
|
|
||||||
from dcs.unittype import UnitType
|
from dcs.unittype import UnitType
|
||||||
|
|
||||||
from game import *
|
from game import db, persistency
|
||||||
|
from game.debriefing import Debriefing
|
||||||
from game.infos.information import Information
|
from game.infos.information import Information
|
||||||
from theater import *
|
from game.operation.operation import Operation
|
||||||
from gen.environmentgen import EnvironmentSettings
|
from gen.environmentgen import EnvironmentSettings
|
||||||
from gen.conflictgen import Conflict
|
from gen.ground_forces.combat_stance import CombatStance
|
||||||
from game.db import assigned_units_from, unitdict_from
|
from theater import ControlPoint
|
||||||
from theater.start_generator import generate_airbase_defense_group
|
from theater.start_generator import generate_airbase_defense_group
|
||||||
|
|
||||||
from userdata.debriefing import Debriefing
|
if TYPE_CHECKING:
|
||||||
from userdata import persistency
|
from ..game import Game
|
||||||
|
|
||||||
import game.db as db
|
|
||||||
|
|
||||||
DIFFICULTY_LOG_BASE = 1.1
|
DIFFICULTY_LOG_BASE = 1.1
|
||||||
EVENT_DEPARTURE_MAX_DISTANCE = 340000
|
EVENT_DEPARTURE_MAX_DISTANCE = 340000
|
||||||
@ -28,6 +28,7 @@ MINOR_DEFEAT_INFLUENCE = 0.1
|
|||||||
DEFEAT_INFLUENCE = 0.3
|
DEFEAT_INFLUENCE = 0.3
|
||||||
STRONG_DEFEAT_INFLUENCE = 0.5
|
STRONG_DEFEAT_INFLUENCE = 0.5
|
||||||
|
|
||||||
|
|
||||||
class Event:
|
class Event:
|
||||||
silent = False
|
silent = False
|
||||||
informational = False
|
informational = False
|
||||||
@ -37,7 +38,6 @@ class Event:
|
|||||||
game = None # type: Game
|
game = None # type: Game
|
||||||
location = None # type: Point
|
location = None # type: Point
|
||||||
from_cp = None # type: ControlPoint
|
from_cp = None # type: ControlPoint
|
||||||
departure_cp = None # type: ControlPoint
|
|
||||||
to_cp = None # type: ControlPoint
|
to_cp = None # type: ControlPoint
|
||||||
|
|
||||||
operation = None # type: Operation
|
operation = None # type: Operation
|
||||||
@ -47,7 +47,7 @@ class Event:
|
|||||||
|
|
||||||
def __init__(self, game, from_cp: ControlPoint, target_cp: ControlPoint, location: Point, attacker_name: str, defender_name: str):
|
def __init__(self, game, from_cp: ControlPoint, target_cp: ControlPoint, location: Point, attacker_name: str, defender_name: str):
|
||||||
self.game = game
|
self.game = game
|
||||||
self.departure_cp = None
|
self.departure_cp: Optional[ControlPoint] = None
|
||||||
self.from_cp = from_cp
|
self.from_cp = from_cp
|
||||||
self.to_cp = target_cp
|
self.to_cp = target_cp
|
||||||
self.location = location
|
self.location = location
|
||||||
@ -59,14 +59,14 @@ class Event:
|
|||||||
return self.attacker_name == self.game.player_name
|
return self.attacker_name == self.game.player_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enemy_cp(self) -> ControlPoint:
|
def enemy_cp(self) -> Optional[ControlPoint]:
|
||||||
if self.attacker_name == self.game.player_name:
|
if self.attacker_name == self.game.player_name:
|
||||||
return self.to_cp
|
return self.to_cp
|
||||||
else:
|
else:
|
||||||
return self.departure_cp
|
return self.departure_cp
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tasks(self) -> typing.Collection[typing.Type[Task]]:
|
def tasks(self) -> List[Type[Task]]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -91,18 +91,6 @@ class Event:
|
|||||||
def is_successfull(self, debriefing: Debriefing) -> bool:
|
def is_successfull(self, debriefing: Debriefing) -> bool:
|
||||||
return self.operation.is_successfull(debriefing)
|
return self.operation.is_successfull(debriefing)
|
||||||
|
|
||||||
def player_attacking(self, cp: ControlPoint, flights: db.TaskForceDict):
|
|
||||||
if self.is_player_attacking:
|
|
||||||
self.departure_cp = cp
|
|
||||||
else:
|
|
||||||
self.to_cp = cp
|
|
||||||
|
|
||||||
def player_defending(self, cp: ControlPoint, flights: db.TaskForceDict):
|
|
||||||
if self.is_player_attacking:
|
|
||||||
self.departure_cp = cp
|
|
||||||
else:
|
|
||||||
self.to_cp = cp
|
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
self.operation.is_awacs_enabled = self.is_awacs_enabled
|
self.operation.is_awacs_enabled = self.is_awacs_enabled
|
||||||
self.operation.ca_slots = self.ca_slots
|
self.operation.ca_slots = self.ca_slots
|
||||||
@ -253,7 +241,7 @@ class Event:
|
|||||||
for enemy_cp in enemy_cps:
|
for enemy_cp in enemy_cps:
|
||||||
print("Compute frontline progression for : " + cp.name + " to " + enemy_cp.name)
|
print("Compute frontline progression for : " + cp.name + " to " + enemy_cp.name)
|
||||||
|
|
||||||
delta = 0
|
delta = 0.0
|
||||||
player_won = True
|
player_won = True
|
||||||
ally_casualties = killed_unit_count_by_cp[cp.id]
|
ally_casualties = killed_unit_count_by_cp[cp.id]
|
||||||
enemy_casualties = killed_unit_count_by_cp[enemy_cp.id]
|
enemy_casualties = killed_unit_count_by_cp[enemy_cp.id]
|
||||||
@ -376,7 +364,6 @@ class Event:
|
|||||||
|
|
||||||
class UnitsDeliveryEvent(Event):
|
class UnitsDeliveryEvent(Event):
|
||||||
informational = True
|
informational = True
|
||||||
units = None # type: typing.Dict[UnitType, int]
|
|
||||||
|
|
||||||
def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint, game):
|
def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint, game):
|
||||||
super(UnitsDeliveryEvent, self).__init__(game=game,
|
super(UnitsDeliveryEvent, self).__init__(game=game,
|
||||||
@ -386,12 +373,12 @@ class UnitsDeliveryEvent(Event):
|
|||||||
attacker_name=attacker_name,
|
attacker_name=attacker_name,
|
||||||
defender_name=defender_name)
|
defender_name=defender_name)
|
||||||
|
|
||||||
self.units = {}
|
self.units: Dict[UnitType, int] = {}
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Pending delivery to {}".format(self.to_cp)
|
return "Pending delivery to {}".format(self.to_cp)
|
||||||
|
|
||||||
def deliver(self, units: typing.Dict[UnitType, int]):
|
def deliver(self, units: Dict[UnitType, int]):
|
||||||
for k, v in units.items():
|
for k, v in units.items():
|
||||||
self.units[k] = self.units.get(k, 0) + v
|
self.units[k] = self.units.get(k, 0) + v
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,17 @@
|
|||||||
from game.event import *
|
from typing import List, Type
|
||||||
|
|
||||||
|
from dcs.task import CAP, CAS, Task
|
||||||
|
|
||||||
|
from game import db
|
||||||
from game.operation.frontlineattack import FrontlineAttackOperation
|
from game.operation.frontlineattack import FrontlineAttackOperation
|
||||||
from userdata.debriefing import Debriefing
|
from .event import Event
|
||||||
|
from ..debriefing import Debriefing
|
||||||
|
|
||||||
|
|
||||||
class FrontlineAttackEvent(Event):
|
class FrontlineAttackEvent(Event):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tasks(self) -> typing.Collection[typing.Type[Task]]:
|
def tasks(self) -> List[Type[Task]]:
|
||||||
if self.is_player_attacking:
|
if self.is_player_attacking:
|
||||||
return [CAS, CAP]
|
return [CAS, CAP]
|
||||||
else:
|
else:
|
||||||
@ -34,6 +39,7 @@ class FrontlineAttackEvent(Event):
|
|||||||
self.to_cp.base.affect_strength(-0.1)
|
self.to_cp.base.affect_strength(-0.1)
|
||||||
|
|
||||||
def player_attacking(self, flights: db.TaskForceDict):
|
def player_attacking(self, flights: db.TaskForceDict):
|
||||||
|
assert self.departure_cp is not None
|
||||||
op = FrontlineAttackOperation(game=self.game,
|
op = FrontlineAttackOperation(game=self.game,
|
||||||
attacker_name=self.attacker_name,
|
attacker_name=self.attacker_name,
|
||||||
defender_name=self.defender_name,
|
defender_name=self.defender_name,
|
||||||
@ -41,13 +47,3 @@ class FrontlineAttackEvent(Event):
|
|||||||
departure_cp=self.departure_cp,
|
departure_cp=self.departure_cp,
|
||||||
to_cp=self.to_cp)
|
to_cp=self.to_cp)
|
||||||
self.operation = op
|
self.operation = op
|
||||||
|
|
||||||
def player_defending(self, flights: db.TaskForceDict):
|
|
||||||
op = FrontlineAttackOperation(game=self.game,
|
|
||||||
attacker_name=self.attacker_name,
|
|
||||||
defender_name=self.defender_name,
|
|
||||||
from_cp=self.from_cp,
|
|
||||||
departure_cp=self.departure_cp,
|
|
||||||
to_cp=self.to_cp)
|
|
||||||
self.operation = op
|
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,27 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
AH_1W,
|
||||||
from dcs.ships import *
|
UH_1H,
|
||||||
from dcs.vehicles import *
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
FA_18C_hornet,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
Ticonderoga_class,
|
||||||
|
USS_Arleigh_Burke_IIa,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
Australia_2005 = {
|
Australia_2005 = {
|
||||||
"country": "Australia",
|
"country": "Australia",
|
||||||
|
|||||||
@ -1,7 +1,30 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
SA342L,
|
||||||
from dcs.ships import *
|
SA342M,
|
||||||
from dcs.vehicles import *
|
UH_1H,
|
||||||
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
AJS37,
|
||||||
|
A_10A,
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
F_14B,
|
||||||
|
F_4E,
|
||||||
|
F_5E_3,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
BLUEFOR_COLDWAR = {
|
BLUEFOR_COLDWAR = {
|
||||||
"country": "Combined Joint Task Forces Blue",
|
"country": "Combined Joint Task Forces Blue",
|
||||||
|
|||||||
@ -1,7 +1,31 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
SA342L,
|
||||||
from dcs.ships import *
|
SA342M,
|
||||||
from dcs.vehicles import *
|
UH_1H,
|
||||||
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
AJS37,
|
||||||
|
A_10A,
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
F_14B,
|
||||||
|
F_4E,
|
||||||
|
F_5E_3,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
Ticonderoga_class,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
from pydcs_extensions.a4ec.a4ec import A_4E_C
|
from pydcs_extensions.a4ec.a4ec import A_4E_C
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,31 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
SA342L,
|
||||||
from dcs.ships import *
|
SA342M,
|
||||||
from dcs.vehicles import *
|
UH_1H,
|
||||||
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
AJS37,
|
||||||
|
A_10A,
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
F_14B,
|
||||||
|
F_4E,
|
||||||
|
F_5E_3,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
Ticonderoga_class,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
from pydcs_extensions.a4ec.a4ec import A_4E_C
|
from pydcs_extensions.a4ec.a4ec import A_4E_C
|
||||||
from pydcs_extensions.mb339.mb339 import MB_339PAN
|
from pydcs_extensions.mb339.mb339 import MB_339PAN
|
||||||
|
|||||||
@ -1,7 +1,45 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
AH_64D,
|
||||||
from dcs.ships import *
|
Ka_50,
|
||||||
from dcs.vehicles import *
|
SA342L,
|
||||||
|
SA342M,
|
||||||
|
UH_1H,
|
||||||
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
AJS37,
|
||||||
|
AV8BNA,
|
||||||
|
A_10A,
|
||||||
|
A_10C,
|
||||||
|
A_10C_2,
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
FA_18C_hornet,
|
||||||
|
F_14B,
|
||||||
|
F_15C,
|
||||||
|
F_16C_50,
|
||||||
|
F_5E_3,
|
||||||
|
JF_17,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
M_2000C,
|
||||||
|
Su_25T,
|
||||||
|
Su_27,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
Oliver_Hazzard_Perry_class,
|
||||||
|
Ticonderoga_class,
|
||||||
|
USS_Arleigh_Burke_IIa,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Artillery,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
BLUEFOR_MODERN = {
|
BLUEFOR_MODERN = {
|
||||||
"country": "Combined Joint Task Forces Blue",
|
"country": "Combined Joint Task Forces Blue",
|
||||||
|
|||||||
@ -1,7 +1,26 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
UH_1H,
|
||||||
from dcs.ships import *
|
)
|
||||||
from dcs.vehicles import *
|
from dcs.planes import (
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
FA_18C_hornet,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
Ticonderoga_class,
|
||||||
|
USS_Arleigh_Burke_IIa,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
Canada_2005 = {
|
Canada_2005 = {
|
||||||
"country": "Canada",
|
"country": "Canada",
|
||||||
|
|||||||
@ -1,7 +1,38 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
Mi_28N,
|
||||||
from dcs.ships import *
|
Mi_8MT,
|
||||||
from dcs.vehicles import *
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
An_26B,
|
||||||
|
An_30M,
|
||||||
|
IL_76MD,
|
||||||
|
IL_78M,
|
||||||
|
JF_17,
|
||||||
|
J_11A,
|
||||||
|
KJ_2000,
|
||||||
|
MiG_21Bis,
|
||||||
|
Su_30,
|
||||||
|
Su_33,
|
||||||
|
WingLoong_I,
|
||||||
|
Yak_40,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Bulk_cargo_ship_Yakushev,
|
||||||
|
CV_1143_5_Admiral_Kuznetsov,
|
||||||
|
Dry_cargo_ship_Ivanov,
|
||||||
|
Tanker_Elnya_160,
|
||||||
|
Type_052B_Destroyer,
|
||||||
|
Type_052C_Destroyer,
|
||||||
|
Type_054A_Frigate,
|
||||||
|
Type_071_Amphibious_Transport_Dock,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Artillery,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
China_2010 = {
|
China_2010 = {
|
||||||
"country": "China",
|
"country": "China",
|
||||||
|
|||||||
@ -1,7 +1,28 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
SA342L,
|
||||||
from dcs.ships import *
|
SA342M,
|
||||||
from dcs.vehicles import *
|
SA342Mistral,
|
||||||
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
M_2000C,
|
||||||
|
Mirage_2000_5,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Artillery,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
France_1995 = {
|
France_1995 = {
|
||||||
"country": "France",
|
"country": "France",
|
||||||
|
|||||||
@ -1,7 +1,31 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
SA342L,
|
||||||
from dcs.ships import *
|
SA342M,
|
||||||
from dcs.vehicles import *
|
SA342Mistral,
|
||||||
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
FA_18C_hornet,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
M_2000C,
|
||||||
|
Mirage_2000_5,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
Oliver_Hazzard_Perry_class,
|
||||||
|
Ticonderoga_class,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Artillery,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
France_2005 = {
|
France_2005 = {
|
||||||
"country": "France",
|
"country": "France",
|
||||||
|
|||||||
@ -1,10 +1,33 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
SA342L,
|
||||||
from dcs.ships import *
|
SA342M,
|
||||||
from dcs.vehicles import *
|
SA342Mistral,
|
||||||
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
M_2000C,
|
||||||
|
Mirage_2000_5,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
Oliver_Hazzard_Perry_class,
|
||||||
|
Ticonderoga_class,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Artillery,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
import pydcs_extensions.frenchpack.frenchpack as frenchpack
|
import pydcs_extensions.frenchpack.frenchpack as frenchpack
|
||||||
from pydcs_extensions.rafale.rafale import Rafale_M, Rafale_A_S
|
from pydcs_extensions.rafale.rafale import Rafale_A_S, Rafale_M
|
||||||
|
|
||||||
France_2005_Modded = {
|
France_2005_Modded = {
|
||||||
"country": "France",
|
"country": "France",
|
||||||
|
|||||||
@ -1,5 +1,16 @@
|
|||||||
from dcs.planes import *
|
from dcs.planes import (
|
||||||
from dcs.vehicles import *
|
Bf_109K_4,
|
||||||
|
FW_190A8,
|
||||||
|
FW_190D9,
|
||||||
|
Ju_88A4,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Artillery,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
from game.data.building_data import WW2_GERMANY_BUILDINGS
|
from game.data.building_data import WW2_GERMANY_BUILDINGS
|
||||||
from game.data.doctrine import WWII_DOCTRINE
|
from game.data.doctrine import WWII_DOCTRINE
|
||||||
|
|||||||
@ -1,5 +1,16 @@
|
|||||||
from dcs.planes import *
|
from dcs.planes import (
|
||||||
from dcs.vehicles import *
|
Bf_109K_4,
|
||||||
|
FW_190A8,
|
||||||
|
FW_190D9,
|
||||||
|
Ju_88A4,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Artillery,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
from game.data.building_data import WW2_GERMANY_BUILDINGS
|
from game.data.building_data import WW2_GERMANY_BUILDINGS
|
||||||
from game.data.doctrine import WWII_DOCTRINE
|
from game.data.doctrine import WWII_DOCTRINE
|
||||||
|
|||||||
@ -1,7 +1,28 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
SA342L,
|
||||||
from dcs.ships import *
|
SA342M,
|
||||||
from dcs.vehicles import *
|
UH_1H,
|
||||||
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
F_4E,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
MiG_29G,
|
||||||
|
Tornado_IDS,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
Germany_1990 = {
|
Germany_1990 = {
|
||||||
"country": "Germany",
|
"country": "Germany",
|
||||||
|
|||||||
@ -1,7 +1,32 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
AH_64A,
|
||||||
from dcs.ships import *
|
Mi_8MT,
|
||||||
from dcs.vehicles import *
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
M_2000C,
|
||||||
|
MiG_21Bis,
|
||||||
|
MiG_27K,
|
||||||
|
MiG_29S,
|
||||||
|
Mirage_2000_5,
|
||||||
|
Su_30,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
CV_1143_5_Admiral_Kuznetsov,
|
||||||
|
FSG_1241_1MP_Molniya,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
India_2010 = {
|
India_2010 = {
|
||||||
"country": "India",
|
"country": "India",
|
||||||
|
|||||||
@ -1,7 +1,14 @@
|
|||||||
from dcs.vehicles import *
|
from dcs.ships import (
|
||||||
from dcs.ships import *
|
Bulk_cargo_ship_Yakushev,
|
||||||
from dcs.planes import *
|
Dry_cargo_ship_Ivanov,
|
||||||
from dcs.helicopters import *
|
Tanker_Elnya_160,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
Insurgent = {
|
Insurgent = {
|
||||||
"country": "Insurgents",
|
"country": "Insurgents",
|
||||||
|
|||||||
@ -1,8 +1,21 @@
|
|||||||
from dcs.ships import *
|
from dcs.ships import (
|
||||||
from dcs.vehicles import *
|
Bulk_cargo_ship_Yakushev,
|
||||||
|
Dry_cargo_ship_Ivanov,
|
||||||
|
Tanker_Elnya_160,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
from pydcs_extensions.frenchpack.frenchpack import DIM__TOYOTA_BLUE, DIM__TOYOTA_DESERT, DIM__TOYOTA_GREEN, \
|
from pydcs_extensions.frenchpack.frenchpack import (
|
||||||
DIM__KAMIKAZE
|
DIM__KAMIKAZE,
|
||||||
|
DIM__TOYOTA_BLUE,
|
||||||
|
DIM__TOYOTA_DESERT,
|
||||||
|
DIM__TOYOTA_GREEN,
|
||||||
|
)
|
||||||
|
|
||||||
Insurgent_modded = {
|
Insurgent_modded = {
|
||||||
"country": "Insurgents",
|
"country": "Insurgents",
|
||||||
|
|||||||
@ -1,7 +1,35 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
Mi_24V,
|
||||||
from dcs.ships import *
|
Mi_28N,
|
||||||
from dcs.vehicles import *
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
A_50,
|
||||||
|
An_26B,
|
||||||
|
An_30M,
|
||||||
|
F_14B,
|
||||||
|
F_4E,
|
||||||
|
F_5E_3,
|
||||||
|
IL_76MD,
|
||||||
|
IL_78M,
|
||||||
|
MiG_21Bis,
|
||||||
|
MiG_29A,
|
||||||
|
Su_17M4,
|
||||||
|
Su_24M,
|
||||||
|
Su_25,
|
||||||
|
Yak_40,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Bulk_cargo_ship_Yakushev,
|
||||||
|
CV_1143_5_Admiral_Kuznetsov,
|
||||||
|
Dry_cargo_ship_Ivanov,
|
||||||
|
Tanker_Elnya_160,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
Iran_2015 = {
|
Iran_2015 = {
|
||||||
"country": "Iran",
|
"country": "Iran",
|
||||||
|
|||||||
@ -1,6 +1,20 @@
|
|||||||
from dcs.planes import *
|
from dcs.planes import (
|
||||||
from dcs.ships import *
|
B_17G,
|
||||||
from dcs.vehicles import *
|
Bf_109K_4,
|
||||||
|
P_51D,
|
||||||
|
P_51D_30_NA,
|
||||||
|
SpitfireLFMkIX,
|
||||||
|
SpitfireLFMkIXCW,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
Israel_1948 = {
|
Israel_1948 = {
|
||||||
"country": "Israel",
|
"country": "Israel",
|
||||||
|
|||||||
@ -1,7 +1,26 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
AH_1W,
|
||||||
from dcs.ships import *
|
UH_1H,
|
||||||
from dcs.vehicles import *
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
F_15C,
|
||||||
|
F_16A,
|
||||||
|
F_16C_50,
|
||||||
|
F_4E,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
from pydcs_extensions.a4ec.a4ec import A_4E_C
|
from pydcs_extensions.a4ec.a4ec import A_4E_C
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,29 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
AH_1W,
|
||||||
from dcs.ships import *
|
AH_64D,
|
||||||
from dcs.vehicles import *
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
F_15C,
|
||||||
|
F_15E,
|
||||||
|
F_16C_50,
|
||||||
|
F_4E,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Artillery,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
Israel_2000 = {
|
Israel_2000 = {
|
||||||
"country": "Israel",
|
"country": "Israel",
|
||||||
|
|||||||
@ -1,7 +1,28 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
AH_1W,
|
||||||
from dcs.ships import *
|
UH_1H,
|
||||||
from dcs.vehicles import *
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
AV8BNA,
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
KC_135,
|
||||||
|
S_3B_Tanker,
|
||||||
|
Tornado_IDS,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
Oliver_Hazzard_Perry_class,
|
||||||
|
Ticonderoga_class,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
Italy_1990 = {
|
Italy_1990 = {
|
||||||
"country": "Italy",
|
"country": "Italy",
|
||||||
|
|||||||
@ -1,7 +1,28 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
AH_1W,
|
||||||
from dcs.ships import *
|
UH_1H,
|
||||||
from dcs.vehicles import *
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
AV8BNA,
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
KC_135,
|
||||||
|
S_3B_Tanker,
|
||||||
|
Tornado_IDS,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
Oliver_Hazzard_Perry_class,
|
||||||
|
Ticonderoga_class,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
from pydcs_extensions.mb339.mb339 import MB_339PAN
|
from pydcs_extensions.mb339.mb339 import MB_339PAN
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,24 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
AH_1W,
|
||||||
from dcs.ships import *
|
AH_64D,
|
||||||
from dcs.vehicles import *
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
F_15C,
|
||||||
|
F_16C_50,
|
||||||
|
F_4E,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
)
|
||||||
|
from dcs.ships import LHA_1_Tarawa, Ticonderoga_class, USS_Arleigh_Burke_IIa
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Artillery,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
Japan_2005 = {
|
Japan_2005 = {
|
||||||
"country": "Japan",
|
"country": "Japan",
|
||||||
|
|||||||
@ -1,6 +1,25 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
Mi_24V,
|
||||||
from dcs.vehicles import *
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
A_50,
|
||||||
|
An_26B,
|
||||||
|
An_30M,
|
||||||
|
IL_76MD,
|
||||||
|
IL_78M,
|
||||||
|
MiG_21Bis,
|
||||||
|
MiG_23MLD,
|
||||||
|
Su_17M4,
|
||||||
|
Su_24M,
|
||||||
|
Yak_40,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Artillery,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
Libya_2011 = {
|
Libya_2011 = {
|
||||||
"country": "Libya",
|
"country": "Libya",
|
||||||
|
|||||||
@ -1,7 +1,25 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
AH_64A,
|
||||||
from dcs.ships import *
|
)
|
||||||
from dcs.vehicles import *
|
from dcs.planes import (
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
F_16C_50,
|
||||||
|
F_5E_3,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
Netherlands_1990 = {
|
Netherlands_1990 = {
|
||||||
"country": "The Netherlands",
|
"country": "The Netherlands",
|
||||||
|
|||||||
@ -1,7 +1,33 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
Mi_24V,
|
||||||
from dcs.ships import *
|
Mi_8MT,
|
||||||
from dcs.vehicles import *
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
A_50,
|
||||||
|
An_26B,
|
||||||
|
An_30M,
|
||||||
|
IL_76MD,
|
||||||
|
IL_78M,
|
||||||
|
MiG_15bis,
|
||||||
|
MiG_19P,
|
||||||
|
MiG_21Bis,
|
||||||
|
MiG_23MLD,
|
||||||
|
MiG_29A,
|
||||||
|
Su_25,
|
||||||
|
Yak_40,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Bulk_cargo_ship_Yakushev,
|
||||||
|
CV_1143_5_Admiral_Kuznetsov,
|
||||||
|
Dry_cargo_ship_Ivanov,
|
||||||
|
Tanker_Elnya_160,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
NorthKorea_2000 = {
|
NorthKorea_2000 = {
|
||||||
"country": "North Korea",
|
"country": "North Korea",
|
||||||
|
|||||||
@ -1,7 +1,25 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
AH_1W,
|
||||||
from dcs.ships import *
|
UH_1H,
|
||||||
from dcs.vehicles import *
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
E_3A,
|
||||||
|
F_16C_50,
|
||||||
|
IL_78M,
|
||||||
|
JF_17,
|
||||||
|
MiG_19P,
|
||||||
|
MiG_21Bis,
|
||||||
|
WingLoong_I,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
Pakistan_2015 = {
|
Pakistan_2015 = {
|
||||||
"country": "Pakistan",
|
"country": "Pakistan",
|
||||||
|
|||||||
@ -1,7 +1,25 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
Ka_50,
|
||||||
from dcs.ships import *
|
Mi_24V,
|
||||||
from dcs.vehicles import *
|
Mi_8MT,
|
||||||
|
OH_58D,
|
||||||
|
SA342M,
|
||||||
|
UH_1H,
|
||||||
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
C_101CC,
|
||||||
|
L_39C,
|
||||||
|
L_39ZA,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
from pydcs_extensions.mb339.mb339 import MB_339PAN
|
from pydcs_extensions.mb339.mb339 import MB_339PAN
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
from dcs.planes import MiG_15bis, IL_76MD, IL_78M, An_26B, An_30M, Yak_40
|
from dcs.planes import An_26B, An_30M, IL_76MD, IL_78M, MiG_15bis, Yak_40
|
||||||
from dcs.ships import CV_1143_5_Admiral_Kuznetsov, Bulk_cargo_ship_Yakushev, Dry_cargo_ship_Ivanov, Tanker_Elnya_160
|
from dcs.ships import (
|
||||||
from dcs.vehicles import AirDefence, Armor, Unarmed, Infantry, Artillery
|
Bulk_cargo_ship_Yakushev,
|
||||||
|
CV_1143_5_Admiral_Kuznetsov,
|
||||||
|
Dry_cargo_ship_Ivanov,
|
||||||
|
Tanker_Elnya_160,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import AirDefence, Armor, Artillery, Infantry, Unarmed
|
||||||
|
|
||||||
Russia_1955 = {
|
Russia_1955 = {
|
||||||
"country": "Russia",
|
"country": "Russia",
|
||||||
|
|||||||
@ -1,7 +1,22 @@
|
|||||||
from dcs.helicopters import Mi_8MT
|
from dcs.helicopters import Mi_8MT
|
||||||
from dcs.planes import MiG_15bis, MiG_19P, MiG_21Bis, IL_76MD, IL_78M, An_26B, An_30M, Yak_40, A_50
|
from dcs.planes import (
|
||||||
from dcs.ships import CV_1143_5_Admiral_Kuznetsov, Bulk_cargo_ship_Yakushev, Dry_cargo_ship_Ivanov, Tanker_Elnya_160
|
A_50,
|
||||||
from dcs.vehicles import AirDefence, Armor, Unarmed, Infantry, Artillery
|
An_26B,
|
||||||
|
An_30M,
|
||||||
|
IL_76MD,
|
||||||
|
IL_78M,
|
||||||
|
MiG_15bis,
|
||||||
|
MiG_19P,
|
||||||
|
MiG_21Bis,
|
||||||
|
Yak_40,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Bulk_cargo_ship_Yakushev,
|
||||||
|
CV_1143_5_Admiral_Kuznetsov,
|
||||||
|
Dry_cargo_ship_Ivanov,
|
||||||
|
Tanker_Elnya_160,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import AirDefence, Armor, Artillery, Infantry, Unarmed
|
||||||
|
|
||||||
Russia_1965 = {
|
Russia_1965 = {
|
||||||
"country": "Russia",
|
"country": "Russia",
|
||||||
|
|||||||
@ -1,8 +1,31 @@
|
|||||||
from dcs.helicopters import Mi_8MT, Mi_24V
|
from dcs.helicopters import (
|
||||||
from dcs.planes import MiG_21Bis, MiG_23MLD, MiG_25PD, MiG_29A, Su_17M4, Su_24M, Su_25, IL_76MD, IL_78M, An_26B, An_30M, \
|
Mi_24V,
|
||||||
Yak_40, A_50
|
Mi_8MT,
|
||||||
from dcs.ships import *
|
)
|
||||||
from dcs.vehicles import AirDefence, Armor, Unarmed, Infantry, Artillery
|
from dcs.planes import (
|
||||||
|
A_50,
|
||||||
|
An_26B,
|
||||||
|
An_30M,
|
||||||
|
IL_76MD,
|
||||||
|
IL_78M,
|
||||||
|
MiG_21Bis,
|
||||||
|
MiG_23MLD,
|
||||||
|
MiG_25PD,
|
||||||
|
MiG_29A,
|
||||||
|
Su_17M4,
|
||||||
|
Su_24M,
|
||||||
|
Su_25,
|
||||||
|
Yak_40,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Bulk_cargo_ship_Yakushev,
|
||||||
|
CGN_1144_2_Pyotr_Velikiy,
|
||||||
|
CV_1143_5_Admiral_Kuznetsov,
|
||||||
|
Dry_cargo_ship_Ivanov,
|
||||||
|
FF_1135M_Rezky,
|
||||||
|
Tanker_Elnya_160,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import AirDefence, Armor, Artillery, Infantry, Unarmed
|
||||||
|
|
||||||
Russia_1975 = {
|
Russia_1975 = {
|
||||||
"country": "Russia",
|
"country": "Russia",
|
||||||
|
|||||||
@ -1,7 +1,39 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
Ka_50,
|
||||||
from dcs.ships import *
|
Mi_24V,
|
||||||
from dcs.vehicles import *
|
Mi_8MT,
|
||||||
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
A_50,
|
||||||
|
An_26B,
|
||||||
|
An_30M,
|
||||||
|
IL_76MD,
|
||||||
|
IL_78M,
|
||||||
|
MiG_23MLD,
|
||||||
|
MiG_25PD,
|
||||||
|
MiG_29A,
|
||||||
|
MiG_29S,
|
||||||
|
MiG_31,
|
||||||
|
Su_24M,
|
||||||
|
Su_25,
|
||||||
|
Su_27,
|
||||||
|
Yak_40,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Bulk_cargo_ship_Yakushev,
|
||||||
|
CV_1143_5_Admiral_Kuznetsov,
|
||||||
|
Dry_cargo_ship_Ivanov,
|
||||||
|
FF_1135M_Rezky,
|
||||||
|
FSG_1241_1MP_Molniya,
|
||||||
|
Tanker_Elnya_160,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Artillery,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
Russia_1990 = {
|
Russia_1990 = {
|
||||||
"country": "Russia",
|
"country": "Russia",
|
||||||
|
|||||||
@ -1,7 +1,42 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
Ka_50,
|
||||||
from dcs.ships import *
|
Mi_24V,
|
||||||
from dcs.vehicles import *
|
Mi_28N,
|
||||||
|
Mi_8MT,
|
||||||
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
A_50,
|
||||||
|
An_26B,
|
||||||
|
An_30M,
|
||||||
|
IL_76MD,
|
||||||
|
IL_78M,
|
||||||
|
L_39ZA,
|
||||||
|
MiG_29S,
|
||||||
|
MiG_31,
|
||||||
|
Su_24M,
|
||||||
|
Su_25,
|
||||||
|
Su_25T,
|
||||||
|
Su_27,
|
||||||
|
Su_30,
|
||||||
|
Su_33,
|
||||||
|
Su_34,
|
||||||
|
Yak_40,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Bulk_cargo_ship_Yakushev,
|
||||||
|
CV_1143_5_Admiral_Kuznetsov,
|
||||||
|
Dry_cargo_ship_Ivanov,
|
||||||
|
FF_1135M_Rezky,
|
||||||
|
FSG_1241_1MP_Molniya,
|
||||||
|
Tanker_Elnya_160,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Artillery,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
Russia_2010 = {
|
Russia_2010 = {
|
||||||
"country": "Russia",
|
"country": "Russia",
|
||||||
|
|||||||
@ -1,6 +1,26 @@
|
|||||||
from dcs.planes import *
|
from dcs.planes import (
|
||||||
from dcs.ships import *
|
AV8BNA,
|
||||||
from dcs.vehicles import *
|
C_101CC,
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
FA_18C_hornet,
|
||||||
|
F_5E_3,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
Oliver_Hazzard_Perry_class,
|
||||||
|
Ticonderoga_class,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
Spain_1990 = {
|
Spain_1990 = {
|
||||||
"country": "Spain",
|
"country": "Spain",
|
||||||
|
|||||||
@ -1,7 +1,21 @@
|
|||||||
from dcs.vehicles import *
|
from dcs.helicopters import (
|
||||||
from dcs.ships import *
|
UH_1H,
|
||||||
from dcs.planes import *
|
)
|
||||||
from dcs.helicopters import *
|
from dcs.planes import (
|
||||||
|
AJS37,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Bulk_cargo_ship_Yakushev,
|
||||||
|
CV_1143_5_Admiral_Kuznetsov,
|
||||||
|
Dry_cargo_ship_Ivanov,
|
||||||
|
Tanker_Elnya_160,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
Sweden_1990 = {
|
Sweden_1990 = {
|
||||||
"country": "Sweden",
|
"country": "Sweden",
|
||||||
|
|||||||
@ -1,6 +1,35 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
Mi_24V,
|
||||||
from dcs.vehicles import *
|
Mi_8MT,
|
||||||
|
SA342L,
|
||||||
|
SA342M,
|
||||||
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
A_50,
|
||||||
|
An_26B,
|
||||||
|
An_30M,
|
||||||
|
IL_76MD,
|
||||||
|
IL_78M,
|
||||||
|
L_39ZA,
|
||||||
|
MiG_15bis,
|
||||||
|
MiG_19P,
|
||||||
|
MiG_21Bis,
|
||||||
|
MiG_23MLD,
|
||||||
|
MiG_25PD,
|
||||||
|
MiG_29S,
|
||||||
|
SpitfireLFMkIX,
|
||||||
|
SpitfireLFMkIXCW,
|
||||||
|
Su_17M4,
|
||||||
|
Su_24M,
|
||||||
|
Yak_40,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Artillery,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
Syria_2011 = {
|
Syria_2011 = {
|
||||||
"country": "Syria",
|
"country": "Syria",
|
||||||
|
|||||||
@ -1,7 +1,26 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
AH_1W,
|
||||||
from dcs.ships import *
|
UH_1H,
|
||||||
from dcs.vehicles import *
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
F_16C_50,
|
||||||
|
F_4E,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
Turkey_2005 = {
|
Turkey_2005 = {
|
||||||
"country": "Turkey",
|
"country": "Turkey",
|
||||||
|
|||||||
@ -1,7 +1,27 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
AH_64D,
|
||||||
from dcs.ships import *
|
)
|
||||||
from dcs.vehicles import *
|
from dcs.planes import (
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
F_16C_50,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
M_2000C,
|
||||||
|
Mirage_2000_5,
|
||||||
|
WingLoong_I,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
UAE_2005 = {
|
UAE_2005 = {
|
||||||
"country": "United Arab Emirates",
|
"country": "United Arab Emirates",
|
||||||
|
|||||||
@ -1,6 +1,19 @@
|
|||||||
from dcs.planes import *
|
from dcs.planes import (
|
||||||
from dcs.ships import *
|
A_20G,
|
||||||
from dcs.vehicles import *
|
B_17G,
|
||||||
|
P_47D_30,
|
||||||
|
P_51D,
|
||||||
|
P_51D_30_NA,
|
||||||
|
SpitfireLFMkIX,
|
||||||
|
SpitfireLFMkIXCW,
|
||||||
|
)
|
||||||
|
from dcs.ships import LCVP__Higgins_boat, LST_Mk_II, LS_Samuel_Chase
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
from game.data.building_data import WW2_ALLIES_BUILDINGS
|
from game.data.building_data import WW2_ALLIES_BUILDINGS
|
||||||
from game.data.doctrine import WWII_DOCTRINE
|
from game.data.doctrine import WWII_DOCTRINE
|
||||||
|
|||||||
@ -1,7 +1,29 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
AH_64A,
|
||||||
from dcs.ships import *
|
SA342M,
|
||||||
from dcs.vehicles import *
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
AV8BNA,
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
F_4E,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
Tornado_GR4,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
Oliver_Hazzard_Perry_class,
|
||||||
|
Ticonderoga_class,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
UnitedKingdom_1990 = {
|
UnitedKingdom_1990 = {
|
||||||
"country": "UK",
|
"country": "UK",
|
||||||
|
|||||||
@ -1,7 +1,33 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
Mi_24V,
|
||||||
from dcs.ships import *
|
Mi_8MT,
|
||||||
from dcs.vehicles import *
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
A_50,
|
||||||
|
An_26B,
|
||||||
|
An_30M,
|
||||||
|
IL_76MD,
|
||||||
|
IL_78M,
|
||||||
|
L_39ZA,
|
||||||
|
MiG_29S,
|
||||||
|
Su_24M,
|
||||||
|
Su_25,
|
||||||
|
Su_25T,
|
||||||
|
Su_27,
|
||||||
|
Yak_40,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Bulk_cargo_ship_Yakushev,
|
||||||
|
CV_1143_5_Admiral_Kuznetsov,
|
||||||
|
Dry_cargo_ship_Ivanov,
|
||||||
|
Tanker_Elnya_160,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
Ukraine_2010 = {
|
Ukraine_2010 = {
|
||||||
"country": "Ukraine",
|
"country": "Ukraine",
|
||||||
|
|||||||
@ -1,7 +1,36 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
AH_64D,
|
||||||
from dcs.ships import *
|
Ka_50,
|
||||||
from dcs.vehicles import *
|
SA342L,
|
||||||
|
SA342M,
|
||||||
|
UH_1H,
|
||||||
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
FA_18C_hornet,
|
||||||
|
F_15C,
|
||||||
|
F_16C_50,
|
||||||
|
F_5E_3,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
Su_27,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
Oliver_Hazzard_Perry_class,
|
||||||
|
Ticonderoga_class,
|
||||||
|
USS_Arleigh_Burke_IIa,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Artillery,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
US_Aggressors = {
|
US_Aggressors = {
|
||||||
"country": "USAF Aggressors",
|
"country": "USAF Aggressors",
|
||||||
|
|||||||
@ -1,6 +1,20 @@
|
|||||||
from dcs.planes import *
|
from dcs.planes import (
|
||||||
from dcs.ships import *
|
A_20G,
|
||||||
from dcs.vehicles import *
|
B_17G,
|
||||||
|
P_47D_30,
|
||||||
|
P_51D,
|
||||||
|
P_51D_30_NA,
|
||||||
|
SpitfireLFMkIX,
|
||||||
|
SpitfireLFMkIXCW,
|
||||||
|
)
|
||||||
|
from dcs.ships import LCVP__Higgins_boat, LST_Mk_II, LS_Samuel_Chase
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Artillery,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
from game.data.building_data import WW2_ALLIES_BUILDINGS
|
from game.data.building_data import WW2_ALLIES_BUILDINGS
|
||||||
from game.data.doctrine import WWII_DOCTRINE
|
from game.data.doctrine import WWII_DOCTRINE
|
||||||
|
|||||||
@ -1,7 +1,22 @@
|
|||||||
from dcs.vehicles import *
|
from dcs.planes import (
|
||||||
from dcs.ships import *
|
C_130,
|
||||||
from dcs.planes import *
|
E_3A,
|
||||||
from dcs.helicopters import *
|
F_86F_Sabre,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
P_51D,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
USA_1955 = {
|
USA_1955 = {
|
||||||
"country": "USA",
|
"country": "USA",
|
||||||
|
|||||||
@ -1,7 +1,25 @@
|
|||||||
from dcs.vehicles import *
|
from dcs.helicopters import (
|
||||||
from dcs.ships import *
|
UH_1H,
|
||||||
from dcs.planes import *
|
)
|
||||||
from dcs.helicopters import *
|
from dcs.planes import (
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
F_86F_Sabre,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
P_51D,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
USA_1960 = {
|
USA_1960 = {
|
||||||
"country": "USA",
|
"country": "USA",
|
||||||
|
|||||||
@ -1,7 +1,26 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
UH_1H,
|
||||||
from dcs.ships import *
|
)
|
||||||
from dcs.vehicles import *
|
from dcs.planes import (
|
||||||
|
B_52H,
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
F_4E,
|
||||||
|
F_5E_3,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
USA_1965 = {
|
USA_1965 = {
|
||||||
"country": "USA",
|
"country": "USA",
|
||||||
|
|||||||
@ -1,7 +1,34 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
AH_64A,
|
||||||
from dcs.ships import *
|
UH_1H,
|
||||||
from dcs.vehicles import *
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
AV8BNA,
|
||||||
|
A_10A,
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
FA_18C_hornet,
|
||||||
|
F_14B,
|
||||||
|
F_15C,
|
||||||
|
F_15E,
|
||||||
|
F_16C_50,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
Oliver_Hazzard_Perry_class,
|
||||||
|
Ticonderoga_class,
|
||||||
|
USS_Arleigh_Burke_IIa,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
USA_1990 = {
|
USA_1990 = {
|
||||||
"country": "USA",
|
"country": "USA",
|
||||||
|
|||||||
@ -1,7 +1,36 @@
|
|||||||
from dcs.helicopters import *
|
from dcs.helicopters import (
|
||||||
from dcs.planes import *
|
AH_64D,
|
||||||
from dcs.ships import *
|
UH_1H,
|
||||||
from dcs.vehicles import *
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
AV8BNA,
|
||||||
|
A_10C,
|
||||||
|
A_10C_2,
|
||||||
|
C_130,
|
||||||
|
E_3A,
|
||||||
|
FA_18C_hornet,
|
||||||
|
F_14B,
|
||||||
|
F_15C,
|
||||||
|
F_15E,
|
||||||
|
F_16C_50,
|
||||||
|
KC130,
|
||||||
|
KC_135,
|
||||||
|
MQ_9_Reaper,
|
||||||
|
)
|
||||||
|
from dcs.ships import (
|
||||||
|
Armed_speedboat,
|
||||||
|
CVN_74_John_C__Stennis,
|
||||||
|
LHA_1_Tarawa,
|
||||||
|
Ticonderoga_class,
|
||||||
|
USS_Arleigh_Burke_IIa,
|
||||||
|
)
|
||||||
|
from dcs.vehicles import (
|
||||||
|
AirDefence,
|
||||||
|
Armor,
|
||||||
|
Artillery,
|
||||||
|
Infantry,
|
||||||
|
Unarmed,
|
||||||
|
)
|
||||||
|
|
||||||
USA_2005 = {
|
USA_2005 = {
|
||||||
"country": "USA",
|
"country": "USA",
|
||||||
|
|||||||
101
game/game.py
101
game/game.py
@ -1,10 +1,32 @@
|
|||||||
|
import logging
|
||||||
|
import math
|
||||||
|
import random
|
||||||
|
import sys
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from game.db import REWARDS, PLAYER_BUDGET_BASE, sys
|
from dcs.action import Coalition
|
||||||
|
from dcs.mapping import Point
|
||||||
|
from dcs.task import CAP, CAS, PinpointStrike, Task
|
||||||
|
from dcs.unittype import UnitType
|
||||||
|
from dcs.vehicles import AirDefence
|
||||||
|
|
||||||
|
from game import db
|
||||||
|
from game.db import PLAYER_BUDGET_BASE, REWARDS
|
||||||
|
from game.inventory import GlobalAircraftInventory
|
||||||
from game.models.game_stats import GameStats
|
from game.models.game_stats import GameStats
|
||||||
from gen.flights.ai_flight_planner import FlightPlanner
|
from gen.ato import AirTaskingOrder
|
||||||
|
from gen.conflictgen import Conflict
|
||||||
|
from gen.flights.ai_flight_planner import CoalitionMissionPlanner
|
||||||
|
from gen.flights.closestairfields import ObjectiveDistanceCache
|
||||||
from gen.ground_forces.ai_ground_planner import GroundPlanner
|
from gen.ground_forces.ai_ground_planner import GroundPlanner
|
||||||
from .event import *
|
from theater import ConflictTheater, ControlPoint
|
||||||
|
from theater.conflicttheater import IMPORTANCE_HIGH, IMPORTANCE_LOW
|
||||||
|
from . import persistency
|
||||||
|
from .debriefing import Debriefing
|
||||||
|
from .event.event import Event, UnitsDeliveryEvent
|
||||||
|
from .event.frontlineattack import FrontlineAttackEvent
|
||||||
|
from .infos.information import Information
|
||||||
from .settings import Settings
|
from .settings import Settings
|
||||||
|
|
||||||
COMMISION_UNIT_VARIETY = 4
|
COMMISION_UNIT_VARIETY = 4
|
||||||
@ -45,20 +67,11 @@ PLAYER_BUDGET_IMPORTANCE_LOG = 2
|
|||||||
|
|
||||||
|
|
||||||
class Game:
|
class Game:
|
||||||
settings = None # type: Settings
|
def __init__(self, player_name: str, enemy_name: str,
|
||||||
budget = PLAYER_BUDGET_INITIAL
|
theater: ConflictTheater, start_date: datetime,
|
||||||
events = None # type: typing.List[Event]
|
settings: Settings):
|
||||||
pending_transfers = None # type: typing.Dict[]
|
|
||||||
ignored_cps = None # type: typing.Collection[ControlPoint]
|
|
||||||
turn = 0
|
|
||||||
game_stats: GameStats = None
|
|
||||||
|
|
||||||
current_unit_id = 0
|
|
||||||
current_group_id = 0
|
|
||||||
|
|
||||||
def __init__(self, player_name: str, enemy_name: str, theater: ConflictTheater, start_date: datetime, settings):
|
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
self.events = []
|
self.events: List[Event] = []
|
||||||
self.theater = theater
|
self.theater = theater
|
||||||
self.player_name = player_name
|
self.player_name = player_name
|
||||||
self.player_country = db.FACTIONS[player_name]["country"]
|
self.player_country = db.FACTIONS[player_name]["country"]
|
||||||
@ -68,17 +81,25 @@ class Game:
|
|||||||
self.date = datetime(start_date.year, start_date.month, start_date.day)
|
self.date = datetime(start_date.year, start_date.month, start_date.day)
|
||||||
self.game_stats = GameStats()
|
self.game_stats = GameStats()
|
||||||
self.game_stats.update(self)
|
self.game_stats.update(self)
|
||||||
self.planners = {}
|
self.ground_planners: Dict[int, GroundPlanner] = {}
|
||||||
self.ground_planners = {}
|
|
||||||
self.informations = []
|
self.informations = []
|
||||||
self.informations.append(Information("Game Start", "-" * 40, 0))
|
self.informations.append(Information("Game Start", "-" * 40, 0))
|
||||||
self.__culling_points = self.compute_conflicts_position()
|
self.__culling_points = self.compute_conflicts_position()
|
||||||
self.__frontlineData = []
|
self.__destroyed_units: List[str] = []
|
||||||
self.__destroyed_units = []
|
|
||||||
self.jtacs = []
|
|
||||||
self.savepath = ""
|
self.savepath = ""
|
||||||
|
self.budget = PLAYER_BUDGET_INITIAL
|
||||||
|
self.current_unit_id = 0
|
||||||
|
self.current_group_id = 0
|
||||||
|
|
||||||
|
self.blue_ato = AirTaskingOrder()
|
||||||
|
self.red_ato = AirTaskingOrder()
|
||||||
|
|
||||||
|
self.aircraft_inventory = GlobalAircraftInventory(
|
||||||
|
self.theater.controlpoints
|
||||||
|
)
|
||||||
|
|
||||||
self.sanitize_sides()
|
self.sanitize_sides()
|
||||||
|
self.on_load()
|
||||||
|
|
||||||
|
|
||||||
def sanitize_sides(self):
|
def sanitize_sides(self):
|
||||||
@ -95,11 +116,11 @@ class Game:
|
|||||||
self.enemy_country = "Russia"
|
self.enemy_country = "Russia"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def player_faction(self):
|
def player_faction(self) -> Dict[str, Any]:
|
||||||
return db.FACTIONS[self.player_name]
|
return db.FACTIONS[self.player_name]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enemy_faction(self):
|
def enemy_faction(self) -> Dict[str, Any]:
|
||||||
return db.FACTIONS[self.enemy_name]
|
return db.FACTIONS[self.enemy_name]
|
||||||
|
|
||||||
def _roll(self, prob, mult):
|
def _roll(self, prob, mult):
|
||||||
@ -116,7 +137,7 @@ class Game:
|
|||||||
for player_cp, enemy_cp in self.theater.conflicts(True):
|
for player_cp, enemy_cp in self.theater.conflicts(True):
|
||||||
self._generate_player_event(FrontlineAttackEvent, player_cp, enemy_cp)
|
self._generate_player_event(FrontlineAttackEvent, player_cp, enemy_cp)
|
||||||
|
|
||||||
def commision_unit_types(self, cp: ControlPoint, for_task: Task) -> typing.Collection[UnitType]:
|
def commision_unit_types(self, cp: ControlPoint, for_task: Task) -> List[UnitType]:
|
||||||
importance_factor = (cp.importance - IMPORTANCE_LOW) / (IMPORTANCE_HIGH - IMPORTANCE_LOW)
|
importance_factor = (cp.importance - IMPORTANCE_LOW) / (IMPORTANCE_HIGH - IMPORTANCE_LOW)
|
||||||
|
|
||||||
if for_task == AirDefence and not self.settings.sams:
|
if for_task == AirDefence and not self.settings.sams:
|
||||||
@ -190,12 +211,14 @@ class Game:
|
|||||||
|
|
||||||
def is_player_attack(self, event):
|
def is_player_attack(self, event):
|
||||||
if isinstance(event, Event):
|
if isinstance(event, Event):
|
||||||
return event.attacker_name == self.player_name
|
return event and event.attacker_name and event.attacker_name == self.player_name
|
||||||
else:
|
else:
|
||||||
return event.name == self.player_name
|
return event and event.name and event.name == self.player_name
|
||||||
|
|
||||||
def pass_turn(self, no_action=False, ignored_cps: typing.Collection[ControlPoint] = None):
|
def on_load(self) -> None:
|
||||||
|
ObjectiveDistanceCache.set_theater(self.theater)
|
||||||
|
|
||||||
|
def pass_turn(self, no_action=False):
|
||||||
logging.info("Pass turn")
|
logging.info("Pass turn")
|
||||||
self.informations.append(Information("End of turn #" + str(self.turn), "-" * 40, 0))
|
self.informations.append(Information("End of turn #" + str(self.turn), "-" * 40, 0))
|
||||||
self.turn = self.turn + 1
|
self.turn = self.turn + 1
|
||||||
@ -219,26 +242,24 @@ class Game:
|
|||||||
if not cp.is_carrier and not cp.is_lha:
|
if not cp.is_carrier and not cp.is_lha:
|
||||||
cp.base.affect_strength(-PLAYER_BASE_STRENGTH_RECOVERY)
|
cp.base.affect_strength(-PLAYER_BASE_STRENGTH_RECOVERY)
|
||||||
|
|
||||||
self.ignored_cps = []
|
self.events = []
|
||||||
if ignored_cps:
|
|
||||||
self.ignored_cps = ignored_cps
|
|
||||||
|
|
||||||
self.events = [] # type: typing.List[Event]
|
|
||||||
self._generate_events()
|
self._generate_events()
|
||||||
|
|
||||||
# Update statistics
|
# Update statistics
|
||||||
self.game_stats.update(self)
|
self.game_stats.update(self)
|
||||||
|
|
||||||
|
self.aircraft_inventory.reset()
|
||||||
|
for cp in self.theater.controlpoints:
|
||||||
|
self.aircraft_inventory.set_from_control_point(cp)
|
||||||
|
|
||||||
# Plan flights & combat for next turn
|
# Plan flights & combat for next turn
|
||||||
self.__culling_points = self.compute_conflicts_position()
|
self.__culling_points = self.compute_conflicts_position()
|
||||||
self.planners = {}
|
|
||||||
self.ground_planners = {}
|
self.ground_planners = {}
|
||||||
|
self.blue_ato.clear()
|
||||||
|
self.red_ato.clear()
|
||||||
|
CoalitionMissionPlanner(self, is_player=True).plan_missions()
|
||||||
|
CoalitionMissionPlanner(self, is_player=False).plan_missions()
|
||||||
for cp in self.theater.controlpoints:
|
for cp in self.theater.controlpoints:
|
||||||
if cp.has_runway():
|
|
||||||
planner = FlightPlanner(cp, self)
|
|
||||||
planner.plan_flights()
|
|
||||||
self.planners[cp.id] = planner
|
|
||||||
|
|
||||||
if cp.has_frontline:
|
if cp.has_frontline:
|
||||||
gplanner = GroundPlanner(cp, self)
|
gplanner = GroundPlanner(cp, self)
|
||||||
gplanner.plan_groundwar()
|
gplanner.plan_groundwar()
|
||||||
@ -408,10 +429,10 @@ class Game:
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
def get_player_coalition(self):
|
def get_player_coalition(self):
|
||||||
return dcs.action.Coalition.Blue
|
return Coalition.Blue
|
||||||
|
|
||||||
def get_enemy_coalition(self):
|
def get_enemy_coalition(self):
|
||||||
return dcs.action.Coalition.Red
|
return Coalition.Red
|
||||||
|
|
||||||
def get_player_color(self):
|
def get_player_color(self):
|
||||||
return "blue"
|
return "blue"
|
||||||
|
|||||||
130
game/inventory.py
Normal file
130
game/inventory.py
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
"""Inventory management APIs."""
|
||||||
|
from collections import defaultdict
|
||||||
|
from typing import Dict, Iterable, Iterator, Set, Tuple
|
||||||
|
|
||||||
|
from dcs.unittype import UnitType
|
||||||
|
|
||||||
|
from gen.flights.flight import Flight
|
||||||
|
from theater import ControlPoint
|
||||||
|
|
||||||
|
|
||||||
|
class ControlPointAircraftInventory:
|
||||||
|
"""Aircraft inventory for a single control point."""
|
||||||
|
|
||||||
|
def __init__(self, control_point: ControlPoint) -> None:
|
||||||
|
self.control_point = control_point
|
||||||
|
self.inventory: Dict[UnitType, int] = defaultdict(int)
|
||||||
|
|
||||||
|
def add_aircraft(self, aircraft: UnitType, count: int) -> None:
|
||||||
|
"""Adds aircraft to the inventory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
aircraft: The type of aircraft to add.
|
||||||
|
count: The number of aircraft to add.
|
||||||
|
"""
|
||||||
|
self.inventory[aircraft] += count
|
||||||
|
|
||||||
|
def remove_aircraft(self, aircraft: UnitType, count: int) -> None:
|
||||||
|
"""Removes aircraft from the inventory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
aircraft: The type of aircraft to remove.
|
||||||
|
count: The number of aircraft to remove.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: The control point cannot fulfill the requested number of
|
||||||
|
aircraft.
|
||||||
|
"""
|
||||||
|
available = self.inventory[aircraft]
|
||||||
|
if available < count:
|
||||||
|
raise ValueError(
|
||||||
|
f"Cannot remove {count} {aircraft.id} from "
|
||||||
|
f"{self.control_point.name}. Only have {available}."
|
||||||
|
)
|
||||||
|
self.inventory[aircraft] -= count
|
||||||
|
|
||||||
|
def available(self, aircraft: UnitType) -> int:
|
||||||
|
"""Returns the number of available aircraft of the given type.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
aircraft: The type of aircraft to query.
|
||||||
|
"""
|
||||||
|
return self.inventory[aircraft]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def types_available(self) -> Iterator[UnitType]:
|
||||||
|
"""Iterates over all available aircraft types."""
|
||||||
|
for aircraft, count in self.inventory.items():
|
||||||
|
if count > 0:
|
||||||
|
yield aircraft
|
||||||
|
|
||||||
|
@property
|
||||||
|
def all_aircraft(self) -> Iterator[Tuple[UnitType, int]]:
|
||||||
|
"""Iterates over all available aircraft types, including amounts."""
|
||||||
|
for aircraft, count in self.inventory.items():
|
||||||
|
if count > 0:
|
||||||
|
yield aircraft, count
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total_available(self) -> int:
|
||||||
|
"""Returns the total number of aircraft available."""
|
||||||
|
# TODO: Remove?
|
||||||
|
# This probably isn't actually useful. It's used by the AI flight
|
||||||
|
# planner to determine how many flights of a given type it should
|
||||||
|
# allocate, but it should probably be making that decision based on the
|
||||||
|
# number of aircraft available to perform a particular role.
|
||||||
|
return sum(self.inventory.values())
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
"""Clears all aircraft from the inventory."""
|
||||||
|
self.inventory.clear()
|
||||||
|
|
||||||
|
|
||||||
|
class GlobalAircraftInventory:
|
||||||
|
"""Game-wide aircraft inventory."""
|
||||||
|
def __init__(self, control_points: Iterable[ControlPoint]) -> None:
|
||||||
|
self.inventories: Dict[ControlPoint, ControlPointAircraftInventory] = {
|
||||||
|
cp: ControlPointAircraftInventory(cp) for cp in control_points
|
||||||
|
}
|
||||||
|
|
||||||
|
def reset(self) -> None:
|
||||||
|
"""Clears all control points and their inventories."""
|
||||||
|
for inventory in self.inventories.values():
|
||||||
|
inventory.clear()
|
||||||
|
|
||||||
|
def set_from_control_point(self, control_point: ControlPoint) -> None:
|
||||||
|
"""Set the control point's aircraft inventory.
|
||||||
|
|
||||||
|
If the inventory for the given control point has already been set for
|
||||||
|
the turn, it will be overwritten.
|
||||||
|
"""
|
||||||
|
inventory = self.inventories[control_point]
|
||||||
|
for aircraft, count in control_point.base.aircraft.items():
|
||||||
|
inventory.add_aircraft(aircraft, count)
|
||||||
|
|
||||||
|
def for_control_point(
|
||||||
|
self,
|
||||||
|
control_point: ControlPoint) -> ControlPointAircraftInventory:
|
||||||
|
"""Returns the inventory specific to the given control point."""
|
||||||
|
return self.inventories[control_point]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available_types_for_player(self) -> Iterator[UnitType]:
|
||||||
|
"""Iterates over all aircraft types available to the player."""
|
||||||
|
seen: Set[UnitType] = set()
|
||||||
|
for control_point, inventory in self.inventories.items():
|
||||||
|
if control_point.captured:
|
||||||
|
for aircraft in inventory.types_available:
|
||||||
|
if aircraft not in seen:
|
||||||
|
seen.add(aircraft)
|
||||||
|
yield aircraft
|
||||||
|
|
||||||
|
def claim_for_flight(self, flight: Flight) -> None:
|
||||||
|
"""Removes aircraft from the inventory for the given flight."""
|
||||||
|
inventory = self.for_control_point(flight.from_cp)
|
||||||
|
inventory.remove_aircraft(flight.unit_type, flight.count)
|
||||||
|
|
||||||
|
def return_from_flight(self, flight: Flight) -> None:
|
||||||
|
"""Returns a flight's aircraft to the inventory."""
|
||||||
|
inventory = self.for_control_point(flight.from_cp)
|
||||||
|
inventory.add_aircraft(flight.unit_type, flight.count)
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
class FactionTurnMetadata:
|
class FactionTurnMetadata:
|
||||||
"""
|
"""
|
||||||
Store metadata about a faction
|
Store metadata about a faction
|
||||||
@ -31,10 +33,8 @@ class GameStats:
|
|||||||
Store statistics for the current game
|
Store statistics for the current game
|
||||||
"""
|
"""
|
||||||
|
|
||||||
data_per_turn: [GameTurnMetadata] = []
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.data_per_turn = []
|
self.data_per_turn: List[GameTurnMetadata] = []
|
||||||
|
|
||||||
def update(self, game):
|
def update(self, game):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
from game.db import assigned_units_split
|
from dcs.terrain.terrain import Terrain
|
||||||
|
|
||||||
from .operation import *
|
|
||||||
|
|
||||||
|
from gen.conflictgen import Conflict
|
||||||
|
from .operation import Operation
|
||||||
|
from .. import db
|
||||||
|
|
||||||
MAX_DISTANCE_BETWEEN_GROUPS = 12000
|
MAX_DISTANCE_BETWEEN_GROUPS = 12000
|
||||||
|
|
||||||
|
|||||||
@ -1,30 +1,51 @@
|
|||||||
from typing import Set
|
import logging
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Optional, Set
|
||||||
|
|
||||||
from gen import *
|
from dcs import Mission
|
||||||
from gen.airfields import AIRFIELD_DATA
|
from dcs.action import DoScript, DoScriptFile
|
||||||
from gen.beacons import load_beacons_for_terrain
|
from dcs.coalition import Coalition
|
||||||
from gen.radios import RadioRegistry
|
|
||||||
from gen.tacan import TacanRegistry
|
|
||||||
from dcs.countries import country_dict
|
from dcs.countries import country_dict
|
||||||
from dcs.lua.parse import loads
|
from dcs.lua.parse import loads
|
||||||
|
from dcs.mapping import Point
|
||||||
from dcs.terrain.terrain import Terrain
|
from dcs.terrain.terrain import Terrain
|
||||||
from userdata.debriefing import *
|
from dcs.translation import String
|
||||||
|
from dcs.triggers import TriggerStart
|
||||||
|
from dcs.unittype import UnitType
|
||||||
|
|
||||||
|
from gen import Conflict, VisualGenerator
|
||||||
|
from gen.aircraft import AIRCRAFT_DATA, AircraftConflictGenerator, FlightData
|
||||||
|
from gen.airfields import AIRFIELD_DATA
|
||||||
|
from gen.airsupportgen import AirSupport, AirSupportConflictGenerator
|
||||||
|
from gen.armor import GroundConflictGenerator, JtacInfo
|
||||||
|
from gen.beacons import load_beacons_for_terrain
|
||||||
|
from gen.briefinggen import BriefingGenerator
|
||||||
|
from gen.environmentgen import EnviromentGenerator
|
||||||
|
from gen.forcedoptionsgen import ForcedOptionsGenerator
|
||||||
|
from gen.groundobjectsgen import GroundObjectsGenerator
|
||||||
|
from gen.kneeboard import KneeboardGenerator
|
||||||
|
from gen.radios import RadioFrequency, RadioRegistry
|
||||||
|
from gen.tacan import TacanRegistry
|
||||||
|
from gen.triggergen import TRIGGER_RADIUS_MEDIUM, TriggersGenerator
|
||||||
|
from theater import ControlPoint
|
||||||
|
from .. import db
|
||||||
|
from ..debriefing import Debriefing
|
||||||
|
|
||||||
|
|
||||||
class Operation:
|
class Operation:
|
||||||
attackers_starting_position = None # type: db.StartingPosition
|
attackers_starting_position = None # type: db.StartingPosition
|
||||||
defenders_starting_position = None # type: db.StartingPosition
|
defenders_starting_position = None # type: db.StartingPosition
|
||||||
|
|
||||||
current_mission = None # type: dcs.Mission
|
current_mission = None # type: Mission
|
||||||
regular_mission = None # type: dcs.Mission
|
regular_mission = None # type: Mission
|
||||||
quick_mission = None # type: dcs.Mission
|
quick_mission = None # type: Mission
|
||||||
conflict = None # type: Conflict
|
conflict = None # type: Conflict
|
||||||
armorgen = None # type: ArmorConflictGenerator
|
|
||||||
airgen = None # type: AircraftConflictGenerator
|
airgen = None # type: AircraftConflictGenerator
|
||||||
triggersgen = None # type: TriggersGenerator
|
triggersgen = None # type: TriggersGenerator
|
||||||
airsupportgen = None # type: AirSupportConflictGenerator
|
airsupportgen = None # type: AirSupportConflictGenerator
|
||||||
visualgen = None # type: VisualGenerator
|
visualgen = None # type: VisualGenerator
|
||||||
envgen = None # type: EnvironmentGenerator
|
envgen = None # type: EnviromentGenerator
|
||||||
groundobjectgen = None # type: GroundObjectsGenerator
|
groundobjectgen = None # type: GroundObjectsGenerator
|
||||||
briefinggen = None # type: BriefingGenerator
|
briefinggen = None # type: BriefingGenerator
|
||||||
forcedoptionsgen = None # type: ForcedOptionsGenerator
|
forcedoptionsgen = None # type: ForcedOptionsGenerator
|
||||||
@ -43,7 +64,7 @@ class Operation:
|
|||||||
defender_name: str,
|
defender_name: str,
|
||||||
from_cp: ControlPoint,
|
from_cp: ControlPoint,
|
||||||
departure_cp: ControlPoint,
|
departure_cp: ControlPoint,
|
||||||
to_cp: ControlPoint = None):
|
to_cp: ControlPoint):
|
||||||
self.game = game
|
self.game = game
|
||||||
self.attacker_name = attacker_name
|
self.attacker_name = attacker_name
|
||||||
self.attacker_country = db.FACTIONS[attacker_name]["country"]
|
self.attacker_country = db.FACTIONS[attacker_name]["country"]
|
||||||
@ -55,7 +76,7 @@ class Operation:
|
|||||||
self.to_cp = to_cp
|
self.to_cp = to_cp
|
||||||
self.is_quick = False
|
self.is_quick = False
|
||||||
|
|
||||||
def units_of(self, country_name: str) -> typing.Collection[UnitType]:
|
def units_of(self, country_name: str) -> List[UnitType]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def is_successfull(self, debriefing: Debriefing) -> bool:
|
def is_successfull(self, debriefing: Debriefing) -> bool:
|
||||||
@ -68,32 +89,14 @@ class Operation:
|
|||||||
def initialize(self, mission: Mission, conflict: Conflict):
|
def initialize(self, mission: Mission, conflict: Conflict):
|
||||||
self.current_mission = mission
|
self.current_mission = mission
|
||||||
self.conflict = conflict
|
self.conflict = conflict
|
||||||
self.radio_registry = RadioRegistry()
|
self.briefinggen = BriefingGenerator(self.current_mission,
|
||||||
self.tacan_registry = TacanRegistry()
|
self.conflict, self.game)
|
||||||
self.airgen = AircraftConflictGenerator(
|
|
||||||
mission, conflict, self.game.settings, self.game,
|
|
||||||
self.radio_registry)
|
|
||||||
self.airsupportgen = AirSupportConflictGenerator(
|
|
||||||
mission, conflict, self.game, self.radio_registry,
|
|
||||||
self.tacan_registry)
|
|
||||||
self.triggersgen = TriggersGenerator(mission, conflict, self.game)
|
|
||||||
self.visualgen = VisualGenerator(mission, conflict, self.game)
|
|
||||||
self.envgen = EnviromentGenerator(mission, conflict, self.game)
|
|
||||||
self.forcedoptionsgen = ForcedOptionsGenerator(mission, conflict, self.game)
|
|
||||||
self.groundobjectgen = GroundObjectsGenerator(
|
|
||||||
mission,
|
|
||||||
conflict,
|
|
||||||
self.game,
|
|
||||||
self.radio_registry,
|
|
||||||
self.tacan_registry
|
|
||||||
)
|
|
||||||
self.briefinggen = BriefingGenerator(mission, conflict, self.game)
|
|
||||||
|
|
||||||
def prepare(self, terrain: Terrain, is_quick: bool):
|
def prepare(self, terrain: Terrain, is_quick: bool):
|
||||||
with open("resources/default_options.lua", "r") as f:
|
with open("resources/default_options.lua", "r") as f:
|
||||||
options_dict = loads(f.read())["options"]
|
options_dict = loads(f.read())["options"]
|
||||||
|
|
||||||
self.current_mission = dcs.Mission(terrain)
|
self.current_mission = Mission(terrain)
|
||||||
|
|
||||||
print(self.game.player_country)
|
print(self.game.player_country)
|
||||||
print(country_dict[db.country_id_from_name(self.game.player_country)])
|
print(country_dict[db.country_id_from_name(self.game.player_country)])
|
||||||
@ -124,9 +127,16 @@ class Operation:
|
|||||||
self.defenders_starting_position = None
|
self.defenders_starting_position = None
|
||||||
else:
|
else:
|
||||||
self.attackers_starting_position = self.departure_cp.at
|
self.attackers_starting_position = self.departure_cp.at
|
||||||
self.defenders_starting_position = self.to_cp.at
|
# TODO: Is this possible?
|
||||||
|
if self.to_cp is not None:
|
||||||
|
self.defenders_starting_position = self.to_cp.at
|
||||||
|
else:
|
||||||
|
self.defenders_starting_position = None
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
|
radio_registry = RadioRegistry()
|
||||||
|
tacan_registry = TacanRegistry()
|
||||||
|
|
||||||
# Dedup beacon/radio frequencies, since some maps have some frequencies
|
# Dedup beacon/radio frequencies, since some maps have some frequencies
|
||||||
# used multiple times.
|
# used multiple times.
|
||||||
beacons = load_beacons_for_terrain(self.game.theater.terrain.name)
|
beacons = load_beacons_for_terrain(self.game.theater.terrain.name)
|
||||||
@ -138,7 +148,7 @@ class Operation:
|
|||||||
logging.error(
|
logging.error(
|
||||||
f"TACAN beacon has no channel: {beacon.callsign}")
|
f"TACAN beacon has no channel: {beacon.callsign}")
|
||||||
else:
|
else:
|
||||||
self.tacan_registry.reserve(beacon.tacan_channel)
|
tacan_registry.reserve(beacon.tacan_channel)
|
||||||
|
|
||||||
for airfield, data in AIRFIELD_DATA.items():
|
for airfield, data in AIRFIELD_DATA.items():
|
||||||
if data.theater == self.game.theater.terrain.name:
|
if data.theater == self.game.theater.terrain.name:
|
||||||
@ -150,16 +160,26 @@ class Operation:
|
|||||||
# beacon list.
|
# beacon list.
|
||||||
|
|
||||||
for frequency in unique_map_frequencies:
|
for frequency in unique_map_frequencies:
|
||||||
self.radio_registry.reserve(frequency)
|
radio_registry.reserve(frequency)
|
||||||
|
|
||||||
# Generate meteo
|
# Generate meteo
|
||||||
|
envgen = EnviromentGenerator(self.current_mission, self.conflict,
|
||||||
|
self.game)
|
||||||
if self.environment_settings is None:
|
if self.environment_settings is None:
|
||||||
self.environment_settings = self.envgen.generate()
|
self.environment_settings = envgen.generate()
|
||||||
else:
|
else:
|
||||||
self.envgen.load(self.environment_settings)
|
envgen.load(self.environment_settings)
|
||||||
|
|
||||||
# Generate ground object first
|
# Generate ground object first
|
||||||
self.groundobjectgen.generate()
|
|
||||||
|
groundobjectgen = GroundObjectsGenerator(
|
||||||
|
self.current_mission,
|
||||||
|
self.conflict,
|
||||||
|
self.game,
|
||||||
|
radio_registry,
|
||||||
|
tacan_registry
|
||||||
|
)
|
||||||
|
groundobjectgen.generate()
|
||||||
|
|
||||||
# Generate destroyed units
|
# Generate destroyed units
|
||||||
for d in self.game.get_destroyed_units():
|
for d in self.game.get_destroyed_units():
|
||||||
@ -180,24 +200,27 @@ class Operation:
|
|||||||
dead=True,
|
dead=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Air Support (Tanker & Awacs)
|
# Air Support (Tanker & Awacs)
|
||||||
self.airsupportgen.generate(self.is_awacs_enabled)
|
airsupportgen = AirSupportConflictGenerator(
|
||||||
|
self.current_mission, self.conflict, self.game, radio_registry,
|
||||||
|
tacan_registry)
|
||||||
|
airsupportgen.generate(self.is_awacs_enabled)
|
||||||
|
|
||||||
# Generate Activity on the map
|
# Generate Activity on the map
|
||||||
for cp in self.game.theater.controlpoints:
|
airgen = AircraftConflictGenerator(
|
||||||
side = cp.captured
|
self.current_mission, self.conflict, self.game.settings, self.game,
|
||||||
if side:
|
radio_registry)
|
||||||
country = self.current_mission.country(self.game.player_country)
|
|
||||||
else:
|
airgen.generate_flights(
|
||||||
country = self.current_mission.country(self.game.enemy_country)
|
self.current_mission.country(self.game.player_country),
|
||||||
if cp.id in self.game.planners.keys():
|
self.game.blue_ato,
|
||||||
self.airgen.generate_flights(
|
groundobjectgen.runways
|
||||||
cp,
|
)
|
||||||
country,
|
airgen.generate_flights(
|
||||||
self.game.planners[cp.id],
|
self.current_mission.country(self.game.enemy_country),
|
||||||
self.groundobjectgen.runways
|
self.game.red_ato,
|
||||||
)
|
groundobjectgen.runways
|
||||||
|
)
|
||||||
|
|
||||||
# Generate ground units on frontline everywhere
|
# Generate ground units on frontline everywhere
|
||||||
jtacs: List[JtacInfo] = []
|
jtacs: List[JtacInfo] = []
|
||||||
@ -221,18 +244,20 @@ class Operation:
|
|||||||
self.current_mission.groundControl.red_tactical_commander = self.ca_slots
|
self.current_mission.groundControl.red_tactical_commander = self.ca_slots
|
||||||
|
|
||||||
# Triggers
|
# Triggers
|
||||||
if self.game.is_player_attack(self.conflict.attackers_country):
|
triggersgen = TriggersGenerator(self.current_mission, self.conflict,
|
||||||
cp = self.conflict.from_cp
|
self.game)
|
||||||
else:
|
triggersgen.generate()
|
||||||
cp = self.conflict.to_cp
|
|
||||||
self.triggersgen.generate()
|
|
||||||
|
|
||||||
# Options
|
# Options
|
||||||
self.forcedoptionsgen.generate()
|
forcedoptionsgen = ForcedOptionsGenerator(self.current_mission,
|
||||||
|
self.conflict, self.game)
|
||||||
|
forcedoptionsgen.generate()
|
||||||
|
|
||||||
# Generate Visuals Smoke Effects
|
# Generate Visuals Smoke Effects
|
||||||
|
visualgen = VisualGenerator(self.current_mission, self.conflict,
|
||||||
|
self.game)
|
||||||
if self.game.settings.perf_smoke_gen:
|
if self.game.settings.perf_smoke_gen:
|
||||||
self.visualgen.generate()
|
visualgen.generate()
|
||||||
|
|
||||||
# Inject Plugins Lua Scripts
|
# Inject Plugins Lua Scripts
|
||||||
listOfPluginsScripts = []
|
listOfPluginsScripts = []
|
||||||
@ -327,19 +352,20 @@ class Operation:
|
|||||||
trigger.add_action(DoScript(String(lua)))
|
trigger.add_action(DoScript(String(lua)))
|
||||||
self.current_mission.triggerrules.triggers.append(trigger)
|
self.current_mission.triggerrules.triggers.append(trigger)
|
||||||
|
|
||||||
self.assign_channels_to_flights()
|
self.assign_channels_to_flights(airgen.flights,
|
||||||
|
airsupportgen.air_support)
|
||||||
|
|
||||||
kneeboard_generator = KneeboardGenerator(self.current_mission)
|
kneeboard_generator = KneeboardGenerator(self.current_mission)
|
||||||
|
|
||||||
for dynamic_runway in self.groundobjectgen.runways.values():
|
for dynamic_runway in groundobjectgen.runways.values():
|
||||||
self.briefinggen.add_dynamic_runway(dynamic_runway)
|
self.briefinggen.add_dynamic_runway(dynamic_runway)
|
||||||
|
|
||||||
for tanker in self.airsupportgen.air_support.tankers:
|
for tanker in airsupportgen.air_support.tankers:
|
||||||
self.briefinggen.add_tanker(tanker)
|
self.briefinggen.add_tanker(tanker)
|
||||||
kneeboard_generator.add_tanker(tanker)
|
kneeboard_generator.add_tanker(tanker)
|
||||||
|
|
||||||
if self.is_awacs_enabled:
|
if self.is_awacs_enabled:
|
||||||
for awacs in self.airsupportgen.air_support.awacs:
|
for awacs in airsupportgen.air_support.awacs:
|
||||||
self.briefinggen.add_awacs(awacs)
|
self.briefinggen.add_awacs(awacs)
|
||||||
kneeboard_generator.add_awacs(awacs)
|
kneeboard_generator.add_awacs(awacs)
|
||||||
|
|
||||||
@ -347,21 +373,23 @@ class Operation:
|
|||||||
self.briefinggen.add_jtac(jtac)
|
self.briefinggen.add_jtac(jtac)
|
||||||
kneeboard_generator.add_jtac(jtac)
|
kneeboard_generator.add_jtac(jtac)
|
||||||
|
|
||||||
for flight in self.airgen.flights:
|
for flight in airgen.flights:
|
||||||
self.briefinggen.add_flight(flight)
|
self.briefinggen.add_flight(flight)
|
||||||
kneeboard_generator.add_flight(flight)
|
kneeboard_generator.add_flight(flight)
|
||||||
|
|
||||||
self.briefinggen.generate()
|
self.briefinggen.generate()
|
||||||
kneeboard_generator.generate()
|
kneeboard_generator.generate()
|
||||||
|
|
||||||
def assign_channels_to_flights(self) -> None:
|
def assign_channels_to_flights(self, flights: List[FlightData],
|
||||||
|
air_support: AirSupport) -> None:
|
||||||
"""Assigns preset radio channels for client flights."""
|
"""Assigns preset radio channels for client flights."""
|
||||||
for flight in self.airgen.flights:
|
for flight in flights:
|
||||||
if not flight.client_units:
|
if not flight.client_units:
|
||||||
continue
|
continue
|
||||||
self.assign_channels_to_flight(flight)
|
self.assign_channels_to_flight(flight, air_support)
|
||||||
|
|
||||||
def assign_channels_to_flight(self, flight: FlightData) -> None:
|
def assign_channels_to_flight(self, flight: FlightData,
|
||||||
|
air_support: AirSupport) -> None:
|
||||||
"""Assigns preset radio channels for a client flight."""
|
"""Assigns preset radio channels for a client flight."""
|
||||||
airframe = flight.aircraft_type
|
airframe = flight.aircraft_type
|
||||||
|
|
||||||
@ -371,5 +399,7 @@ class Operation:
|
|||||||
logging.warning(f"No aircraft data for {airframe.id}")
|
logging.warning(f"No aircraft data for {airframe.id}")
|
||||||
return
|
return
|
||||||
|
|
||||||
aircraft_data.channel_allocator.assign_channels_for_flight(
|
if aircraft_data.channel_allocator is not None:
|
||||||
flight, self.airsupportgen.air_support)
|
aircraft_data.channel_allocator.assign_channels_for_flight(
|
||||||
|
flight, air_support
|
||||||
|
)
|
||||||
|
|||||||
@ -2,8 +2,9 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
import shutil
|
import shutil
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
_dcs_saved_game_folder = None # type: str
|
_dcs_saved_game_folder: Optional[str] = None
|
||||||
_file_abs_path = None
|
_file_abs_path = None
|
||||||
|
|
||||||
def setup(user_folder: str):
|
def setup(user_folder: str):
|
||||||
@ -40,30 +41,33 @@ def restore_game():
|
|||||||
try:
|
try:
|
||||||
save = pickle.load(f)
|
save = pickle.load(f)
|
||||||
return save
|
return save
|
||||||
except:
|
except Exception:
|
||||||
logging.error("Invalid Save game")
|
logging.exception("Invalid Save game")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def load_game(path):
|
def load_game(path):
|
||||||
with open(path, "rb") as f:
|
with open(path, "rb") as f:
|
||||||
try:
|
try:
|
||||||
save = pickle.load(f)
|
save = pickle.load(f)
|
||||||
save.savepath = path
|
save.savepath = path
|
||||||
return save
|
return save
|
||||||
except:
|
except Exception:
|
||||||
logging.error("Invalid Save game")
|
logging.exception("Invalid Save game")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def save_game(game) -> bool:
|
def save_game(game) -> bool:
|
||||||
try:
|
try:
|
||||||
with open(_temporary_save_file(), "wb") as f:
|
with open(_temporary_save_file(), "wb") as f:
|
||||||
pickle.dump(game, f)
|
pickle.dump(game, f)
|
||||||
shutil.copy(_temporary_save_file(), game.savepath)
|
shutil.copy(_temporary_save_file(), game.savepath)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception:
|
||||||
logging.error(e)
|
logging.exception("Could not save game")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def autosave(game) -> bool:
|
def autosave(game) -> bool:
|
||||||
"""
|
"""
|
||||||
Autosave to the autosave location
|
Autosave to the autosave location
|
||||||
@ -74,7 +78,7 @@ def autosave(game) -> bool:
|
|||||||
with open(_autosave_path(), "wb") as f:
|
with open(_autosave_path(), "wb") as f:
|
||||||
pickle.dump(game, f)
|
pickle.dump(game, f)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception:
|
||||||
logging.error(e)
|
logging.exception("Could not save game")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -1,4 +1,3 @@
|
|||||||
from .aaa import *
|
|
||||||
from .aircraft import *
|
from .aircraft import *
|
||||||
from .armor import *
|
from .armor import *
|
||||||
from .airsupportgen import *
|
from .airsupportgen import *
|
||||||
@ -12,4 +11,3 @@ from .forcedoptionsgen import *
|
|||||||
from .kneeboard import *
|
from .kneeboard import *
|
||||||
|
|
||||||
from . import naming
|
from . import naming
|
||||||
|
|
||||||
|
|||||||
51
gen/aaa.py
51
gen/aaa.py
@ -1,51 +0,0 @@
|
|||||||
from .conflictgen import *
|
|
||||||
from .naming import *
|
|
||||||
|
|
||||||
from dcs.mission import *
|
|
||||||
from dcs.mission import *
|
|
||||||
|
|
||||||
from .conflictgen import *
|
|
||||||
from .naming import *
|
|
||||||
|
|
||||||
DISTANCE_FACTOR = 0.5, 1
|
|
||||||
EXTRA_AA_MIN_DISTANCE = 50000
|
|
||||||
EXTRA_AA_MAX_DISTANCE = 150000
|
|
||||||
EXTRA_AA_POSITION_FROM_CP = 550
|
|
||||||
|
|
||||||
class ExtraAAConflictGenerator:
|
|
||||||
def __init__(self, mission: Mission, conflict: Conflict, game, player_country: Country, enemy_country: Country):
|
|
||||||
self.mission = mission
|
|
||||||
self.game = game
|
|
||||||
self.conflict = conflict
|
|
||||||
self.player_country = player_country
|
|
||||||
self.enemy_country = enemy_country
|
|
||||||
|
|
||||||
def generate(self):
|
|
||||||
|
|
||||||
for cp in self.game.theater.controlpoints:
|
|
||||||
if cp.is_global:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if cp.position.distance_to_point(self.conflict.position) < EXTRA_AA_MIN_DISTANCE:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if cp.position.distance_to_point(self.conflict.from_cp.position) < EXTRA_AA_MIN_DISTANCE:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if cp.position.distance_to_point(self.conflict.to_cp.position) < EXTRA_AA_MIN_DISTANCE:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if cp.position.distance_to_point(self.conflict.position) > EXTRA_AA_MAX_DISTANCE:
|
|
||||||
continue
|
|
||||||
|
|
||||||
country_name = cp.captured and self.player_country or self.enemy_country
|
|
||||||
position = cp.position.point_from_heading(0, EXTRA_AA_POSITION_FROM_CP)
|
|
||||||
|
|
||||||
self.mission.vehicle_group(
|
|
||||||
country=self.mission.country(country_name),
|
|
||||||
name=namegen.next_basedefense_name(),
|
|
||||||
_type=db.EXTRA_AA[country_name],
|
|
||||||
position=position,
|
|
||||||
group_size=1
|
|
||||||
)
|
|
||||||
|
|
||||||
197
gen/aircraft.py
197
gen/aircraft.py
@ -1,30 +1,84 @@
|
|||||||
|
import logging
|
||||||
|
import random
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Type
|
from typing import Dict, List, Optional, Tuple, Type, Union
|
||||||
|
|
||||||
from dcs import helicopters
|
from dcs import helicopters
|
||||||
from dcs.action import ActivateGroup, AITaskPush, MessageToAll
|
from dcs.action import AITaskPush, ActivateGroup, MessageToAll
|
||||||
from dcs.condition import TimeAfter, CoalitionHasAirdrome, PartOfCoalitionInZone
|
from dcs.condition import CoalitionHasAirdrome, PartOfCoalitionInZone, TimeAfter
|
||||||
|
from dcs.country import Country
|
||||||
from dcs.flyingunit import FlyingUnit
|
from dcs.flyingunit import FlyingUnit
|
||||||
from dcs.helicopters import helicopter_map, UH_1H
|
from dcs.helicopters import UH_1H, helicopter_map
|
||||||
|
from dcs.mapping import Point
|
||||||
|
from dcs.mission import Mission, StartType
|
||||||
|
from dcs.planes import (
|
||||||
|
AJS37,
|
||||||
|
B_17G,
|
||||||
|
Bf_109K_4,
|
||||||
|
FW_190A8,
|
||||||
|
FW_190D9,
|
||||||
|
F_14B,
|
||||||
|
I_16,
|
||||||
|
JF_17,
|
||||||
|
Ju_88A4,
|
||||||
|
P_47D_30,
|
||||||
|
P_51D,
|
||||||
|
P_51D_30_NA,
|
||||||
|
SpitfireLFMkIX,
|
||||||
|
SpitfireLFMkIXCW,
|
||||||
|
Su_33,
|
||||||
|
)
|
||||||
|
from dcs.point import PointAction
|
||||||
|
from dcs.task import (
|
||||||
|
AntishipStrike,
|
||||||
|
AttackGroup,
|
||||||
|
Bombing,
|
||||||
|
CAP,
|
||||||
|
CAS,
|
||||||
|
ControlledTask,
|
||||||
|
EPLRS,
|
||||||
|
EngageTargets,
|
||||||
|
Escort,
|
||||||
|
GroundAttack,
|
||||||
|
MainTask,
|
||||||
|
NoTask,
|
||||||
|
OptROE,
|
||||||
|
OptRTBOnBingoFuel,
|
||||||
|
OptRTBOnOutOfAmmo,
|
||||||
|
OptReactOnThreat,
|
||||||
|
OptRestrictAfterburner,
|
||||||
|
OptRestrictJettison,
|
||||||
|
OrbitAction,
|
||||||
|
PinpointStrike,
|
||||||
|
SEAD,
|
||||||
|
StartCommand,
|
||||||
|
Targets,
|
||||||
|
Task,
|
||||||
|
)
|
||||||
from dcs.terrain.terrain import Airport, NoParkingSlotError
|
from dcs.terrain.terrain import Airport, NoParkingSlotError
|
||||||
from dcs.triggers import TriggerOnce, Event
|
from dcs.translation import String
|
||||||
|
from dcs.triggers import Event, TriggerOnce
|
||||||
|
from dcs.unitgroup import FlyingGroup, Group, ShipGroup, StaticGroup
|
||||||
|
from dcs.unittype import FlyingType, UnitType
|
||||||
|
|
||||||
|
from game import db
|
||||||
from game.data.cap_capabilities_db import GUNFIGHTERS
|
from game.data.cap_capabilities_db import GUNFIGHTERS
|
||||||
from game.settings import Settings
|
from game.settings import Settings
|
||||||
from game.utils import nm_to_meter
|
from game.utils import nm_to_meter
|
||||||
from gen.airfields import RunwayData
|
from gen.airfields import RunwayData
|
||||||
from gen.airsupportgen import AirSupport
|
from gen.airsupportgen import AirSupport
|
||||||
|
from gen.ato import AirTaskingOrder
|
||||||
from gen.callsigns import create_group_callsign_from_unit
|
from gen.callsigns import create_group_callsign_from_unit
|
||||||
from gen.flights.ai_flight_planner import FlightPlanner
|
|
||||||
from gen.flights.flight import (
|
from gen.flights.flight import (
|
||||||
Flight,
|
Flight,
|
||||||
FlightType,
|
FlightType,
|
||||||
FlightWaypoint,
|
FlightWaypoint,
|
||||||
FlightWaypointType,
|
FlightWaypointType,
|
||||||
)
|
)
|
||||||
from gen.radios import get_radio, MHz, Radio, RadioFrequency, RadioRegistry
|
from gen.radios import MHz, Radio, RadioFrequency, RadioRegistry, get_radio
|
||||||
from .conflictgen import *
|
from theater.controlpoint import ControlPoint, ControlPointType
|
||||||
from .naming import *
|
from .naming import namegen
|
||||||
|
from .conflictgen import Conflict
|
||||||
|
|
||||||
WARM_START_HELI_AIRSPEED = 120
|
WARM_START_HELI_AIRSPEED = 120
|
||||||
WARM_START_HELI_ALT = 500
|
WARM_START_HELI_ALT = 500
|
||||||
@ -264,8 +318,12 @@ class CommonRadioChannelAllocator(RadioChannelAllocator):
|
|||||||
|
|
||||||
def assign_channels_for_flight(self, flight: FlightData,
|
def assign_channels_for_flight(self, flight: FlightData,
|
||||||
air_support: AirSupport) -> None:
|
air_support: AirSupport) -> None:
|
||||||
flight.assign_channel(
|
if self.intra_flight_radio_index is not None:
|
||||||
self.intra_flight_radio_index, 1, flight.intra_flight_channel)
|
flight.assign_channel(
|
||||||
|
self.intra_flight_radio_index, 1, flight.intra_flight_channel)
|
||||||
|
|
||||||
|
if self.inter_flight_radio_index is None:
|
||||||
|
return
|
||||||
|
|
||||||
# For cases where the inter-flight and intra-flight radios share presets
|
# For cases where the inter-flight and intra-flight radios share presets
|
||||||
# (the JF-17 only has one set of channels, even though it can use two
|
# (the JF-17 only has one set of channels, even though it can use two
|
||||||
@ -335,8 +393,10 @@ class ViggenRadioChannelAllocator(RadioChannelAllocator):
|
|||||||
# the guard channel.
|
# the guard channel.
|
||||||
radio_id = 1
|
radio_id = 1
|
||||||
flight.assign_channel(radio_id, 1, flight.intra_flight_channel)
|
flight.assign_channel(radio_id, 1, flight.intra_flight_channel)
|
||||||
flight.assign_channel(radio_id, 4, flight.departure.atc)
|
if flight.departure.atc is not None:
|
||||||
flight.assign_channel(radio_id, 5, flight.arrival.atc)
|
flight.assign_channel(radio_id, 4, flight.departure.atc)
|
||||||
|
if flight.arrival.atc is not None:
|
||||||
|
flight.assign_channel(radio_id, 5, flight.arrival.atc)
|
||||||
# TODO: Assign divert to 6 when we support divert airfields.
|
# TODO: Assign divert to 6 when we support divert airfields.
|
||||||
|
|
||||||
|
|
||||||
@ -348,8 +408,10 @@ class SCR522RadioChannelAllocator(RadioChannelAllocator):
|
|||||||
air_support: AirSupport) -> None:
|
air_support: AirSupport) -> None:
|
||||||
radio_id = 1
|
radio_id = 1
|
||||||
flight.assign_channel(radio_id, 1, flight.intra_flight_channel)
|
flight.assign_channel(radio_id, 1, flight.intra_flight_channel)
|
||||||
flight.assign_channel(radio_id, 2, flight.departure.atc)
|
if flight.departure.atc is not None:
|
||||||
flight.assign_channel(radio_id, 3, flight.arrival.atc)
|
flight.assign_channel(radio_id, 2, flight.departure.atc)
|
||||||
|
if flight.arrival.atc is not None:
|
||||||
|
flight.assign_channel(radio_id, 3, flight.arrival.atc)
|
||||||
|
|
||||||
# TODO : Some GCI on Channel 4 ?
|
# TODO : Some GCI on Channel 4 ?
|
||||||
|
|
||||||
@ -471,8 +533,6 @@ AIRCRAFT_DATA["P-47D-30"] = AIRCRAFT_DATA["P-51D"]
|
|||||||
|
|
||||||
|
|
||||||
class AircraftConflictGenerator:
|
class AircraftConflictGenerator:
|
||||||
escort_targets = [] # type: typing.List[typing.Tuple[FlyingGroup, int]]
|
|
||||||
|
|
||||||
def __init__(self, mission: Mission, conflict: Conflict, settings: Settings,
|
def __init__(self, mission: Mission, conflict: Conflict, settings: Settings,
|
||||||
game, radio_registry: RadioRegistry):
|
game, radio_registry: RadioRegistry):
|
||||||
self.m = mission
|
self.m = mission
|
||||||
@ -480,7 +540,7 @@ class AircraftConflictGenerator:
|
|||||||
self.settings = settings
|
self.settings = settings
|
||||||
self.conflict = conflict
|
self.conflict = conflict
|
||||||
self.radio_registry = radio_registry
|
self.radio_registry = radio_registry
|
||||||
self.escort_targets = []
|
self.escort_targets: List[Tuple[FlyingGroup, int]] = []
|
||||||
self.flights: List[FlightData] = []
|
self.flights: List[FlightData] = []
|
||||||
|
|
||||||
def get_intra_flight_channel(self, airframe: UnitType) -> RadioFrequency:
|
def get_intra_flight_channel(self, airframe: UnitType) -> RadioFrequency:
|
||||||
@ -502,33 +562,23 @@ class AircraftConflictGenerator:
|
|||||||
def _start_type(self) -> StartType:
|
def _start_type(self) -> StartType:
|
||||||
return self.settings.cold_start and StartType.Cold or StartType.Warm
|
return self.settings.cold_start and StartType.Cold or StartType.Warm
|
||||||
|
|
||||||
def _setup_group(self, group: FlyingGroup, for_task: typing.Type[Task],
|
def _setup_group(self, group: FlyingGroup, for_task: Type[Task],
|
||||||
flight: Flight, dynamic_runways: Dict[str, RunwayData]):
|
flight: Flight, dynamic_runways: Dict[str, RunwayData]):
|
||||||
did_load_loadout = False
|
did_load_loadout = False
|
||||||
unit_type = group.units[0].unit_type
|
unit_type = group.units[0].unit_type
|
||||||
|
|
||||||
if unit_type in db.PLANE_PAYLOAD_OVERRIDES:
|
if unit_type in db.PLANE_PAYLOAD_OVERRIDES:
|
||||||
override_loadout = db.PLANE_PAYLOAD_OVERRIDES[unit_type]
|
override_loadout = db.PLANE_PAYLOAD_OVERRIDES[unit_type]
|
||||||
if type(override_loadout) == dict:
|
# Clear pylons
|
||||||
|
for p in group.units:
|
||||||
|
p.pylons.clear()
|
||||||
|
|
||||||
# Clear pylons
|
# Now load loadout
|
||||||
for p in group.units:
|
if for_task in db.PLANE_PAYLOAD_OVERRIDES[unit_type]:
|
||||||
p.pylons.clear()
|
payload_name = db.PLANE_PAYLOAD_OVERRIDES[unit_type][for_task]
|
||||||
|
group.load_loadout(payload_name)
|
||||||
# Now load loadout
|
|
||||||
if for_task in db.PLANE_PAYLOAD_OVERRIDES[unit_type]:
|
|
||||||
payload_name = db.PLANE_PAYLOAD_OVERRIDES[unit_type][for_task]
|
|
||||||
group.load_loadout(payload_name)
|
|
||||||
did_load_loadout = True
|
|
||||||
logging.info("Loaded overridden payload for {} - {} for task {}".format(unit_type, payload_name, for_task))
|
|
||||||
elif "*" in db.PLANE_PAYLOAD_OVERRIDES[unit_type]:
|
|
||||||
payload_name = db.PLANE_PAYLOAD_OVERRIDES[unit_type]["*"]
|
|
||||||
group.load_loadout(payload_name)
|
|
||||||
did_load_loadout = True
|
|
||||||
logging.info("Loaded overridden payload for {} - {} for task {}".format(unit_type, payload_name, for_task))
|
|
||||||
elif issubclass(override_loadout, MainTask):
|
|
||||||
group.load_task_default_loadout(override_loadout)
|
|
||||||
did_load_loadout = True
|
did_load_loadout = True
|
||||||
|
logging.info("Loaded overridden payload for {} - {} for task {}".format(unit_type, payload_name, for_task))
|
||||||
|
|
||||||
if not did_load_loadout:
|
if not did_load_loadout:
|
||||||
group.load_task_default_loadout(for_task)
|
group.load_task_default_loadout(for_task)
|
||||||
@ -590,7 +640,7 @@ class AircraftConflictGenerator:
|
|||||||
|
|
||||||
# Special case so Su 33 carrier take off
|
# Special case so Su 33 carrier take off
|
||||||
if unit_type is Su_33:
|
if unit_type is Su_33:
|
||||||
if task is not CAP:
|
if flight.flight_type is not CAP:
|
||||||
for unit in group.units:
|
for unit in group.units:
|
||||||
unit.fuel = Su_33.fuel_max / 2.2
|
unit.fuel = Su_33.fuel_max / 2.2
|
||||||
else:
|
else:
|
||||||
@ -613,9 +663,12 @@ class AircraftConflictGenerator:
|
|||||||
# so just use the first runway.
|
# so just use the first runway.
|
||||||
return runways[0]
|
return runways[0]
|
||||||
|
|
||||||
def _generate_at_airport(self, name: str, side: Country, unit_type: FlyingType, count: int, client_count: int, airport: Airport = None, start_type = None) -> FlyingGroup:
|
def _generate_at_airport(self, name: str, side: Country,
|
||||||
|
unit_type: FlyingType, count: int,
|
||||||
|
client_count: int,
|
||||||
|
airport: Optional[Airport] = None,
|
||||||
|
start_type=None) -> FlyingGroup:
|
||||||
assert count > 0
|
assert count > 0
|
||||||
assert unit is not None
|
|
||||||
|
|
||||||
if start_type is None:
|
if start_type is None:
|
||||||
start_type = self._start_type()
|
start_type = self._start_type()
|
||||||
@ -633,7 +686,6 @@ class AircraftConflictGenerator:
|
|||||||
|
|
||||||
def _generate_inflight(self, name: str, side: Country, unit_type: FlyingType, count: int, client_count: int, at: Point) -> FlyingGroup:
|
def _generate_inflight(self, name: str, side: Country, unit_type: FlyingType, count: int, client_count: int, at: Point) -> FlyingGroup:
|
||||||
assert count > 0
|
assert count > 0
|
||||||
assert unit is not None
|
|
||||||
|
|
||||||
if unit_type in helicopters.helicopter_map.values():
|
if unit_type in helicopters.helicopter_map.values():
|
||||||
alt = WARM_START_HELI_ALT
|
alt = WARM_START_HELI_ALT
|
||||||
@ -660,9 +712,11 @@ class AircraftConflictGenerator:
|
|||||||
group.points[0].alt_type = "RADIO"
|
group.points[0].alt_type = "RADIO"
|
||||||
return group
|
return group
|
||||||
|
|
||||||
def _generate_at_group(self, name: str, side: Country, unit_type: FlyingType, count: int, client_count: int, at: typing.Union[ShipGroup, StaticGroup], start_type=None) -> FlyingGroup:
|
def _generate_at_group(self, name: str, side: Country,
|
||||||
|
unit_type: FlyingType, count: int, client_count: int,
|
||||||
|
at: Union[ShipGroup, StaticGroup],
|
||||||
|
start_type=None) -> FlyingGroup:
|
||||||
assert count > 0
|
assert count > 0
|
||||||
assert unit is not None
|
|
||||||
|
|
||||||
if start_type is None:
|
if start_type is None:
|
||||||
start_type = self._start_type()
|
start_type = self._start_type()
|
||||||
@ -688,7 +742,7 @@ class AircraftConflictGenerator:
|
|||||||
return self._generate_at_group(name, side, unit_type, count, client_count, at)
|
return self._generate_at_group(name, side, unit_type, count, client_count, at)
|
||||||
else:
|
else:
|
||||||
return self._generate_inflight(name, side, unit_type, count, client_count, at.position)
|
return self._generate_inflight(name, side, unit_type, count, client_count, at.position)
|
||||||
elif issubclass(at, Airport):
|
elif isinstance(at, Airport):
|
||||||
takeoff_ban = unit_type in db.TAKEOFF_BAN
|
takeoff_ban = unit_type in db.TAKEOFF_BAN
|
||||||
ai_ban = client_count == 0 and self.settings.only_player_takeoff
|
ai_ban = client_count == 0 and self.settings.only_player_takeoff
|
||||||
|
|
||||||
@ -707,8 +761,9 @@ class AircraftConflictGenerator:
|
|||||||
point.alt_type = "RADIO"
|
point.alt_type = "RADIO"
|
||||||
return point
|
return point
|
||||||
|
|
||||||
def _rtb_for(self, group: FlyingGroup, cp: ControlPoint, at: db.StartingPosition = None):
|
def _rtb_for(self, group: FlyingGroup, cp: ControlPoint,
|
||||||
if not at:
|
at: Optional[db.StartingPosition] = None):
|
||||||
|
if at is None:
|
||||||
at = cp.at
|
at = cp.at
|
||||||
position = at if isinstance(at, Point) else at.position
|
position = at if isinstance(at, Point) else at.position
|
||||||
|
|
||||||
@ -751,31 +806,28 @@ class AircraftConflictGenerator:
|
|||||||
else:
|
else:
|
||||||
logging.warning("Pylon not found ! => Pylon" + key + " on " + str(flight.unit_type))
|
logging.warning("Pylon not found ! => Pylon" + key + " on " + str(flight.unit_type))
|
||||||
|
|
||||||
|
def clear_parking_slots(self) -> None:
|
||||||
def generate_flights(self, cp, country, flight_planner: FlightPlanner,
|
for cp in self.game.theater.controlpoints:
|
||||||
dynamic_runways: Dict[str, RunwayData]):
|
|
||||||
# Clear pydcs parking slots
|
|
||||||
if cp.airport is not None:
|
|
||||||
logging.info("CLEARING SLOTS @ " + cp.airport.name)
|
|
||||||
logging.info("===============")
|
|
||||||
if cp.airport is not None:
|
if cp.airport is not None:
|
||||||
for ps in cp.airport.parking_slots:
|
for parking_slot in cp.airport.parking_slots:
|
||||||
logging.info("SLOT : " + str(ps.unit_id))
|
parking_slot.unit_id = None
|
||||||
ps.unit_id = None
|
|
||||||
logging.info("----------------")
|
|
||||||
logging.info("===============")
|
|
||||||
|
|
||||||
for flight in flight_planner.flights:
|
def generate_flights(self, country, ato: AirTaskingOrder,
|
||||||
|
dynamic_runways: Dict[str, RunwayData]) -> None:
|
||||||
if flight.client_count == 0 and self.game.position_culled(flight.from_cp.position):
|
self.clear_parking_slots()
|
||||||
logging.info("Flight not generated : culled")
|
|
||||||
continue
|
|
||||||
logging.info("Generating flight : " + str(flight.unit_type))
|
|
||||||
group = self.generate_planned_flight(cp, country, flight)
|
|
||||||
self.setup_flight_group(group, flight, flight.flight_type,
|
|
||||||
dynamic_runways)
|
|
||||||
self.setup_group_activation_trigger(flight, group)
|
|
||||||
|
|
||||||
|
for package in ato.packages:
|
||||||
|
for flight in package.flights:
|
||||||
|
culled = self.game.position_culled(flight.from_cp.position)
|
||||||
|
if flight.client_count == 0 and culled:
|
||||||
|
logging.info("Flight not generated: culled")
|
||||||
|
continue
|
||||||
|
logging.info(f"Generating flight: {flight.unit_type}")
|
||||||
|
group = self.generate_planned_flight(flight.from_cp, country,
|
||||||
|
flight)
|
||||||
|
self.setup_flight_group(group, flight, flight.flight_type,
|
||||||
|
dynamic_runways)
|
||||||
|
self.setup_group_activation_trigger(flight, group)
|
||||||
|
|
||||||
def setup_group_activation_trigger(self, flight, group):
|
def setup_group_activation_trigger(self, flight, group):
|
||||||
if flight.scheduled_in > 0 and flight.client_count == 0:
|
if flight.scheduled_in > 0 and flight.client_count == 0:
|
||||||
@ -932,6 +984,14 @@ class AircraftConflictGenerator:
|
|||||||
group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
|
group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
|
||||||
group.points[0].tasks.append(OptROE(OptROE.Values.OpenFire))
|
group.points[0].tasks.append(OptROE(OptROE.Values.OpenFire))
|
||||||
group.points[0].tasks.append(OptRestrictJettison(True))
|
group.points[0].tasks.append(OptRestrictJettison(True))
|
||||||
|
elif flight_type == FlightType.ESCORT:
|
||||||
|
group.task = Escort.name
|
||||||
|
self._setup_group(group, Escort, flight, dynamic_runways)
|
||||||
|
# TODO: Cleanup duplication...
|
||||||
|
group.points[0].tasks.clear()
|
||||||
|
group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
|
||||||
|
group.points[0].tasks.append(OptROE(OptROE.Values.OpenFire))
|
||||||
|
group.points[0].tasks.append(OptRestrictJettison(True))
|
||||||
|
|
||||||
group.points[0].tasks.append(OptRTBOnBingoFuel(True))
|
group.points[0].tasks.append(OptRTBOnBingoFuel(True))
|
||||||
group.points[0].tasks.append(OptRestrictAfterburner(True))
|
group.points[0].tasks.append(OptRestrictAfterburner(True))
|
||||||
@ -952,6 +1012,7 @@ class AircraftConflictGenerator:
|
|||||||
# pt.tasks.append(engagetgt)
|
# pt.tasks.append(engagetgt)
|
||||||
elif point.waypoint_type == FlightWaypointType.LANDING_POINT:
|
elif point.waypoint_type == FlightWaypointType.LANDING_POINT:
|
||||||
pt.type = "Land"
|
pt.type = "Land"
|
||||||
|
pt.action = PointAction.Landing
|
||||||
elif point.waypoint_type == FlightWaypointType.INGRESS_STRIKE:
|
elif point.waypoint_type == FlightWaypointType.INGRESS_STRIKE:
|
||||||
|
|
||||||
if group.units[0].unit_type == B_17G:
|
if group.units[0].unit_type == B_17G:
|
||||||
|
|||||||
@ -195,10 +195,12 @@ AIRFIELD_DATA = {
|
|||||||
runway_length=8623,
|
runway_length=8623,
|
||||||
atc=AtcData(MHz(3, 750), MHz(121, 0), MHz(38, 400), MHz(250, 0)),
|
atc=AtcData(MHz(3, 750), MHz(121, 0), MHz(38, 400), MHz(250, 0)),
|
||||||
outer_ndb={
|
outer_ndb={
|
||||||
"22": ("AP", MHz(443, 0)), "4": "443.00 (AN)"
|
"22": ("AP", MHz(443, 0)),
|
||||||
|
"04": ("AN", MHz(443)),
|
||||||
},
|
},
|
||||||
inner_ndb={
|
inner_ndb={
|
||||||
"22": ("P", MHz(215, 0)), "4": "215.00 (N)"
|
"22": ("P", MHz(215, 0)),
|
||||||
|
"04": ("N", MHz(215)),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,21 @@
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
from typing import List, Type
|
||||||
|
|
||||||
|
from dcs.mission import Mission, StartType
|
||||||
|
from dcs.planes import IL_78M
|
||||||
|
from dcs.task import (
|
||||||
|
AWACS,
|
||||||
|
ActivateBeaconCommand,
|
||||||
|
MainTask,
|
||||||
|
Refueling,
|
||||||
|
SetImmortalCommand,
|
||||||
|
SetInvisibleCommand,
|
||||||
|
)
|
||||||
|
|
||||||
|
from game import db
|
||||||
|
from .naming import namegen
|
||||||
from .callsigns import callsign_for_support_unit
|
from .callsigns import callsign_for_support_unit
|
||||||
from .conflictgen import *
|
from .conflictgen import Conflict
|
||||||
from .naming import *
|
|
||||||
from .radios import RadioFrequency, RadioRegistry
|
from .radios import RadioFrequency, RadioRegistry
|
||||||
from .tacan import TacanBand, TacanChannel, TacanRegistry
|
from .tacan import TacanBand, TacanChannel, TacanRegistry
|
||||||
|
|
||||||
@ -49,7 +62,7 @@ class AirSupportConflictGenerator:
|
|||||||
self.tacan_registry = tacan_registry
|
self.tacan_registry = tacan_registry
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def support_tasks(cls) -> typing.Collection[typing.Type[MainTask]]:
|
def support_tasks(cls) -> List[Type[MainTask]]:
|
||||||
return [Refueling, AWACS]
|
return [Refueling, AWACS]
|
||||||
|
|
||||||
def generate(self, is_awacs_enabled):
|
def generate(self, is_awacs_enabled):
|
||||||
@ -76,6 +89,7 @@ class AirSupportConflictGenerator:
|
|||||||
speed=574,
|
speed=574,
|
||||||
tacanchannel=str(tacan),
|
tacanchannel=str(tacan),
|
||||||
)
|
)
|
||||||
|
tanker_group.set_frequency(freq.mhz)
|
||||||
|
|
||||||
callsign = callsign_for_support_unit(tanker_group)
|
callsign = callsign_for_support_unit(tanker_group)
|
||||||
tacan_callsign = {
|
tacan_callsign = {
|
||||||
@ -118,6 +132,8 @@ class AirSupportConflictGenerator:
|
|||||||
frequency=freq.mhz,
|
frequency=freq.mhz,
|
||||||
start_type=StartType.Warm,
|
start_type=StartType.Warm,
|
||||||
)
|
)
|
||||||
|
awacs_flight.set_frequency(freq.mhz)
|
||||||
|
|
||||||
awacs_flight.points[0].tasks.append(SetInvisibleCommand(True))
|
awacs_flight.points[0].tasks.append(SetInvisibleCommand(True))
|
||||||
awacs_flight.points[0].tasks.append(SetImmortalCommand(True))
|
awacs_flight.points[0].tasks.append(SetImmortalCommand(True))
|
||||||
|
|
||||||
|
|||||||
45
gen/armor.py
45
gen/armor.py
@ -1,13 +1,39 @@
|
|||||||
|
import logging
|
||||||
|
import random
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from dcs import Mission
|
||||||
from dcs.action import AITaskPush
|
from dcs.action import AITaskPush
|
||||||
from dcs.condition import TimeAfter, UnitDamaged, Or, GroupLifeLess
|
from dcs.condition import GroupLifeLess, Or, TimeAfter, UnitDamaged
|
||||||
from dcs.triggers import TriggerOnce, Event
|
from dcs.country import Country
|
||||||
|
from dcs.mapping import Point
|
||||||
|
from dcs.planes import MQ_9_Reaper
|
||||||
|
from dcs.point import PointAction
|
||||||
|
from dcs.task import (
|
||||||
|
AttackGroup,
|
||||||
|
ControlledTask,
|
||||||
|
EPLRS,
|
||||||
|
FireAtPoint,
|
||||||
|
GoToWaypoint,
|
||||||
|
Hold,
|
||||||
|
OrbitAction,
|
||||||
|
SetImmortalCommand,
|
||||||
|
SetInvisibleCommand,
|
||||||
|
)
|
||||||
|
from dcs.triggers import Event, TriggerOnce
|
||||||
|
from dcs.unit import Vehicle
|
||||||
|
from dcs.unittype import VehicleType
|
||||||
|
|
||||||
from gen import namegen
|
from game import db
|
||||||
from gen.ground_forces.ai_ground_planner import CombatGroupRole, DISTANCE_FROM_FRONTLINE
|
from .naming import namegen
|
||||||
|
from gen.ground_forces.ai_ground_planner import (
|
||||||
|
CombatGroupRole,
|
||||||
|
DISTANCE_FROM_FRONTLINE,
|
||||||
|
)
|
||||||
from .callsigns import callsign_for_support_unit
|
from .callsigns import callsign_for_support_unit
|
||||||
from .conflictgen import *
|
from .conflictgen import Conflict
|
||||||
|
from .ground_forces.combat_stance import CombatStance
|
||||||
|
|
||||||
SPREAD_DISTANCE_FACTOR = 0.1, 0.3
|
SPREAD_DISTANCE_FACTOR = 0.1, 0.3
|
||||||
SPREAD_DISTANCE_SIZE_FACTOR = 0.1
|
SPREAD_DISTANCE_SIZE_FACTOR = 0.1
|
||||||
@ -48,7 +74,7 @@ class GroundConflictGenerator:
|
|||||||
self.jtacs: List[JtacInfo] = []
|
self.jtacs: List[JtacInfo] = []
|
||||||
|
|
||||||
def _group_point(self, point) -> Point:
|
def _group_point(self, point) -> Point:
|
||||||
distance = randint(
|
distance = random.randint(
|
||||||
int(self.conflict.size * SPREAD_DISTANCE_FACTOR[0]),
|
int(self.conflict.size * SPREAD_DISTANCE_FACTOR[0]),
|
||||||
int(self.conflict.size * SPREAD_DISTANCE_FACTOR[1]),
|
int(self.conflict.size * SPREAD_DISTANCE_FACTOR[1]),
|
||||||
)
|
)
|
||||||
@ -165,7 +191,7 @@ class GroundConflictGenerator:
|
|||||||
heading=forward_heading,
|
heading=forward_heading,
|
||||||
move_formation=PointAction.OffRoad)
|
move_formation=PointAction.OffRoad)
|
||||||
|
|
||||||
for i in range(randint(3, 10)):
|
for i in range(random.randint(3, 10)):
|
||||||
u = random.choice(possible_infantry_units)
|
u = random.choice(possible_infantry_units)
|
||||||
position = infantry_position.random_point_within(55, 5)
|
position = infantry_position.random_point_within(55, 5)
|
||||||
self.mission.vehicle_group(
|
self.mission.vehicle_group(
|
||||||
@ -183,6 +209,11 @@ class GroundConflictGenerator:
|
|||||||
return
|
return
|
||||||
|
|
||||||
for dcs_group, group in ally_groups:
|
for dcs_group, group in ally_groups:
|
||||||
|
|
||||||
|
if hasattr(group.units[0], 'eplrs'):
|
||||||
|
if group.units[0].eplrs:
|
||||||
|
dcs_group.points[0].tasks.append(EPLRS(dcs_group.id))
|
||||||
|
|
||||||
if group.role == CombatGroupRole.ARTILLERY:
|
if group.role == CombatGroupRole.ARTILLERY:
|
||||||
# Fire on any ennemy in range
|
# Fire on any ennemy in range
|
||||||
if self.game.settings.perf_artillery:
|
if self.game.settings.perf_artillery:
|
||||||
|
|||||||
136
gen/ato.py
Normal file
136
gen/ato.py
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
"""Air Tasking Orders.
|
||||||
|
|
||||||
|
The classes of the Air Tasking Order (ATO) define all of the missions that have
|
||||||
|
been planned, and which aircraft have been assigned to them. Each planned
|
||||||
|
mission, or "package" is composed of individual flights. The package may contain
|
||||||
|
dissimilar aircraft performing different roles, but all for the same goal. For
|
||||||
|
example, the package to strike an enemy airfield may contain an escort flight,
|
||||||
|
a SEAD flight, and the strike aircraft themselves. CAP packages may contain only
|
||||||
|
the single CAP flight.
|
||||||
|
"""
|
||||||
|
from collections import defaultdict
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
import logging
|
||||||
|
from typing import Dict, Iterator, List, Optional
|
||||||
|
|
||||||
|
from dcs.mapping import Point
|
||||||
|
from .flights.flight import Flight, FlightType
|
||||||
|
from theater.missiontarget import MissionTarget
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Task:
|
||||||
|
"""The main task of a flight or package."""
|
||||||
|
|
||||||
|
#: The type of task.
|
||||||
|
task_type: FlightType
|
||||||
|
|
||||||
|
#: The location of the objective.
|
||||||
|
location: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Package:
|
||||||
|
"""A mission package."""
|
||||||
|
|
||||||
|
#: The mission target. Currently can be either a ControlPoint or a
|
||||||
|
#: TheaterGroundObject (non-ControlPoint map objectives).
|
||||||
|
target: MissionTarget
|
||||||
|
|
||||||
|
#: The set of flights in the package.
|
||||||
|
flights: List[Flight] = field(default_factory=list)
|
||||||
|
|
||||||
|
delay: int = field(default=0)
|
||||||
|
|
||||||
|
join_point: Optional[Point] = field(default=None, init=False, hash=False)
|
||||||
|
split_point: Optional[Point] = field(default=None, init=False, hash=False)
|
||||||
|
ingress_point: Optional[Point] = field(default=None, init=False, hash=False)
|
||||||
|
egress_point: Optional[Point] = field(default=None, init=False, hash=False)
|
||||||
|
|
||||||
|
def add_flight(self, flight: Flight) -> None:
|
||||||
|
"""Adds a flight to the package."""
|
||||||
|
self.flights.append(flight)
|
||||||
|
|
||||||
|
def remove_flight(self, flight: Flight) -> None:
|
||||||
|
"""Removes a flight from the package."""
|
||||||
|
self.flights.remove(flight)
|
||||||
|
if not self.flights:
|
||||||
|
self.ingress_point = None
|
||||||
|
self.egress_point = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def primary_task(self) -> Optional[FlightType]:
|
||||||
|
if not self.flights:
|
||||||
|
return None
|
||||||
|
|
||||||
|
flight_counts: Dict[FlightType, int] = defaultdict(lambda: 0)
|
||||||
|
for flight in self.flights:
|
||||||
|
flight_counts[flight.flight_type] += 1
|
||||||
|
|
||||||
|
# The package will contain a mix of mission types, but in general we can
|
||||||
|
# determine the goal of the mission because some mission types are more
|
||||||
|
# likely to be the main task than others. For example, a package with
|
||||||
|
# only CAP flights is a CAP package, a flight with CAP and strike is a
|
||||||
|
# strike package, a flight with CAP and DEAD is a DEAD package, and a
|
||||||
|
# flight with strike and SEAD is an OCA/Strike package. The type of
|
||||||
|
# package is determined by the highest priority flight in the package.
|
||||||
|
task_priorities = [
|
||||||
|
FlightType.CAS,
|
||||||
|
FlightType.STRIKE,
|
||||||
|
FlightType.ANTISHIP,
|
||||||
|
FlightType.BAI,
|
||||||
|
FlightType.EVAC,
|
||||||
|
FlightType.TROOP_TRANSPORT,
|
||||||
|
FlightType.RECON,
|
||||||
|
FlightType.ELINT,
|
||||||
|
FlightType.DEAD,
|
||||||
|
FlightType.SEAD,
|
||||||
|
FlightType.LOGISTICS,
|
||||||
|
FlightType.INTERCEPTION,
|
||||||
|
FlightType.TARCAP,
|
||||||
|
FlightType.CAP,
|
||||||
|
FlightType.BARCAP,
|
||||||
|
FlightType.EWAR,
|
||||||
|
FlightType.ESCORT,
|
||||||
|
]
|
||||||
|
for task in task_priorities:
|
||||||
|
if flight_counts[task]:
|
||||||
|
return task
|
||||||
|
|
||||||
|
# If we get here, our task_priorities list above is incomplete. Log the
|
||||||
|
# issue and return the type of *any* flight in the package.
|
||||||
|
some_mission = next(iter(self.flights)).flight_type
|
||||||
|
logging.warning(f"Unhandled mission type: {some_mission}")
|
||||||
|
return some_mission
|
||||||
|
|
||||||
|
@property
|
||||||
|
def package_description(self) -> str:
|
||||||
|
"""Generates a package description based on flight composition."""
|
||||||
|
task = self.primary_task
|
||||||
|
if task is None:
|
||||||
|
return "No mission"
|
||||||
|
return task.name
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
# TODO: Far from perfect. Number packages?
|
||||||
|
return hash(self.target.name)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AirTaskingOrder:
|
||||||
|
"""The entire ATO for one coalition."""
|
||||||
|
|
||||||
|
#: The set of all planned packages in the ATO.
|
||||||
|
packages: List[Package] = field(default_factory=list)
|
||||||
|
|
||||||
|
def add_package(self, package: Package) -> None:
|
||||||
|
"""Adds a package to the ATO."""
|
||||||
|
self.packages.append(package)
|
||||||
|
|
||||||
|
def remove_package(self, package: Package) -> None:
|
||||||
|
"""Removes a package from the ATO."""
|
||||||
|
self.packages.remove(package)
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
"""Removes all packages from the ATO."""
|
||||||
|
self.packages.clear()
|
||||||
@ -106,7 +106,7 @@ class BriefingGenerator(MissionInfoGenerator):
|
|||||||
aircraft = flight.aircraft_type
|
aircraft = flight.aircraft_type
|
||||||
flight_unit_name = db.unit_type_name(aircraft)
|
flight_unit_name = db.unit_type_name(aircraft)
|
||||||
self.description += "-" * 50 + "\n"
|
self.description += "-" * 50 + "\n"
|
||||||
self.description += f"{flight_unit_name} x {flight.size + 2}\n\n"
|
self.description += f"{flight_unit_name} x {flight.size}\n\n"
|
||||||
|
|
||||||
for i, wpt in enumerate(flight.waypoints):
|
for i, wpt in enumerate(flight.waypoints):
|
||||||
self.description += f"#{i + 1} -- {wpt.name} : {wpt.description}\n"
|
self.description += f"#{i + 1} -- {wpt.name} : {wpt.description}\n"
|
||||||
|
|||||||
@ -1,21 +1,11 @@
|
|||||||
import logging
|
import logging
|
||||||
import typing
|
import random
|
||||||
import pdb
|
from typing import Tuple
|
||||||
import dcs
|
|
||||||
|
|
||||||
from random import randint
|
from dcs.country import Country
|
||||||
from dcs import Mission
|
from dcs.mapping import Point
|
||||||
|
|
||||||
from dcs.mission import *
|
from theater import ConflictTheater, ControlPoint
|
||||||
from dcs.vehicles import *
|
|
||||||
from dcs.unitgroup import *
|
|
||||||
from dcs.unittype import *
|
|
||||||
from dcs.mapping import *
|
|
||||||
from dcs.point import *
|
|
||||||
from dcs.task import *
|
|
||||||
from dcs.country import *
|
|
||||||
|
|
||||||
from theater import *
|
|
||||||
|
|
||||||
AIR_DISTANCE = 40000
|
AIR_DISTANCE = 40000
|
||||||
|
|
||||||
@ -65,24 +55,6 @@ def _heading_sum(h, a) -> int:
|
|||||||
|
|
||||||
|
|
||||||
class Conflict:
|
class Conflict:
|
||||||
attackers_side = None # type: str
|
|
||||||
defenders_side = None # type: str
|
|
||||||
attackers_country = None # type: Country
|
|
||||||
defenders_country = None # type: Country
|
|
||||||
from_cp = None # type: ControlPoint
|
|
||||||
to_cp = None # type: ControlPoint
|
|
||||||
position = None # type: Point
|
|
||||||
size = None # type: int
|
|
||||||
radials = None # type: typing.List[int]
|
|
||||||
|
|
||||||
heading = None # type: int
|
|
||||||
distance = None # type: int
|
|
||||||
|
|
||||||
ground_attackers_location = None # type: Point
|
|
||||||
ground_defenders_location = None # type: Point
|
|
||||||
air_attackers_location = None # type: Point
|
|
||||||
air_defenders_location = None # type: Point
|
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
theater: ConflictTheater,
|
theater: ConflictTheater,
|
||||||
from_cp: ControlPoint,
|
from_cp: ControlPoint,
|
||||||
@ -155,7 +127,7 @@ class Conflict:
|
|||||||
else:
|
else:
|
||||||
return self.position
|
return self.position
|
||||||
|
|
||||||
def find_ground_position(self, at: Point, heading: int, max_distance: int = 40000) -> typing.Optional[Point]:
|
def find_ground_position(self, at: Point, heading: int, max_distance: int = 40000) -> Point:
|
||||||
return Conflict._find_ground_position(at, max_distance, heading, self.theater)
|
return Conflict._find_ground_position(at, max_distance, heading, self.theater)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -163,7 +135,7 @@ class Conflict:
|
|||||||
return from_cp.has_frontline and to_cp.has_frontline
|
return from_cp.has_frontline and to_cp.has_frontline
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def frontline_position(cls, theater: ConflictTheater, from_cp: ControlPoint, to_cp: ControlPoint) -> typing.Optional[typing.Tuple[Point, int]]:
|
def frontline_position(cls, theater: ConflictTheater, from_cp: ControlPoint, to_cp: ControlPoint) -> Tuple[Point, int]:
|
||||||
attack_heading = from_cp.position.heading_between_point(to_cp.position)
|
attack_heading = from_cp.position.heading_between_point(to_cp.position)
|
||||||
attack_distance = from_cp.position.distance_to_point(to_cp.position)
|
attack_distance = from_cp.position.distance_to_point(to_cp.position)
|
||||||
middle_point = from_cp.position.point_from_heading(attack_heading, attack_distance / 2)
|
middle_point = from_cp.position.point_from_heading(attack_heading, attack_distance / 2)
|
||||||
@ -174,9 +146,7 @@ class Conflict:
|
|||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def frontline_vector(cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater) -> typing.Optional[typing.Tuple[Point, int, int]]:
|
def frontline_vector(cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater) -> Tuple[Point, int, int]:
|
||||||
initial, heading = cls.frontline_position(theater, from_cp, to_cp)
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
probe_end_point = initial.point_from_heading(heading, FRONTLINE_LENGTH)
|
probe_end_point = initial.point_from_heading(heading, FRONTLINE_LENGTH)
|
||||||
probe = geometry.LineString([(initial.x, initial.y), (probe_end_point.x, probe_end_point.y) ])
|
probe = geometry.LineString([(initial.x, initial.y), (probe_end_point.x, probe_end_point.y) ])
|
||||||
@ -193,9 +163,6 @@ class Conflict:
|
|||||||
return Point(*intersection.xy[0]), _heading_sum(heading, 90), intersection.length
|
return Point(*intersection.xy[0]), _heading_sum(heading, 90), intersection.length
|
||||||
"""
|
"""
|
||||||
frontline = cls.frontline_position(theater, from_cp, to_cp)
|
frontline = cls.frontline_position(theater, from_cp, to_cp)
|
||||||
if not frontline:
|
|
||||||
return None
|
|
||||||
|
|
||||||
center_position, heading = frontline
|
center_position, heading = frontline
|
||||||
left_position, right_position = None, None
|
left_position, right_position = None, None
|
||||||
|
|
||||||
@ -243,7 +210,7 @@ class Conflict:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _find_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> typing.Optional[Point]:
|
def _find_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point:
|
||||||
pos = initial
|
pos = initial
|
||||||
for _ in range(0, int(max_distance), 500):
|
for _ in range(0, int(max_distance), 500):
|
||||||
if theater.is_on_land(pos):
|
if theater.is_on_land(pos):
|
||||||
@ -302,10 +269,14 @@ class Conflict:
|
|||||||
|
|
||||||
distance = to_cp.size * GROUND_DISTANCE_FACTOR
|
distance = to_cp.size * GROUND_DISTANCE_FACTOR
|
||||||
attackers_location = position.point_from_heading(attack_heading, distance)
|
attackers_location = position.point_from_heading(attack_heading, distance)
|
||||||
attackers_location = Conflict._find_ground_position(attackers_location, distance * 2, _heading_sum(attack_heading, 180), theater)
|
attackers_location = Conflict._find_ground_position(
|
||||||
|
attackers_location, int(distance * 2),
|
||||||
|
_heading_sum(attack_heading, 180), theater)
|
||||||
|
|
||||||
defenders_location = position.point_from_heading(defense_heading, distance)
|
defenders_location = position.point_from_heading(defense_heading, distance)
|
||||||
defenders_location = Conflict._find_ground_position(defenders_location, distance * 2, _heading_sum(defense_heading, 180), theater)
|
defenders_location = Conflict._find_ground_position(
|
||||||
|
defenders_location, int(distance * 2),
|
||||||
|
_heading_sum(defense_heading, 180), theater)
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
position=position,
|
position=position,
|
||||||
@ -429,7 +400,7 @@ class Conflict:
|
|||||||
assert cls.has_frontline_between(from_cp, to_cp)
|
assert cls.has_frontline_between(from_cp, to_cp)
|
||||||
|
|
||||||
position, heading, distance = cls.frontline_vector(from_cp, to_cp, theater)
|
position, heading, distance = cls.frontline_vector(from_cp, to_cp, theater)
|
||||||
attack_position = position.point_from_heading(heading, randint(0, int(distance)))
|
attack_position = position.point_from_heading(heading, random.randint(0, int(distance)))
|
||||||
attackers_position = attack_position.point_from_heading(heading - 90, AIR_DISTANCE)
|
attackers_position = attack_position.point_from_heading(heading - 90, AIR_DISTANCE)
|
||||||
defenders_position = attack_position.point_from_heading(heading + 90, random.randint(*CAP_CAS_DISTANCE))
|
defenders_position = attack_position.point_from_heading(heading + 90, random.randint(*CAP_CAS_DISTANCE))
|
||||||
|
|
||||||
@ -456,7 +427,9 @@ class Conflict:
|
|||||||
|
|
||||||
distance = to_cp.size * GROUND_DISTANCE_FACTOR
|
distance = to_cp.size * GROUND_DISTANCE_FACTOR
|
||||||
defenders_location = position.point_from_heading(defense_heading, distance)
|
defenders_location = position.point_from_heading(defense_heading, distance)
|
||||||
defenders_location = Conflict._find_ground_position(defenders_location, distance * 2, _heading_sum(defense_heading, 180), theater)
|
defenders_location = Conflict._find_ground_position(
|
||||||
|
defenders_location, int(distance * 2),
|
||||||
|
_heading_sum(defense_heading, 180), theater)
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
position=position,
|
position=position,
|
||||||
|
|||||||
@ -1,20 +1,11 @@
|
|||||||
import logging
|
import logging
|
||||||
import typing
|
|
||||||
import random
|
import random
|
||||||
from datetime import datetime, timedelta, time
|
from datetime import timedelta
|
||||||
|
|
||||||
from dcs.mission import Mission
|
from dcs.mission import Mission
|
||||||
from dcs.triggers import *
|
from dcs.weather import Weather, Wind
|
||||||
from dcs.condition import *
|
|
||||||
from dcs.action import *
|
|
||||||
from dcs.unit import Skill
|
|
||||||
from dcs.point import MovingPoint, PointProperties
|
|
||||||
from dcs.action import *
|
|
||||||
from dcs.weather import *
|
|
||||||
|
|
||||||
from game import db
|
from .conflictgen import Conflict
|
||||||
from theater import *
|
|
||||||
from gen import *
|
|
||||||
|
|
||||||
WEATHER_CLOUD_BASE = 2000, 3000
|
WEATHER_CLOUD_BASE = 2000, 3000
|
||||||
WEATHER_CLOUD_DENSITY = 1, 8
|
WEATHER_CLOUD_DENSITY = 1, 8
|
||||||
|
|||||||
@ -28,13 +28,13 @@ SHIP_MAP = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def generate_ship_group(game, ground_object, faction:str):
|
def generate_ship_group(game, ground_object, faction_name: str):
|
||||||
"""
|
"""
|
||||||
This generate a ship group
|
This generate a ship group
|
||||||
:return: Nothing, but put the group reference inside the ground object
|
:return: Nothing, but put the group reference inside the ground object
|
||||||
"""
|
"""
|
||||||
faction = db.FACTIONS[faction]
|
faction = db.FACTIONS[faction_name]
|
||||||
if "boat" in faction.keys():
|
if "boat" in faction:
|
||||||
generators = faction["boat"]
|
generators = faction["boat"]
|
||||||
if len(generators) > 0:
|
if len(generators) > 0:
|
||||||
gen = random.choice(generators)
|
gen = random.choice(generators)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,78 @@
|
|||||||
from dcs.planes import *
|
from dcs.helicopters import (
|
||||||
from dcs.helicopters import *
|
AH_1W,
|
||||||
|
AH_64A,
|
||||||
|
AH_64D,
|
||||||
|
Ka_50,
|
||||||
|
Mi_24V,
|
||||||
|
Mi_28N,
|
||||||
|
Mi_8MT,
|
||||||
|
OH_58D,
|
||||||
|
SA342L,
|
||||||
|
SA342M,
|
||||||
|
UH_1H,
|
||||||
|
)
|
||||||
|
from dcs.planes import (
|
||||||
|
AJS37,
|
||||||
|
AV8BNA,
|
||||||
|
A_10A,
|
||||||
|
A_10C,
|
||||||
|
A_10C_2,
|
||||||
|
A_20G,
|
||||||
|
B_17G,
|
||||||
|
Bf_109K_4,
|
||||||
|
C_101CC,
|
||||||
|
FA_18C_hornet,
|
||||||
|
FW_190A8,
|
||||||
|
FW_190D9,
|
||||||
|
F_14B,
|
||||||
|
F_15C,
|
||||||
|
F_15E,
|
||||||
|
F_16A,
|
||||||
|
F_16C_50,
|
||||||
|
F_4E,
|
||||||
|
F_5E_3,
|
||||||
|
F_86F_Sabre,
|
||||||
|
F_A_18C,
|
||||||
|
JF_17,
|
||||||
|
J_11A,
|
||||||
|
Ju_88A4,
|
||||||
|
L_39ZA,
|
||||||
|
MQ_9_Reaper,
|
||||||
|
M_2000C,
|
||||||
|
MiG_15bis,
|
||||||
|
MiG_19P,
|
||||||
|
MiG_21Bis,
|
||||||
|
MiG_23MLD,
|
||||||
|
MiG_25PD,
|
||||||
|
MiG_27K,
|
||||||
|
MiG_29A,
|
||||||
|
MiG_29G,
|
||||||
|
MiG_29K,
|
||||||
|
MiG_29S,
|
||||||
|
MiG_31,
|
||||||
|
Mirage_2000_5,
|
||||||
|
P_47D_30,
|
||||||
|
P_47D_30bl1,
|
||||||
|
P_47D_40,
|
||||||
|
P_51D,
|
||||||
|
P_51D_30_NA,
|
||||||
|
RQ_1A_Predator,
|
||||||
|
SpitfireLFMkIX,
|
||||||
|
SpitfireLFMkIXCW,
|
||||||
|
Su_17M4,
|
||||||
|
Su_24M,
|
||||||
|
Su_24MR,
|
||||||
|
Su_25,
|
||||||
|
Su_25T,
|
||||||
|
Su_25TM,
|
||||||
|
Su_27,
|
||||||
|
Su_30,
|
||||||
|
Su_33,
|
||||||
|
Su_34,
|
||||||
|
Tornado_GR4,
|
||||||
|
Tornado_IDS,
|
||||||
|
WingLoong_I,
|
||||||
|
)
|
||||||
|
|
||||||
# Interceptor are the aircraft prioritized for interception tasks
|
# Interceptor are the aircraft prioritized for interception tasks
|
||||||
# If none is available, the AI will use regular CAP-capable aircraft instead
|
# If none is available, the AI will use regular CAP-capable aircraft instead
|
||||||
@ -7,6 +80,10 @@ from pydcs_extensions.a4ec.a4ec import A_4E_C
|
|||||||
from pydcs_extensions.mb339.mb339 import MB_339PAN
|
from pydcs_extensions.mb339.mb339 import MB_339PAN
|
||||||
from pydcs_extensions.rafale.rafale import Rafale_A_S, Rafale_M
|
from pydcs_extensions.rafale.rafale import Rafale_A_S, Rafale_M
|
||||||
|
|
||||||
|
# TODO: These lists really ought to be era (faction) dependent.
|
||||||
|
# Factions which have F-5s, F-86s, and A-4s will should prefer F-5s for CAP, but
|
||||||
|
# factions that also have F-4s should not.
|
||||||
|
|
||||||
INTERCEPT_CAPABLE = [
|
INTERCEPT_CAPABLE = [
|
||||||
MiG_21Bis,
|
MiG_21Bis,
|
||||||
MiG_25PD,
|
MiG_25PD,
|
||||||
@ -77,6 +154,42 @@ CAP_CAPABLE = [
|
|||||||
Rafale_M,
|
Rafale_M,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
CAP_PREFERRED = [
|
||||||
|
MiG_15bis,
|
||||||
|
MiG_19P,
|
||||||
|
MiG_21Bis,
|
||||||
|
MiG_23MLD,
|
||||||
|
MiG_25PD,
|
||||||
|
MiG_29A,
|
||||||
|
MiG_29G,
|
||||||
|
MiG_29S,
|
||||||
|
MiG_31,
|
||||||
|
|
||||||
|
Su_27,
|
||||||
|
J_11A,
|
||||||
|
Su_30,
|
||||||
|
Su_33,
|
||||||
|
|
||||||
|
M_2000C,
|
||||||
|
Mirage_2000_5,
|
||||||
|
|
||||||
|
F_86F_Sabre,
|
||||||
|
F_14B,
|
||||||
|
F_15C,
|
||||||
|
|
||||||
|
P_51D_30_NA,
|
||||||
|
P_51D,
|
||||||
|
|
||||||
|
SpitfireLFMkIXCW,
|
||||||
|
SpitfireLFMkIX,
|
||||||
|
|
||||||
|
Bf_109K_4,
|
||||||
|
FW_190D9,
|
||||||
|
FW_190A8,
|
||||||
|
|
||||||
|
Rafale_M,
|
||||||
|
]
|
||||||
|
|
||||||
# Used for CAS (Close air support) and BAI (Battlefield Interdiction)
|
# Used for CAS (Close air support) and BAI (Battlefield Interdiction)
|
||||||
CAS_CAPABLE = [
|
CAS_CAPABLE = [
|
||||||
|
|
||||||
@ -155,6 +268,59 @@ CAS_CAPABLE = [
|
|||||||
RQ_1A_Predator
|
RQ_1A_Predator
|
||||||
]
|
]
|
||||||
|
|
||||||
|
CAS_PREFERRED = [
|
||||||
|
Su_17M4,
|
||||||
|
Su_24M,
|
||||||
|
Su_24MR,
|
||||||
|
Su_25,
|
||||||
|
Su_25T,
|
||||||
|
Su_25TM,
|
||||||
|
Su_34,
|
||||||
|
|
||||||
|
JF_17,
|
||||||
|
|
||||||
|
A_10A,
|
||||||
|
A_10C,
|
||||||
|
A_10C_2,
|
||||||
|
AV8BNA,
|
||||||
|
|
||||||
|
F_15E,
|
||||||
|
|
||||||
|
Tornado_GR4,
|
||||||
|
|
||||||
|
C_101CC,
|
||||||
|
MB_339PAN,
|
||||||
|
L_39ZA,
|
||||||
|
AJS37,
|
||||||
|
|
||||||
|
SA342M,
|
||||||
|
SA342L,
|
||||||
|
OH_58D,
|
||||||
|
|
||||||
|
AH_64A,
|
||||||
|
AH_64D,
|
||||||
|
AH_1W,
|
||||||
|
|
||||||
|
UH_1H,
|
||||||
|
|
||||||
|
Mi_8MT,
|
||||||
|
Mi_28N,
|
||||||
|
Mi_24V,
|
||||||
|
Ka_50,
|
||||||
|
|
||||||
|
P_47D_30,
|
||||||
|
P_47D_30bl1,
|
||||||
|
P_47D_40,
|
||||||
|
A_20G,
|
||||||
|
|
||||||
|
A_4E_C,
|
||||||
|
Rafale_A_S,
|
||||||
|
|
||||||
|
WingLoong_I,
|
||||||
|
MQ_9_Reaper,
|
||||||
|
RQ_1A_Predator
|
||||||
|
]
|
||||||
|
|
||||||
# Aircraft used for SEAD / DEAD tasks
|
# Aircraft used for SEAD / DEAD tasks
|
||||||
SEAD_CAPABLE = [
|
SEAD_CAPABLE = [
|
||||||
F_4E,
|
F_4E,
|
||||||
@ -179,6 +345,12 @@ SEAD_CAPABLE = [
|
|||||||
Rafale_A_S
|
Rafale_A_S
|
||||||
]
|
]
|
||||||
|
|
||||||
|
SEAD_PREFERRED = [
|
||||||
|
F_4E,
|
||||||
|
Su_25T,
|
||||||
|
Tornado_IDS,
|
||||||
|
]
|
||||||
|
|
||||||
# Aircraft used for Strike mission
|
# Aircraft used for Strike mission
|
||||||
STRIKE_CAPABLE = [
|
STRIKE_CAPABLE = [
|
||||||
MiG_15bis,
|
MiG_15bis,
|
||||||
@ -236,6 +408,15 @@ STRIKE_CAPABLE = [
|
|||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
STRIKE_PREFERRED = [
|
||||||
|
AJS37,
|
||||||
|
F_15E,
|
||||||
|
Tornado_GR4,
|
||||||
|
|
||||||
|
A_20G,
|
||||||
|
B_17G,
|
||||||
|
]
|
||||||
|
|
||||||
ANTISHIP_CAPABLE = [
|
ANTISHIP_CAPABLE = [
|
||||||
Su_24M,
|
Su_24M,
|
||||||
Su_17M4,
|
Su_17M4,
|
||||||
|
|||||||
51
gen/flights/closestairfields.py
Normal file
51
gen/flights/closestairfields.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
"""Objective adjacency lists."""
|
||||||
|
from typing import Dict, Iterator, List, Optional
|
||||||
|
|
||||||
|
from theater import ConflictTheater, ControlPoint, MissionTarget
|
||||||
|
|
||||||
|
|
||||||
|
class ClosestAirfields:
|
||||||
|
"""Precalculates which control points are closes to the given target."""
|
||||||
|
|
||||||
|
def __init__(self, target: MissionTarget,
|
||||||
|
all_control_points: List[ControlPoint]) -> None:
|
||||||
|
self.target = target
|
||||||
|
self.closest_airfields: List[ControlPoint] = sorted(
|
||||||
|
all_control_points, key=lambda c: self.target.distance_to(c)
|
||||||
|
)
|
||||||
|
|
||||||
|
def airfields_within(self, meters: int) -> Iterator[ControlPoint]:
|
||||||
|
"""Iterates over all airfields within the given range of the target.
|
||||||
|
|
||||||
|
Note that this iterates over *all* airfields, not just friendly
|
||||||
|
airfields.
|
||||||
|
"""
|
||||||
|
for cp in self.closest_airfields:
|
||||||
|
if cp.distance_to(self.target) < meters:
|
||||||
|
yield cp
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectiveDistanceCache:
|
||||||
|
theater: Optional[ConflictTheater] = None
|
||||||
|
closest_airfields: Dict[str, ClosestAirfields] = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def set_theater(cls, theater: ConflictTheater) -> None:
|
||||||
|
if cls.theater is not None:
|
||||||
|
cls.closest_airfields = {}
|
||||||
|
cls.theater = theater
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_closest_airfields(cls, location: MissionTarget) -> ClosestAirfields:
|
||||||
|
if cls.theater is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Call ObjectiveDistanceCache.set_theater before using"
|
||||||
|
)
|
||||||
|
|
||||||
|
if location.name not in cls.closest_airfields:
|
||||||
|
cls.closest_airfields[location.name] = ClosestAirfields(
|
||||||
|
location, cls.theater.controlpoints
|
||||||
|
)
|
||||||
|
return cls.closest_airfields[location.name]
|
||||||
@ -1,10 +1,10 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from game import db
|
from game import db
|
||||||
from dcs.unittype import UnitType
|
from dcs.unittype import UnitType
|
||||||
from dcs.point import MovingPoint, PointAction
|
from dcs.point import MovingPoint, PointAction
|
||||||
from theater.controlpoint import ControlPoint
|
from theater.controlpoint import ControlPoint, MissionTarget
|
||||||
|
|
||||||
|
|
||||||
class FlightType(Enum):
|
class FlightType(Enum):
|
||||||
@ -47,6 +47,8 @@ class FlightWaypointType(Enum):
|
|||||||
TARGET_GROUP_LOC = 13 # A target group approximate location
|
TARGET_GROUP_LOC = 13 # A target group approximate location
|
||||||
TARGET_SHIP = 14 # A target ship known location
|
TARGET_SHIP = 14 # A target ship known location
|
||||||
CUSTOM = 15 # User waypoint (no specific behaviour)
|
CUSTOM = 15 # User waypoint (no specific behaviour)
|
||||||
|
JOIN = 16
|
||||||
|
SPLIT = 17
|
||||||
|
|
||||||
|
|
||||||
class PredefinedWaypointCategory(Enum):
|
class PredefinedWaypointCategory(Enum):
|
||||||
@ -71,8 +73,8 @@ class FlightWaypoint:
|
|||||||
self.alt_type = "BARO"
|
self.alt_type = "BARO"
|
||||||
self.name = ""
|
self.name = ""
|
||||||
self.description = ""
|
self.description = ""
|
||||||
self.targets = []
|
self.targets: List[MissionTarget] = []
|
||||||
self.targetGroup = None
|
self.targetGroup: Optional[MissionTarget] = None
|
||||||
self.obj_name = ""
|
self.obj_name = ""
|
||||||
self.pretty_name = ""
|
self.pretty_name = ""
|
||||||
self.category: PredefinedWaypointCategory = PredefinedWaypointCategory.NOT_PREDEFINED
|
self.category: PredefinedWaypointCategory = PredefinedWaypointCategory.NOT_PREDEFINED
|
||||||
@ -108,15 +110,9 @@ class FlightWaypoint:
|
|||||||
|
|
||||||
|
|
||||||
class Flight:
|
class Flight:
|
||||||
unit_type: UnitType = None
|
|
||||||
from_cp = None
|
|
||||||
points: List[FlightWaypoint] = []
|
|
||||||
flight_type: FlightType = None
|
|
||||||
count: int = 0
|
count: int = 0
|
||||||
client_count: int = 0
|
client_count: int = 0
|
||||||
targets = []
|
|
||||||
use_custom_loadout = False
|
use_custom_loadout = False
|
||||||
loadout = {}
|
|
||||||
preset_loadout_name = ""
|
preset_loadout_name = ""
|
||||||
start_type = "Runway"
|
start_type = "Runway"
|
||||||
group = False # Contains DCS Mission group data after mission has been generated
|
group = False # Contains DCS Mission group data after mission has been generated
|
||||||
@ -124,14 +120,14 @@ class Flight:
|
|||||||
# How long before this flight should take off
|
# How long before this flight should take off
|
||||||
scheduled_in = 0
|
scheduled_in = 0
|
||||||
|
|
||||||
def __init__(self, unit_type: UnitType, count: int, from_cp, flight_type: FlightType):
|
def __init__(self, unit_type: UnitType, count: int, from_cp: ControlPoint, flight_type: FlightType):
|
||||||
self.unit_type = unit_type
|
self.unit_type = unit_type
|
||||||
self.count = count
|
self.count = count
|
||||||
self.from_cp = from_cp
|
self.from_cp = from_cp
|
||||||
self.flight_type = flight_type
|
self.flight_type = flight_type
|
||||||
self.points = []
|
self.points: List[FlightWaypoint] = []
|
||||||
self.targets = []
|
self.targets: List[MissionTarget] = []
|
||||||
self.loadout = {}
|
self.loadout: Dict[str, str] = {}
|
||||||
self.start_type = "Runway"
|
self.start_type = "Runway"
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@ -141,10 +137,10 @@ class Flight:
|
|||||||
|
|
||||||
# Test
|
# Test
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from pydcs.dcs.planes import A_10C
|
from dcs.planes import A_10C
|
||||||
from theater import ControlPoint, Point, List
|
from theater import ControlPoint, Point, List
|
||||||
|
|
||||||
from_cp = ControlPoint(0, "AA", Point(0, 0), None, [], 0, 0)
|
from_cp = ControlPoint(0, "AA", Point(0, 0), Point(0, 0), [], 0, 0)
|
||||||
f = Flight(A_10C(), 4, from_cp, FlightType.CAS)
|
f = Flight(A_10C(), 4, from_cp, FlightType.CAS)
|
||||||
f.scheduled_in = 50
|
f.scheduled_in = 50
|
||||||
print(f)
|
print(f)
|
||||||
|
|||||||
442
gen/flights/flightplan.py
Normal file
442
gen/flights/flightplan.py
Normal file
@ -0,0 +1,442 @@
|
|||||||
|
"""Flight plan generation.
|
||||||
|
|
||||||
|
Flights are first planned generically by either the player or by the
|
||||||
|
MissionPlanner. Those only plan basic information like the objective, aircraft
|
||||||
|
type, and the size of the flight. The FlightPlanBuilder is responsible for
|
||||||
|
generating the waypoints for the mission.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
|
from typing import List, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
|
from dcs.mapping import Point
|
||||||
|
from dcs.unit import Unit
|
||||||
|
|
||||||
|
from game.data.doctrine import Doctrine, MODERN_DOCTRINE
|
||||||
|
from game.utils import nm_to_meter
|
||||||
|
from gen.ato import Package
|
||||||
|
from theater import ControlPoint, FrontLine, MissionTarget, TheaterGroundObject
|
||||||
|
from .closestairfields import ObjectiveDistanceCache
|
||||||
|
from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
|
||||||
|
from .waypointbuilder import WaypointBuilder
|
||||||
|
from ..conflictgen import Conflict
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from game import Game
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidObjectiveLocation(RuntimeError):
|
||||||
|
"""Raised when the objective location is invalid for the mission type."""
|
||||||
|
def __init__(self, task: FlightType, location: MissionTarget) -> None:
|
||||||
|
super().__init__(
|
||||||
|
f"{location.name} is not valid for {task.name} missions."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FlightPlanBuilder:
|
||||||
|
"""Generates flight plans for flights."""
|
||||||
|
|
||||||
|
def __init__(self, game: Game, package: Package, is_player: bool) -> None:
|
||||||
|
self.game = game
|
||||||
|
self.package = package
|
||||||
|
self.is_player = is_player
|
||||||
|
if is_player:
|
||||||
|
faction = self.game.player_faction
|
||||||
|
else:
|
||||||
|
faction = self.game.enemy_faction
|
||||||
|
self.doctrine: Doctrine = faction.get("doctrine", MODERN_DOCTRINE)
|
||||||
|
|
||||||
|
def populate_flight_plan(
|
||||||
|
self, flight: Flight,
|
||||||
|
# TODO: Custom targets should be an attribute of the flight.
|
||||||
|
custom_targets: Optional[List[Unit]] = None) -> None:
|
||||||
|
"""Creates a default flight plan for the given mission."""
|
||||||
|
if flight not in self.package.flights:
|
||||||
|
raise RuntimeError("Flight must be a part of the package")
|
||||||
|
self.generate_missing_package_waypoints()
|
||||||
|
|
||||||
|
# TODO: Flesh out mission types.
|
||||||
|
try:
|
||||||
|
task = flight.flight_type
|
||||||
|
if task == FlightType.ANTISHIP:
|
||||||
|
logging.error(
|
||||||
|
"Anti-ship flight plan generation not implemented"
|
||||||
|
)
|
||||||
|
elif task == FlightType.BAI:
|
||||||
|
logging.error("BAI flight plan generation not implemented")
|
||||||
|
elif task == FlightType.BARCAP:
|
||||||
|
self.generate_barcap(flight)
|
||||||
|
elif task == FlightType.CAP:
|
||||||
|
self.generate_barcap(flight)
|
||||||
|
elif task == FlightType.CAS:
|
||||||
|
self.generate_cas(flight)
|
||||||
|
elif task == FlightType.DEAD:
|
||||||
|
self.generate_sead(flight, custom_targets)
|
||||||
|
elif task == FlightType.ELINT:
|
||||||
|
logging.error("ELINT flight plan generation not implemented")
|
||||||
|
elif task == FlightType.ESCORT:
|
||||||
|
self.generate_escort(flight)
|
||||||
|
elif task == FlightType.EVAC:
|
||||||
|
logging.error("Evac flight plan generation not implemented")
|
||||||
|
elif task == FlightType.EWAR:
|
||||||
|
logging.error("EWar flight plan generation not implemented")
|
||||||
|
elif task == FlightType.INTERCEPTION:
|
||||||
|
logging.error(
|
||||||
|
"Intercept flight plan generation not implemented"
|
||||||
|
)
|
||||||
|
elif task == FlightType.LOGISTICS:
|
||||||
|
logging.error(
|
||||||
|
"Logistics flight plan generation not implemented"
|
||||||
|
)
|
||||||
|
elif task == FlightType.RECON:
|
||||||
|
logging.error("Recon flight plan generation not implemented")
|
||||||
|
elif task == FlightType.SEAD:
|
||||||
|
self.generate_sead(flight, custom_targets)
|
||||||
|
elif task == FlightType.STRIKE:
|
||||||
|
self.generate_strike(flight)
|
||||||
|
elif task == FlightType.TARCAP:
|
||||||
|
self.generate_frontline_cap(flight)
|
||||||
|
elif task == FlightType.TROOP_TRANSPORT:
|
||||||
|
logging.error(
|
||||||
|
"Troop transport flight plan generation not implemented"
|
||||||
|
)
|
||||||
|
except InvalidObjectiveLocation as ex:
|
||||||
|
logging.error(f"Could not create flight plan: {ex}")
|
||||||
|
|
||||||
|
def generate_missing_package_waypoints(self) -> None:
|
||||||
|
if self.package.ingress_point is None:
|
||||||
|
self.package.ingress_point = self._ingress_point()
|
||||||
|
if self.package.egress_point is None:
|
||||||
|
self.package.egress_point = self._egress_point()
|
||||||
|
if self.package.join_point is None:
|
||||||
|
self.package.join_point = self._join_point()
|
||||||
|
if self.package.split_point is None:
|
||||||
|
self.package.split_point = self._split_point()
|
||||||
|
|
||||||
|
def generate_strike(self, flight: Flight) -> None:
|
||||||
|
"""Generates a strike flight plan.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
flight: The flight to generate the flight plan for.
|
||||||
|
"""
|
||||||
|
location = self.package.target
|
||||||
|
|
||||||
|
# TODO: Support airfield strikes.
|
||||||
|
if not isinstance(location, TheaterGroundObject):
|
||||||
|
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||||
|
|
||||||
|
builder = WaypointBuilder(self.doctrine)
|
||||||
|
builder.ascent(flight.from_cp)
|
||||||
|
builder.join(self.package.join_point)
|
||||||
|
builder.ingress_strike(self.package.ingress_point, location)
|
||||||
|
|
||||||
|
if len(location.groups) > 0 and location.dcs_identifier == "AA":
|
||||||
|
# TODO: Replace with DEAD?
|
||||||
|
# Strike missions on SEAD targets target units.
|
||||||
|
for g in location.groups:
|
||||||
|
for j, u in enumerate(g.units):
|
||||||
|
builder.strike_point(u, f"{u.type} #{j}", location)
|
||||||
|
else:
|
||||||
|
# TODO: Does this actually happen?
|
||||||
|
# ConflictTheater is built with the belief that multiple ground
|
||||||
|
# objects have the same name. If that's the case,
|
||||||
|
# TheaterGroundObject needs some refactoring because it behaves very
|
||||||
|
# differently for SAM sites than it does for strike targets.
|
||||||
|
buildings = self.game.theater.find_ground_objects_by_obj_name(
|
||||||
|
location.obj_name
|
||||||
|
)
|
||||||
|
for building in buildings:
|
||||||
|
if building.is_dead:
|
||||||
|
continue
|
||||||
|
|
||||||
|
builder.strike_point(
|
||||||
|
building,
|
||||||
|
f"{building.obj_name} {building.category}",
|
||||||
|
location
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.egress(self.package.egress_point, location)
|
||||||
|
builder.split(self.package.split_point)
|
||||||
|
builder.rtb(flight.from_cp)
|
||||||
|
|
||||||
|
flight.points = builder.build()
|
||||||
|
|
||||||
|
def generate_barcap(self, flight: Flight) -> None:
|
||||||
|
"""Generate a BARCAP flight at a given location.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
flight: The flight to generate the flight plan for.
|
||||||
|
"""
|
||||||
|
location = self.package.target
|
||||||
|
|
||||||
|
if isinstance(location, FrontLine):
|
||||||
|
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||||
|
|
||||||
|
patrol_alt = random.randint(
|
||||||
|
self.doctrine.min_patrol_altitude,
|
||||||
|
self.doctrine.max_patrol_altitude
|
||||||
|
)
|
||||||
|
|
||||||
|
closest_cache = ObjectiveDistanceCache.get_closest_airfields(location)
|
||||||
|
for airfield in closest_cache.closest_airfields:
|
||||||
|
if airfield.captured != self.is_player:
|
||||||
|
closest_airfield = airfield
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
logging.error("Could not find any enemy airfields")
|
||||||
|
return
|
||||||
|
|
||||||
|
heading = location.position.heading_between_point(
|
||||||
|
closest_airfield.position
|
||||||
|
)
|
||||||
|
|
||||||
|
end = location.position.point_from_heading(
|
||||||
|
heading,
|
||||||
|
random.randint(self.doctrine.cap_min_distance_from_cp,
|
||||||
|
self.doctrine.cap_max_distance_from_cp)
|
||||||
|
)
|
||||||
|
diameter = random.randint(
|
||||||
|
self.doctrine.cap_min_track_length,
|
||||||
|
self.doctrine.cap_max_track_length
|
||||||
|
)
|
||||||
|
start = end.point_from_heading(heading - 180, diameter)
|
||||||
|
|
||||||
|
builder = WaypointBuilder(self.doctrine)
|
||||||
|
builder.ascent(flight.from_cp)
|
||||||
|
builder.race_track(start, end, patrol_alt)
|
||||||
|
builder.rtb(flight.from_cp)
|
||||||
|
flight.points = builder.build()
|
||||||
|
|
||||||
|
def generate_frontline_cap(self, flight: Flight) -> None:
|
||||||
|
"""Generate a CAP flight plan for the given front line.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
flight: The flight to generate the flight plan for.
|
||||||
|
"""
|
||||||
|
location = self.package.target
|
||||||
|
|
||||||
|
if not isinstance(location, FrontLine):
|
||||||
|
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||||
|
|
||||||
|
ally_cp, enemy_cp = location.control_points
|
||||||
|
patrol_alt = random.randint(self.doctrine.min_patrol_altitude,
|
||||||
|
self.doctrine.max_patrol_altitude)
|
||||||
|
|
||||||
|
# Find targets waypoints
|
||||||
|
ingress, heading, distance = Conflict.frontline_vector(
|
||||||
|
ally_cp, enemy_cp, self.game.theater
|
||||||
|
)
|
||||||
|
center = ingress.point_from_heading(heading, distance / 2)
|
||||||
|
orbit_center = center.point_from_heading(
|
||||||
|
heading - 90, random.randint(nm_to_meter(6), nm_to_meter(15))
|
||||||
|
)
|
||||||
|
|
||||||
|
combat_width = distance / 2
|
||||||
|
if combat_width > 500000:
|
||||||
|
combat_width = 500000
|
||||||
|
if combat_width < 35000:
|
||||||
|
combat_width = 35000
|
||||||
|
|
||||||
|
radius = combat_width*1.25
|
||||||
|
orbit0p = orbit_center.point_from_heading(heading, radius)
|
||||||
|
orbit1p = orbit_center.point_from_heading(heading + 180, radius)
|
||||||
|
|
||||||
|
# Create points
|
||||||
|
builder = WaypointBuilder(self.doctrine)
|
||||||
|
builder.ascent(flight.from_cp)
|
||||||
|
builder.race_track(orbit0p, orbit1p, patrol_alt)
|
||||||
|
builder.rtb(flight.from_cp)
|
||||||
|
flight.points = builder.build()
|
||||||
|
|
||||||
|
def generate_sead(self, flight: Flight,
|
||||||
|
custom_targets: Optional[List[Unit]]) -> None:
|
||||||
|
"""Generate a SEAD/DEAD flight at a given location.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
flight: The flight to generate the flight plan for.
|
||||||
|
custom_targets: Specific radar equipped units selected by the user.
|
||||||
|
"""
|
||||||
|
location = self.package.target
|
||||||
|
|
||||||
|
if not isinstance(location, TheaterGroundObject):
|
||||||
|
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||||
|
|
||||||
|
if custom_targets is None:
|
||||||
|
custom_targets = []
|
||||||
|
|
||||||
|
builder = WaypointBuilder(self.doctrine)
|
||||||
|
builder.ascent(flight.from_cp)
|
||||||
|
builder.join(self.package.join_point)
|
||||||
|
builder.ingress_sead(self.package.ingress_point, location)
|
||||||
|
|
||||||
|
# TODO: Unify these.
|
||||||
|
# There doesn't seem to be any reason to treat the UI fragged missions
|
||||||
|
# different from the automatic missions.
|
||||||
|
if custom_targets:
|
||||||
|
for target in custom_targets:
|
||||||
|
point = FlightWaypoint(
|
||||||
|
FlightWaypointType.TARGET_POINT,
|
||||||
|
target.position.x,
|
||||||
|
target.position.y,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
point.alt_type = "RADIO"
|
||||||
|
if flight.flight_type == FlightType.DEAD:
|
||||||
|
builder.dead_point(target, location.name, location)
|
||||||
|
else:
|
||||||
|
builder.sead_point(target, location.name, location)
|
||||||
|
else:
|
||||||
|
if flight.flight_type == FlightType.DEAD:
|
||||||
|
builder.dead_area(location)
|
||||||
|
else:
|
||||||
|
builder.sead_area(location)
|
||||||
|
|
||||||
|
builder.egress(self.package.egress_point, location)
|
||||||
|
builder.split(self.package.split_point)
|
||||||
|
builder.rtb(flight.from_cp)
|
||||||
|
|
||||||
|
flight.points = builder.build()
|
||||||
|
|
||||||
|
def generate_escort(self, flight: Flight) -> None:
|
||||||
|
# TODO: Decide common waypoints for the package ahead of time.
|
||||||
|
# Packages should determine some common points like push, ingress,
|
||||||
|
# egress, and split points ahead of time so they can be shared by all
|
||||||
|
# flights.
|
||||||
|
|
||||||
|
patrol_alt = random.randint(
|
||||||
|
self.doctrine.min_patrol_altitude,
|
||||||
|
self.doctrine.max_patrol_altitude
|
||||||
|
)
|
||||||
|
|
||||||
|
builder = WaypointBuilder(self.doctrine)
|
||||||
|
builder.ascent(flight.from_cp)
|
||||||
|
builder.join(self.package.join_point)
|
||||||
|
builder.race_track(
|
||||||
|
self.package.ingress_point,
|
||||||
|
self.package.egress_point,
|
||||||
|
patrol_alt
|
||||||
|
)
|
||||||
|
builder.split(self.package.split_point)
|
||||||
|
builder.rtb(flight.from_cp)
|
||||||
|
|
||||||
|
flight.points = builder.build()
|
||||||
|
|
||||||
|
def generate_cas(self, flight: Flight) -> None:
|
||||||
|
"""Generate a CAS flight plan for the given target.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
flight: The flight to generate the flight plan for.
|
||||||
|
"""
|
||||||
|
location = self.package.target
|
||||||
|
|
||||||
|
if not isinstance(location, FrontLine):
|
||||||
|
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||||
|
|
||||||
|
is_helo = getattr(flight.unit_type, "helicopter", False)
|
||||||
|
cap_alt = 500 if is_helo else 1000
|
||||||
|
|
||||||
|
ingress, heading, distance = Conflict.frontline_vector(
|
||||||
|
location.control_points[0], location.control_points[1],
|
||||||
|
self.game.theater
|
||||||
|
)
|
||||||
|
center = ingress.point_from_heading(heading, distance / 2)
|
||||||
|
egress = ingress.point_from_heading(heading, distance)
|
||||||
|
|
||||||
|
builder = WaypointBuilder(self.doctrine)
|
||||||
|
builder.ascent(flight.from_cp, is_helo)
|
||||||
|
builder.join(self.package.join_point)
|
||||||
|
builder.ingress_cas(ingress, location)
|
||||||
|
builder.cas(center, cap_alt)
|
||||||
|
builder.egress(egress, location)
|
||||||
|
builder.split(self.package.split_point)
|
||||||
|
builder.rtb(flight.from_cp, is_helo)
|
||||||
|
|
||||||
|
flight.points = builder.build()
|
||||||
|
|
||||||
|
# TODO: Make a model for the waypoint builder and use that in the UI.
|
||||||
|
def generate_ascend_point(self, departure: ControlPoint) -> FlightWaypoint:
|
||||||
|
"""Generate ascend point.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
departure: Departure airfield or carrier.
|
||||||
|
"""
|
||||||
|
builder = WaypointBuilder(self.doctrine)
|
||||||
|
builder.ascent(departure)
|
||||||
|
return builder.build()[0]
|
||||||
|
|
||||||
|
def generate_descend_point(self, arrival: ControlPoint) -> FlightWaypoint:
|
||||||
|
"""Generate approach/descend point.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
arrival: Arrival airfield or carrier.
|
||||||
|
"""
|
||||||
|
builder = WaypointBuilder(self.doctrine)
|
||||||
|
builder.descent(arrival)
|
||||||
|
return builder.build()[0]
|
||||||
|
|
||||||
|
def generate_rtb_waypoint(self, arrival: ControlPoint) -> FlightWaypoint:
|
||||||
|
"""Generate RTB landing point.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
arrival: Arrival airfield or carrier.
|
||||||
|
"""
|
||||||
|
builder = WaypointBuilder(self.doctrine)
|
||||||
|
builder.land(arrival)
|
||||||
|
return builder.build()[0]
|
||||||
|
|
||||||
|
def _join_point(self) -> Point:
|
||||||
|
ingress_point = self.package.ingress_point
|
||||||
|
assert ingress_point is not None
|
||||||
|
heading = self._heading_to_package_airfield(ingress_point)
|
||||||
|
return ingress_point.point_from_heading(heading,
|
||||||
|
-self.doctrine.join_distance)
|
||||||
|
|
||||||
|
def _split_point(self) -> Point:
|
||||||
|
egress_point = self.package.egress_point
|
||||||
|
assert egress_point is not None
|
||||||
|
heading = self._heading_to_package_airfield(egress_point)
|
||||||
|
return egress_point.point_from_heading(heading,
|
||||||
|
-self.doctrine.split_distance)
|
||||||
|
|
||||||
|
def _ingress_point(self) -> Point:
|
||||||
|
heading = self._target_heading_to_package_airfield()
|
||||||
|
return self.package.target.position.point_from_heading(
|
||||||
|
heading - 180 + 25, self.doctrine.ingress_egress_distance
|
||||||
|
)
|
||||||
|
|
||||||
|
def _egress_point(self) -> Point:
|
||||||
|
heading = self._target_heading_to_package_airfield()
|
||||||
|
return self.package.target.position.point_from_heading(
|
||||||
|
heading - 180 - 25, self.doctrine.ingress_egress_distance
|
||||||
|
)
|
||||||
|
|
||||||
|
def _target_heading_to_package_airfield(self) -> int:
|
||||||
|
return self._heading_to_package_airfield(self.package.target.position)
|
||||||
|
|
||||||
|
def _heading_to_package_airfield(self, point: Point) -> int:
|
||||||
|
return self.package_airfield().position.heading_between_point(point)
|
||||||
|
|
||||||
|
# TODO: Set ingress/egress/join/split points in the Package.
|
||||||
|
def package_airfield(self) -> ControlPoint:
|
||||||
|
# We'll always have a package, but if this is being planned via the UI
|
||||||
|
# it could be the first flight in the package.
|
||||||
|
if not self.package.flights:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Cannot determine source airfield for package with no flights"
|
||||||
|
)
|
||||||
|
|
||||||
|
# The package airfield is either the flight's airfield (when there is no
|
||||||
|
# package) or the closest airfield to the objective that is the
|
||||||
|
# departure airfield for some flight in the package.
|
||||||
|
cache = ObjectiveDistanceCache.get_closest_airfields(
|
||||||
|
self.package.target
|
||||||
|
)
|
||||||
|
for airfield in cache.closest_airfields:
|
||||||
|
for flight in self.package.flights:
|
||||||
|
if flight.from_cp == airfield:
|
||||||
|
return airfield
|
||||||
|
raise RuntimeError(
|
||||||
|
"Could not find any airfield assigned to this package"
|
||||||
|
)
|
||||||
298
gen/flights/waypointbuilder.py
Normal file
298
gen/flights/waypointbuilder.py
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
|
from dcs.mapping import Point
|
||||||
|
from dcs.unit import Unit
|
||||||
|
|
||||||
|
from game.data.doctrine import Doctrine
|
||||||
|
from game.utils import nm_to_meter
|
||||||
|
from theater import ControlPoint, MissionTarget, TheaterGroundObject
|
||||||
|
from .flight import FlightWaypoint, FlightWaypointType
|
||||||
|
|
||||||
|
|
||||||
|
class WaypointBuilder:
|
||||||
|
def __init__(self, doctrine: Doctrine) -> None:
|
||||||
|
self.doctrine = doctrine
|
||||||
|
self.waypoints: List[FlightWaypoint] = []
|
||||||
|
self.ingress_point: Optional[FlightWaypoint] = None
|
||||||
|
|
||||||
|
def build(self) -> List[FlightWaypoint]:
|
||||||
|
return self.waypoints
|
||||||
|
|
||||||
|
def ascent(self, departure: ControlPoint, is_helo: bool = False) -> None:
|
||||||
|
"""Create ascent waypoint for the given departure airfield or carrier.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
departure: Departure airfield or carrier.
|
||||||
|
"""
|
||||||
|
# TODO: Pick runway based on wind direction.
|
||||||
|
heading = departure.heading
|
||||||
|
position = departure.position.point_from_heading(
|
||||||
|
heading, nm_to_meter(5)
|
||||||
|
)
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.ASCEND_POINT,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
500 if is_helo else self.doctrine.pattern_altitude
|
||||||
|
)
|
||||||
|
waypoint.name = "ASCEND"
|
||||||
|
waypoint.alt_type = "RADIO"
|
||||||
|
waypoint.description = "Ascend"
|
||||||
|
waypoint.pretty_name = "Ascend"
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
|
||||||
|
def descent(self, arrival: ControlPoint, is_helo: bool = False) -> None:
|
||||||
|
"""Create descent waypoint for the given arrival airfield or carrier.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
arrival: Arrival airfield or carrier.
|
||||||
|
"""
|
||||||
|
# TODO: Pick runway based on wind direction.
|
||||||
|
# ControlPoint.heading is the departure heading.
|
||||||
|
heading = (arrival.heading + 180) % 360
|
||||||
|
position = arrival.position.point_from_heading(
|
||||||
|
heading, nm_to_meter(5)
|
||||||
|
)
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.DESCENT_POINT,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
300 if is_helo else self.doctrine.pattern_altitude
|
||||||
|
)
|
||||||
|
waypoint.name = "DESCEND"
|
||||||
|
waypoint.alt_type = "RADIO"
|
||||||
|
waypoint.description = "Descend to pattern altitude"
|
||||||
|
waypoint.pretty_name = "Ascend"
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
|
||||||
|
def land(self, arrival: ControlPoint) -> None:
|
||||||
|
"""Create descent waypoint for the given arrival airfield or carrier.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
arrival: Arrival airfield or carrier.
|
||||||
|
"""
|
||||||
|
position = arrival.position
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.LANDING_POINT,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
waypoint.name = "LANDING"
|
||||||
|
waypoint.alt_type = "RADIO"
|
||||||
|
waypoint.description = "Land"
|
||||||
|
waypoint.pretty_name = "Land"
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
|
||||||
|
def join(self, position: Point) -> None:
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.JOIN,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
self.doctrine.ingress_altitude
|
||||||
|
)
|
||||||
|
waypoint.pretty_name = "Join"
|
||||||
|
waypoint.description = "Rendezvous with package"
|
||||||
|
waypoint.name = "JOIN"
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
|
||||||
|
def split(self, position: Point) -> None:
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.SPLIT,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
self.doctrine.ingress_altitude
|
||||||
|
)
|
||||||
|
waypoint.pretty_name = "Split"
|
||||||
|
waypoint.description = "Depart from package"
|
||||||
|
waypoint.name = "SPLIT"
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
|
||||||
|
def ingress_cas(self, position: Point, objective: MissionTarget) -> None:
|
||||||
|
self._ingress(FlightWaypointType.INGRESS_CAS, position, objective)
|
||||||
|
|
||||||
|
def ingress_sead(self, position: Point, objective: MissionTarget) -> None:
|
||||||
|
self._ingress(FlightWaypointType.INGRESS_SEAD, position, objective)
|
||||||
|
|
||||||
|
def ingress_strike(self, position: Point, objective: MissionTarget) -> None:
|
||||||
|
self._ingress(FlightWaypointType.INGRESS_STRIKE, position, objective)
|
||||||
|
|
||||||
|
def _ingress(self, ingress_type: FlightWaypointType, position: Point,
|
||||||
|
objective: MissionTarget) -> None:
|
||||||
|
if self.ingress_point is not None:
|
||||||
|
raise RuntimeError("A flight plan can have only one ingress point.")
|
||||||
|
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
ingress_type,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
self.doctrine.ingress_altitude
|
||||||
|
)
|
||||||
|
waypoint.pretty_name = "INGRESS on " + objective.name
|
||||||
|
waypoint.description = "INGRESS on " + objective.name
|
||||||
|
waypoint.name = "INGRESS"
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
self.ingress_point = waypoint
|
||||||
|
|
||||||
|
def egress(self, position: Point, target: MissionTarget) -> None:
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.EGRESS,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
self.doctrine.ingress_altitude
|
||||||
|
)
|
||||||
|
waypoint.pretty_name = "EGRESS from " + target.name
|
||||||
|
waypoint.description = "EGRESS from " + target.name
|
||||||
|
waypoint.name = "EGRESS"
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
|
||||||
|
def dead_point(self, target: Union[TheaterGroundObject, Unit], name: str,
|
||||||
|
location: MissionTarget) -> None:
|
||||||
|
self._target_point(target, name, f"STRIKE [{location.name}]: {name}",
|
||||||
|
location)
|
||||||
|
# TODO: Seems fishy.
|
||||||
|
if self.ingress_point is not None:
|
||||||
|
self.ingress_point.targetGroup = location
|
||||||
|
|
||||||
|
def sead_point(self, target: Union[TheaterGroundObject, Unit], name: str,
|
||||||
|
location: MissionTarget) -> None:
|
||||||
|
self._target_point(target, name, f"STRIKE [{location.name}]: {name}",
|
||||||
|
location)
|
||||||
|
# TODO: Seems fishy.
|
||||||
|
if self.ingress_point is not None:
|
||||||
|
self.ingress_point.targetGroup = location
|
||||||
|
|
||||||
|
def strike_point(self, target: Union[TheaterGroundObject, Unit], name: str,
|
||||||
|
location: MissionTarget) -> None:
|
||||||
|
self._target_point(target, name, f"STRIKE [{location.name}]: {name}",
|
||||||
|
location)
|
||||||
|
|
||||||
|
def _target_point(self, target: Union[TheaterGroundObject, Unit], name: str,
|
||||||
|
description: str, location: MissionTarget) -> None:
|
||||||
|
if self.ingress_point is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
"An ingress point must be added before target points."
|
||||||
|
)
|
||||||
|
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.TARGET_POINT,
|
||||||
|
target.position.x,
|
||||||
|
target.position.y,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
waypoint.description = description
|
||||||
|
waypoint.pretty_name = description
|
||||||
|
waypoint.name = name
|
||||||
|
waypoint.only_for_player = True
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
# TODO: This seems wrong, but it's what was there before.
|
||||||
|
self.ingress_point.targets.append(location)
|
||||||
|
|
||||||
|
def sead_area(self, target: MissionTarget) -> None:
|
||||||
|
self._target_area(f"SEAD on {target.name}", target)
|
||||||
|
# TODO: Seems fishy.
|
||||||
|
if self.ingress_point is not None:
|
||||||
|
self.ingress_point.targetGroup = target
|
||||||
|
|
||||||
|
def dead_area(self, target: MissionTarget) -> None:
|
||||||
|
self._target_area(f"DEAD on {target.name}", target)
|
||||||
|
# TODO: Seems fishy.
|
||||||
|
if self.ingress_point is not None:
|
||||||
|
self.ingress_point.targetGroup = target
|
||||||
|
|
||||||
|
def _target_area(self, name: str, location: MissionTarget) -> None:
|
||||||
|
if self.ingress_point is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
"An ingress point must be added before target points."
|
||||||
|
)
|
||||||
|
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.TARGET_GROUP_LOC,
|
||||||
|
location.position.x,
|
||||||
|
location.position.y,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
waypoint.description = name
|
||||||
|
waypoint.pretty_name = name
|
||||||
|
waypoint.name = name
|
||||||
|
waypoint.only_for_player = True
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
# TODO: This seems wrong, but it's what was there before.
|
||||||
|
self.ingress_point.targets.append(location)
|
||||||
|
|
||||||
|
def cas(self, position: Point, altitude: int) -> None:
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.CAS,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
altitude
|
||||||
|
)
|
||||||
|
waypoint.alt_type = "RADIO"
|
||||||
|
waypoint.description = "Provide CAS"
|
||||||
|
waypoint.name = "CAS"
|
||||||
|
waypoint.pretty_name = "CAS"
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
|
||||||
|
def race_track_start(self, position: Point, altitude: int) -> None:
|
||||||
|
"""Creates a racetrack start waypoint.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
position: Position of the waypoint.
|
||||||
|
altitude: Altitude of the racetrack in meters.
|
||||||
|
"""
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.PATROL_TRACK,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
altitude
|
||||||
|
)
|
||||||
|
waypoint.name = "RACETRACK START"
|
||||||
|
waypoint.description = "Orbit between this point and the next point"
|
||||||
|
waypoint.pretty_name = "Race-track start"
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
|
||||||
|
# TODO: Does this actually do anything?
|
||||||
|
# orbit0.targets.append(location)
|
||||||
|
# Note: Targets of PATROL TRACK waypoints are the points to be defended.
|
||||||
|
# orbit0.targets.append(flight.from_cp)
|
||||||
|
# orbit0.targets.append(center)
|
||||||
|
|
||||||
|
def race_track_end(self, position: Point, altitude: int) -> None:
|
||||||
|
"""Creates a racetrack end waypoint.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
position: Position of the waypoint.
|
||||||
|
altitude: Altitude of the racetrack in meters.
|
||||||
|
"""
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.PATROL,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
altitude
|
||||||
|
)
|
||||||
|
waypoint.name = "RACETRACK END"
|
||||||
|
waypoint.description = "Orbit between this point and the previous point"
|
||||||
|
waypoint.pretty_name = "Race-track end"
|
||||||
|
self.waypoints.append(waypoint)
|
||||||
|
|
||||||
|
def race_track(self, start: Point, end: Point, altitude: int) -> None:
|
||||||
|
"""Creates two waypoint for a racetrack orbit.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
start: The beginning racetrack waypoint.
|
||||||
|
end: The ending racetrack waypoint.
|
||||||
|
altitude: The racetrack altitude.
|
||||||
|
"""
|
||||||
|
self.race_track_start(start, altitude)
|
||||||
|
self.race_track_end(end, altitude)
|
||||||
|
|
||||||
|
def rtb(self, arrival: ControlPoint, is_helo: bool = False) -> None:
|
||||||
|
"""Creates descent ant landing waypoints for the given control point.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
arrival: Arrival airfield or carrier.
|
||||||
|
"""
|
||||||
|
self.descent(arrival, is_helo)
|
||||||
|
self.land(arrival)
|
||||||
@ -1,13 +1,13 @@
|
|||||||
import random
|
import random
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
from dcs.vehicles import *
|
from dcs.vehicles import Armor, Artillery, Infantry, Unarmed
|
||||||
|
from dcs.unittype import VehicleType
|
||||||
from gen import Conflict
|
|
||||||
from gen.ground_forces.combat_stance import CombatStance
|
|
||||||
from theater import ControlPoint
|
|
||||||
|
|
||||||
import pydcs_extensions.frenchpack.frenchpack as frenchpack
|
import pydcs_extensions.frenchpack.frenchpack as frenchpack
|
||||||
|
from gen.ground_forces.combat_stance import CombatStance
|
||||||
|
from theater import ControlPoint
|
||||||
|
|
||||||
TYPE_TANKS = [
|
TYPE_TANKS = [
|
||||||
Armor.MBT_T_55,
|
Armor.MBT_T_55,
|
||||||
@ -207,8 +207,8 @@ GROUP_SIZES_BY_COMBAT_STANCE = {
|
|||||||
|
|
||||||
class CombatGroup:
|
class CombatGroup:
|
||||||
|
|
||||||
def __init__(self, role:CombatGroupRole):
|
def __init__(self, role: CombatGroupRole):
|
||||||
self.units = []
|
self.units: List[VehicleType] = []
|
||||||
self.role = role
|
self.role = role
|
||||||
self.assigned_enemy_cp = None
|
self.assigned_enemy_cp = None
|
||||||
self.start_position = None
|
self.start_position = None
|
||||||
@ -222,33 +222,22 @@ class CombatGroup:
|
|||||||
|
|
||||||
class GroundPlanner:
|
class GroundPlanner:
|
||||||
|
|
||||||
cp = None
|
|
||||||
combat_groups_dict = {}
|
|
||||||
connected_enemy_cp = []
|
|
||||||
|
|
||||||
tank_groups = []
|
|
||||||
apc_group = []
|
|
||||||
ifv_group = []
|
|
||||||
art_group = []
|
|
||||||
shorad_groups = []
|
|
||||||
logi_groups = []
|
|
||||||
|
|
||||||
def __init__(self, cp:ControlPoint, game):
|
def __init__(self, cp:ControlPoint, game):
|
||||||
self.cp = cp
|
self.cp = cp
|
||||||
self.game = game
|
self.game = game
|
||||||
self.connected_enemy_cp = [cp for cp in self.cp.connected_points if cp.captured != self.cp.captured]
|
self.connected_enemy_cp = [cp for cp in self.cp.connected_points if cp.captured != self.cp.captured]
|
||||||
self.tank_groups = []
|
self.tank_groups: List[CombatGroup] = []
|
||||||
self.apc_group = []
|
self.apc_group: List[CombatGroup] = []
|
||||||
self.ifv_group = []
|
self.ifv_group: List[CombatGroup] = []
|
||||||
self.art_group = []
|
self.art_group: List[CombatGroup] = []
|
||||||
self.atgm_group = []
|
self.atgm_group: List[CombatGroup] = []
|
||||||
self.logi_groups = []
|
self.logi_groups: List[CombatGroup] = []
|
||||||
self.shorad_groups = []
|
self.shorad_groups: List[CombatGroup] = []
|
||||||
|
|
||||||
self.units_per_cp = {}
|
self.units_per_cp: Dict[int, List[CombatGroup]] = {}
|
||||||
for cp in self.connected_enemy_cp:
|
for cp in self.connected_enemy_cp:
|
||||||
self.units_per_cp[cp.id] = []
|
self.units_per_cp[cp.id] = []
|
||||||
self.reserve = []
|
self.reserve: List[CombatGroup] = []
|
||||||
|
|
||||||
|
|
||||||
def plan_groundwar(self):
|
def plan_groundwar(self):
|
||||||
|
|||||||
@ -1,11 +1,23 @@
|
|||||||
from dcs.statics import *
|
import logging
|
||||||
from dcs.unit import Ship, Vehicle
|
import random
|
||||||
|
from typing import Dict, Iterator
|
||||||
|
|
||||||
from game.data.building_data import FORTIFICATION_UNITS_ID, FORTIFICATION_UNITS
|
from dcs import Mission
|
||||||
|
from dcs.statics import fortification_map, warehouse_map
|
||||||
|
from dcs.task import (
|
||||||
|
ActivateBeaconCommand,
|
||||||
|
ActivateICLSCommand,
|
||||||
|
EPLRS,
|
||||||
|
OptAlarmState,
|
||||||
|
)
|
||||||
|
from dcs.unit import Ship, Vehicle
|
||||||
|
from dcs.unitgroup import StaticGroup
|
||||||
|
|
||||||
|
from game import db
|
||||||
|
from game.data.building_data import FORTIFICATION_UNITS, FORTIFICATION_UNITS_ID
|
||||||
from game.db import unit_type_from_name
|
from game.db import unit_type_from_name
|
||||||
from .airfields import RunwayData
|
from .airfields import RunwayData
|
||||||
from .conflictgen import *
|
from .conflictgen import Conflict
|
||||||
from .naming import *
|
|
||||||
from .radios import RadioRegistry
|
from .radios import RadioRegistry
|
||||||
from .tacan import TacanBand, TacanRegistry
|
from .tacan import TacanBand, TacanRegistry
|
||||||
|
|
||||||
@ -26,7 +38,7 @@ class GroundObjectsGenerator:
|
|||||||
self.icls_alloc = iter(range(1, 21))
|
self.icls_alloc = iter(range(1, 21))
|
||||||
self.runways: Dict[str, RunwayData] = {}
|
self.runways: Dict[str, RunwayData] = {}
|
||||||
|
|
||||||
def generate_farps(self, number_of_units=1) -> typing.Collection[StaticGroup]:
|
def generate_farps(self, number_of_units=1) -> Iterator[StaticGroup]:
|
||||||
if self.conflict.is_vector:
|
if self.conflict.is_vector:
|
||||||
center = self.conflict.center
|
center = self.conflict.center
|
||||||
heading = self.conflict.heading - 90
|
heading = self.conflict.heading - 90
|
||||||
@ -80,6 +92,10 @@ class GroundObjectsGenerator:
|
|||||||
vehicle.heading = u.heading
|
vehicle.heading = u.heading
|
||||||
vehicle.player_can_drive = True
|
vehicle.player_can_drive = True
|
||||||
vg.add_unit(vehicle)
|
vg.add_unit(vehicle)
|
||||||
|
|
||||||
|
if hasattr(utype, 'eplrs'):
|
||||||
|
if utype.eplrs:
|
||||||
|
vg.points[0].tasks.append(EPLRS(vg.id))
|
||||||
else:
|
else:
|
||||||
vg = self.m.ship_group(side, g.name, utype, position=g.position,
|
vg = self.m.ship_group(side, g.name, utype, position=g.position,
|
||||||
heading=g.units[0].heading)
|
heading=g.units[0].heading)
|
||||||
|
|||||||
@ -82,6 +82,8 @@ class KneeboardPageWriter:
|
|||||||
|
|
||||||
def table(self, cells: List[List[str]],
|
def table(self, cells: List[List[str]],
|
||||||
headers: Optional[List[str]] = None) -> None:
|
headers: Optional[List[str]] = None) -> None:
|
||||||
|
if headers is None:
|
||||||
|
headers = []
|
||||||
table = tabulate(cells, headers=headers, numalign="right")
|
table = tabulate(cells, headers=headers, numalign="right")
|
||||||
self.text(table, font=self.table_font)
|
self.text(table, font=self.table_font)
|
||||||
|
|
||||||
@ -136,7 +138,7 @@ class FlightPlanBuilder:
|
|||||||
|
|
||||||
def add_waypoint_row(self, waypoint: NumberedWaypoint) -> None:
|
def add_waypoint_row(self, waypoint: NumberedWaypoint) -> None:
|
||||||
self.rows.append([
|
self.rows.append([
|
||||||
waypoint.number,
|
str(waypoint.number),
|
||||||
waypoint.waypoint.pretty_name,
|
waypoint.waypoint.pretty_name,
|
||||||
str(int(units.meters_to_feet(waypoint.waypoint.alt)))
|
str(int(units.meters_to_feet(waypoint.waypoint.alt)))
|
||||||
])
|
])
|
||||||
@ -194,7 +196,7 @@ class BriefingPage(KneeboardPage):
|
|||||||
tankers.append([
|
tankers.append([
|
||||||
tanker.callsign,
|
tanker.callsign,
|
||||||
tanker.variant,
|
tanker.variant,
|
||||||
tanker.tacan,
|
str(tanker.tacan),
|
||||||
self.format_frequency(tanker.freq),
|
self.format_frequency(tanker.freq),
|
||||||
])
|
])
|
||||||
writer.table(tankers, headers=["Callsign", "Type", "TACAN", "UHF"])
|
writer.table(tankers, headers=["Callsign", "Type", "TACAN", "UHF"])
|
||||||
@ -225,12 +227,22 @@ class BriefingPage(KneeboardPage):
|
|||||||
atc = ""
|
atc = ""
|
||||||
if runway.atc is not None:
|
if runway.atc is not None:
|
||||||
atc = self.format_frequency(runway.atc)
|
atc = self.format_frequency(runway.atc)
|
||||||
|
if runway.tacan is None:
|
||||||
|
tacan = ""
|
||||||
|
else:
|
||||||
|
tacan = str(runway.tacan)
|
||||||
|
if runway.ils is not None:
|
||||||
|
ils = str(runway.ils)
|
||||||
|
elif runway.icls is not None:
|
||||||
|
ils = str(runway.icls)
|
||||||
|
else:
|
||||||
|
ils = ""
|
||||||
return [
|
return [
|
||||||
row_title,
|
row_title,
|
||||||
runway.airfield_name,
|
runway.airfield_name,
|
||||||
atc,
|
atc,
|
||||||
runway.tacan or "",
|
tacan,
|
||||||
runway.ils or runway.icls or "",
|
ils,
|
||||||
runway.runway_name,
|
runway.runway_name,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -8,13 +8,13 @@ MISSILES_MAP = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def generate_missile_group(game, ground_object, faction:str):
|
def generate_missile_group(game, ground_object, faction_name: str):
|
||||||
"""
|
"""
|
||||||
This generate a ship group
|
This generate a ship group
|
||||||
:return: Nothing, but put the group reference inside the ground object
|
:return: Nothing, but put the group reference inside the ground object
|
||||||
"""
|
"""
|
||||||
faction = db.FACTIONS[faction]
|
faction = db.FACTIONS[faction_name]
|
||||||
if "missiles" in faction.keys():
|
if "missiles" in faction:
|
||||||
generators = faction["missiles"]
|
generators = faction["missiles"]
|
||||||
if len(generators) > 0:
|
if len(generators) > 0:
|
||||||
gen = random.choice(generators)
|
gen = random.choice(generators)
|
||||||
|
|||||||
@ -1,19 +1,12 @@
|
|||||||
import typing
|
from dcs.action import MarkToAll
|
||||||
import random
|
from dcs.condition import TimeAfter
|
||||||
from datetime import datetime, timedelta, time
|
|
||||||
|
|
||||||
from dcs.mission import Mission
|
from dcs.mission import Mission
|
||||||
from dcs.triggers import *
|
from dcs.task import Option
|
||||||
from dcs.condition import *
|
from dcs.translation import String
|
||||||
from dcs.action import *
|
from dcs.triggers import Event, TriggerOnce
|
||||||
from dcs.unit import Skill
|
from dcs.unit import Skill
|
||||||
from dcs.point import MovingPoint, PointProperties
|
|
||||||
from dcs.action import *
|
|
||||||
|
|
||||||
from game import db
|
from .conflictgen import Conflict
|
||||||
from theater import *
|
|
||||||
from gen.airsupportgen import AirSupportConflictGenerator
|
|
||||||
from gen import *
|
|
||||||
|
|
||||||
PUSH_TRIGGER_SIZE = 3000
|
PUSH_TRIGGER_SIZE = 3000
|
||||||
PUSH_TRIGGER_ACTIVATION_AGL = 25
|
PUSH_TRIGGER_ACTIVATION_AGL = 25
|
||||||
|
|||||||
@ -1,18 +1,20 @@
|
|||||||
import typing
|
from __future__ import annotations
|
||||||
|
|
||||||
import random
|
import random
|
||||||
from datetime import datetime, timedelta
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from dcs.mapping import Point
|
||||||
from dcs.mission import Mission
|
from dcs.mission import Mission
|
||||||
from dcs.statics import *
|
|
||||||
from dcs.unit import Static
|
from dcs.unit import Static
|
||||||
|
from dcs.unittype import StaticType
|
||||||
|
|
||||||
from theater import *
|
if TYPE_CHECKING:
|
||||||
from .conflictgen import *
|
from game import Game
|
||||||
#from game.game import Game
|
|
||||||
from game import db
|
from .conflictgen import Conflict, FRONTLINE_LENGTH
|
||||||
|
|
||||||
|
|
||||||
class MarkerSmoke(unittype.StaticType):
|
class MarkerSmoke(StaticType):
|
||||||
id = "big_smoke"
|
id = "big_smoke"
|
||||||
category = "Effects"
|
category = "Effects"
|
||||||
name = "big_smoke"
|
name = "big_smoke"
|
||||||
@ -20,7 +22,7 @@ class MarkerSmoke(unittype.StaticType):
|
|||||||
rate = 0.1
|
rate = 0.1
|
||||||
|
|
||||||
|
|
||||||
class Smoke(unittype.StaticType):
|
class Smoke(StaticType):
|
||||||
id = "big_smoke"
|
id = "big_smoke"
|
||||||
category = "Effects"
|
category = "Effects"
|
||||||
name = "big_smoke"
|
name = "big_smoke"
|
||||||
@ -28,7 +30,7 @@ class Smoke(unittype.StaticType):
|
|||||||
rate = 1
|
rate = 1
|
||||||
|
|
||||||
|
|
||||||
class BigSmoke(unittype.StaticType):
|
class BigSmoke(StaticType):
|
||||||
id = "big_smoke"
|
id = "big_smoke"
|
||||||
category = "Effects"
|
category = "Effects"
|
||||||
name = "big_smoke"
|
name = "big_smoke"
|
||||||
@ -36,7 +38,7 @@ class BigSmoke(unittype.StaticType):
|
|||||||
rate = 1
|
rate = 1
|
||||||
|
|
||||||
|
|
||||||
class MassiveSmoke(unittype.StaticType):
|
class MassiveSmoke(StaticType):
|
||||||
id = "big_smoke"
|
id = "big_smoke"
|
||||||
category = "Effects"
|
category = "Effects"
|
||||||
name = "big_smoke"
|
name = "big_smoke"
|
||||||
@ -44,7 +46,7 @@ class MassiveSmoke(unittype.StaticType):
|
|||||||
rate = 1
|
rate = 1
|
||||||
|
|
||||||
|
|
||||||
class Outpost(unittype.StaticType):
|
class Outpost(StaticType):
|
||||||
id = "outpost"
|
id = "outpost"
|
||||||
name = "outpost"
|
name = "outpost"
|
||||||
category = "Fortifications"
|
category = "Fortifications"
|
||||||
@ -90,9 +92,7 @@ def turn_heading(heading, fac):
|
|||||||
|
|
||||||
|
|
||||||
class VisualGenerator:
|
class VisualGenerator:
|
||||||
game = None # type: Game
|
def __init__(self, mission: Mission, conflict: Conflict, game: Game):
|
||||||
|
|
||||||
def __init__(self, mission: Mission, conflict: Conflict, game):
|
|
||||||
self.mission = mission
|
self.mission = mission
|
||||||
self.conflict = conflict
|
self.conflict = conflict
|
||||||
self.game = game
|
self.game = game
|
||||||
|
|||||||
12
mypy.ini
Normal file
12
mypy.ini
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[mypy]
|
||||||
|
namespace_packages = True
|
||||||
|
|
||||||
|
[mypy-dcs.*]
|
||||||
|
follow_imports=silent
|
||||||
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
[mypy-PIL.*]
|
||||||
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
[mypy-winreg.*]
|
||||||
|
ignore_missing_imports = True
|
||||||
2
pydcs
2
pydcs
@ -1 +1 @@
|
|||||||
Subproject commit ceea62a8e0731c21b3e1a3e90682aa0affc168f1
|
Subproject commit c203e5a1b8d5eb42d559dab074e668bf37fa5158
|
||||||
@ -25,7 +25,7 @@ class ERC_90(unittype.VehicleType):
|
|||||||
detection_range = 0
|
detection_range = 0
|
||||||
threat_range = 4000
|
threat_range = 4000
|
||||||
air_weapon_dist = 4000
|
air_weapon_dist = 4000
|
||||||
eprls = True
|
eplrs = True
|
||||||
|
|
||||||
|
|
||||||
class VAB__50(unittype.VehicleType):
|
class VAB__50(unittype.VehicleType):
|
||||||
@ -34,7 +34,7 @@ class VAB__50(unittype.VehicleType):
|
|||||||
detection_range = 0
|
detection_range = 0
|
||||||
threat_range = 1200
|
threat_range = 1200
|
||||||
air_weapon_dist = 1200
|
air_weapon_dist = 1200
|
||||||
eprls = True
|
eplrs = True
|
||||||
|
|
||||||
|
|
||||||
class VAB_T20_13(unittype.VehicleType):
|
class VAB_T20_13(unittype.VehicleType):
|
||||||
@ -43,7 +43,7 @@ class VAB_T20_13(unittype.VehicleType):
|
|||||||
detection_range = 0
|
detection_range = 0
|
||||||
threat_range = 2000
|
threat_range = 2000
|
||||||
air_weapon_dist = 2000
|
air_weapon_dist = 2000
|
||||||
eprls = True
|
eplrs = True
|
||||||
|
|
||||||
|
|
||||||
class VAB_MEPHISTO(unittype.VehicleType):
|
class VAB_MEPHISTO(unittype.VehicleType):
|
||||||
@ -52,7 +52,7 @@ class VAB_MEPHISTO(unittype.VehicleType):
|
|||||||
detection_range = 0
|
detection_range = 0
|
||||||
threat_range = 4000
|
threat_range = 4000
|
||||||
air_weapon_dist = 4000
|
air_weapon_dist = 4000
|
||||||
eprls = True
|
eplrs = True
|
||||||
|
|
||||||
|
|
||||||
class VBL__50(unittype.VehicleType):
|
class VBL__50(unittype.VehicleType):
|
||||||
@ -61,7 +61,7 @@ class VBL__50(unittype.VehicleType):
|
|||||||
detection_range = 0
|
detection_range = 0
|
||||||
threat_range = 1200
|
threat_range = 1200
|
||||||
air_weapon_dist = 1200
|
air_weapon_dist = 1200
|
||||||
eprls = True
|
eplrs = True
|
||||||
|
|
||||||
|
|
||||||
class VBL_AANF1(unittype.VehicleType):
|
class VBL_AANF1(unittype.VehicleType):
|
||||||
@ -70,7 +70,7 @@ class VBL_AANF1(unittype.VehicleType):
|
|||||||
detection_range = 0
|
detection_range = 0
|
||||||
threat_range = 1000
|
threat_range = 1000
|
||||||
air_weapon_dist = 1000
|
air_weapon_dist = 1000
|
||||||
eprls = True
|
eplrs = True
|
||||||
|
|
||||||
|
|
||||||
class VBAE_CRAB(unittype.VehicleType):
|
class VBAE_CRAB(unittype.VehicleType):
|
||||||
@ -79,7 +79,7 @@ class VBAE_CRAB(unittype.VehicleType):
|
|||||||
detection_range = 0
|
detection_range = 0
|
||||||
threat_range = 3500
|
threat_range = 3500
|
||||||
air_weapon_dist = 3500
|
air_weapon_dist = 3500
|
||||||
eprls = True
|
eplrs = True
|
||||||
|
|
||||||
|
|
||||||
class VBAE_CRAB_MMP(unittype.VehicleType):
|
class VBAE_CRAB_MMP(unittype.VehicleType):
|
||||||
@ -88,7 +88,7 @@ class VBAE_CRAB_MMP(unittype.VehicleType):
|
|||||||
detection_range = 0
|
detection_range = 0
|
||||||
threat_range = 3500
|
threat_range = 3500
|
||||||
air_weapon_dist = 3500
|
air_weapon_dist = 3500
|
||||||
eprls = True
|
eplrs = True
|
||||||
|
|
||||||
|
|
||||||
class AMX_30B2(unittype.VehicleType):
|
class AMX_30B2(unittype.VehicleType):
|
||||||
@ -121,7 +121,7 @@ class DIM__TOYOTA_BLUE(unittype.VehicleType):
|
|||||||
detection_range = 0
|
detection_range = 0
|
||||||
threat_range = 1200
|
threat_range = 1200
|
||||||
air_weapon_dist = 1200
|
air_weapon_dist = 1200
|
||||||
eprls = True
|
eplrs = True
|
||||||
|
|
||||||
|
|
||||||
class DIM__TOYOTA_GREEN(unittype.VehicleType):
|
class DIM__TOYOTA_GREEN(unittype.VehicleType):
|
||||||
@ -130,7 +130,7 @@ class DIM__TOYOTA_GREEN(unittype.VehicleType):
|
|||||||
detection_range = 0
|
detection_range = 0
|
||||||
threat_range = 1200
|
threat_range = 1200
|
||||||
air_weapon_dist = 1200
|
air_weapon_dist = 1200
|
||||||
eprls = True
|
eplrs = True
|
||||||
|
|
||||||
|
|
||||||
class DIM__TOYOTA_DESERT(unittype.VehicleType):
|
class DIM__TOYOTA_DESERT(unittype.VehicleType):
|
||||||
@ -139,7 +139,7 @@ class DIM__TOYOTA_DESERT(unittype.VehicleType):
|
|||||||
detection_range = 0
|
detection_range = 0
|
||||||
threat_range = 1200
|
threat_range = 1200
|
||||||
air_weapon_dist = 1200
|
air_weapon_dist = 1200
|
||||||
eprls = True
|
eplrs = True
|
||||||
|
|
||||||
|
|
||||||
class DIM__KAMIKAZE(unittype.VehicleType):
|
class DIM__KAMIKAZE(unittype.VehicleType):
|
||||||
@ -148,7 +148,7 @@ class DIM__KAMIKAZE(unittype.VehicleType):
|
|||||||
detection_range = 0
|
detection_range = 0
|
||||||
threat_range = 50
|
threat_range = 50
|
||||||
air_weapon_dist = 50
|
air_weapon_dist = 50
|
||||||
eprls = True
|
eplrs = True
|
||||||
|
|
||||||
## FORTIFICATION
|
## FORTIFICATION
|
||||||
|
|
||||||
@ -187,7 +187,7 @@ class TRM_2000(unittype.VehicleType):
|
|||||||
detection_range = 3500
|
detection_range = 3500
|
||||||
threat_range = 0
|
threat_range = 0
|
||||||
air_weapon_dist = 0
|
air_weapon_dist = 0
|
||||||
eprls = True
|
eplrs = True
|
||||||
|
|
||||||
class TRM_2000_Fuel(unittype.VehicleType):
|
class TRM_2000_Fuel(unittype.VehicleType):
|
||||||
id = "TRM2000_Citerne"
|
id = "TRM2000_Citerne"
|
||||||
@ -195,7 +195,7 @@ class TRM_2000_Fuel(unittype.VehicleType):
|
|||||||
detection_range = 3500
|
detection_range = 3500
|
||||||
threat_range = 0
|
threat_range = 0
|
||||||
air_weapon_dist = 0
|
air_weapon_dist = 0
|
||||||
eprls = True
|
eplrs = True
|
||||||
|
|
||||||
class VAB_MEDICAL(unittype.VehicleType):
|
class VAB_MEDICAL(unittype.VehicleType):
|
||||||
id = "VABH"
|
id = "VABH"
|
||||||
@ -203,7 +203,7 @@ class VAB_MEDICAL(unittype.VehicleType):
|
|||||||
detection_range = 0
|
detection_range = 0
|
||||||
threat_range = 0
|
threat_range = 0
|
||||||
air_weapon_dist = 0
|
air_weapon_dist = 0
|
||||||
eprls = True
|
eplrs = True
|
||||||
|
|
||||||
class VAB(unittype.VehicleType):
|
class VAB(unittype.VehicleType):
|
||||||
id = "VAB_RADIO"
|
id = "VAB_RADIO"
|
||||||
@ -211,7 +211,7 @@ class VAB(unittype.VehicleType):
|
|||||||
detection_range = 0
|
detection_range = 0
|
||||||
threat_range = 0
|
threat_range = 0
|
||||||
air_weapon_dist = 0
|
air_weapon_dist = 0
|
||||||
eprls = True
|
eplrs = True
|
||||||
|
|
||||||
class VBL(unittype.VehicleType):
|
class VBL(unittype.VehicleType):
|
||||||
id = "VBL-Radio"
|
id = "VBL-Radio"
|
||||||
@ -219,7 +219,7 @@ class VBL(unittype.VehicleType):
|
|||||||
detection_range = 0
|
detection_range = 0
|
||||||
threat_range = 0
|
threat_range = 0
|
||||||
air_weapon_dist = 0
|
air_weapon_dist = 0
|
||||||
eprls = True
|
eplrs = True
|
||||||
|
|
||||||
class Tracma_TD_1500(unittype.VehicleType):
|
class Tracma_TD_1500(unittype.VehicleType):
|
||||||
id = "Tracma"
|
id = "Tracma"
|
||||||
@ -236,7 +236,7 @@ class SMOKE_SAM_IR(unittype.VehicleType):
|
|||||||
detection_range = 20000
|
detection_range = 20000
|
||||||
threat_range = 20000
|
threat_range = 20000
|
||||||
air_weapon_dist = 20000
|
air_weapon_dist = 20000
|
||||||
eprls = True
|
eplrs = True
|
||||||
|
|
||||||
class _53T2(unittype.VehicleType):
|
class _53T2(unittype.VehicleType):
|
||||||
id = "AA20"
|
id = "AA20"
|
||||||
@ -251,7 +251,7 @@ class TRM_2000_53T2(unittype.VehicleType):
|
|||||||
detection_range = 6000
|
detection_range = 6000
|
||||||
threat_range = 2000
|
threat_range = 2000
|
||||||
air_weapon_dist = 2000
|
air_weapon_dist = 2000
|
||||||
eprls = True
|
eplrs = True
|
||||||
|
|
||||||
class TRM_2000_PAMELA(unittype.VehicleType):
|
class TRM_2000_PAMELA(unittype.VehicleType):
|
||||||
id = "TRMMISTRAL"
|
id = "TRMMISTRAL"
|
||||||
@ -259,7 +259,7 @@ class TRM_2000_PAMELA(unittype.VehicleType):
|
|||||||
detection_range = 8000
|
detection_range = 8000
|
||||||
threat_range = 10000
|
threat_range = 10000
|
||||||
air_weapon_dist = 10000
|
air_weapon_dist = 10000
|
||||||
eprls = True
|
eplrs = True
|
||||||
|
|
||||||
## INFANTRY
|
## INFANTRY
|
||||||
|
|
||||||
@ -285,4 +285,4 @@ class VAB_MORTIER(unittype.VehicleType):
|
|||||||
detection_range = 0
|
detection_range = 0
|
||||||
threat_range = 15000
|
threat_range = 15000
|
||||||
air_weapon_dist = 15000
|
air_weapon_dist = 15000
|
||||||
eprls = True
|
eplrs = True
|
||||||
65
qt_ui/dialogs.py
Normal file
65
qt_ui/dialogs.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
"""Application-wide dialog management."""
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from gen.flights.flight import Flight
|
||||||
|
from theater.missiontarget import MissionTarget
|
||||||
|
from .models import GameModel, PackageModel
|
||||||
|
from .windows.mission.QEditFlightDialog import QEditFlightDialog
|
||||||
|
from .windows.mission.QPackageDialog import (
|
||||||
|
QEditPackageDialog,
|
||||||
|
QNewPackageDialog,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Dialog:
|
||||||
|
"""Dialog management singleton.
|
||||||
|
|
||||||
|
Opens dialogs and keeps references to dialog windows so that their creators
|
||||||
|
do not need to worry about the lifetime of the dialog object, and can open
|
||||||
|
dialogs without needing to have their own reference to common data like the
|
||||||
|
game model.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: The game model. Is only None before initialization, as the game model
|
||||||
|
#: itself is responsible for handling the case where no game is loaded.
|
||||||
|
game_model: Optional[GameModel] = None
|
||||||
|
|
||||||
|
new_package_dialog: Optional[QNewPackageDialog] = None
|
||||||
|
edit_package_dialog: Optional[QEditPackageDialog] = None
|
||||||
|
edit_flight_dialog: Optional[QEditFlightDialog] = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def set_game(cls, game_model: GameModel) -> None:
|
||||||
|
"""Sets the game model."""
|
||||||
|
cls.game_model = game_model
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def open_new_package_dialog(cls, mission_target: MissionTarget):
|
||||||
|
"""Opens the dialog to create a new package with the given target."""
|
||||||
|
cls.new_package_dialog = QNewPackageDialog(
|
||||||
|
cls.game_model.game,
|
||||||
|
cls.game_model.ato_model,
|
||||||
|
mission_target
|
||||||
|
)
|
||||||
|
cls.new_package_dialog.show()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def open_edit_package_dialog(cls, package_model: PackageModel):
|
||||||
|
"""Opens the dialog to edit the given package."""
|
||||||
|
cls.edit_package_dialog = QEditPackageDialog(
|
||||||
|
cls.game_model.game,
|
||||||
|
cls.game_model.ato_model,
|
||||||
|
package_model
|
||||||
|
)
|
||||||
|
cls.edit_package_dialog.show()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def open_edit_flight_dialog(cls, package_model: PackageModel,
|
||||||
|
flight: Flight) -> None:
|
||||||
|
"""Opens the dialog to edit the given flight."""
|
||||||
|
cls.edit_flight_dialog = QEditFlightDialog(
|
||||||
|
cls.game_model.game,
|
||||||
|
package_model.package,
|
||||||
|
flight
|
||||||
|
)
|
||||||
|
cls.edit_flight_dialog.show()
|
||||||
@ -4,13 +4,14 @@ from shutil import copyfile
|
|||||||
|
|
||||||
import dcs
|
import dcs
|
||||||
|
|
||||||
from userdata import persistency
|
from game import persistency
|
||||||
|
|
||||||
global __dcs_saved_game_directory
|
global __dcs_saved_game_directory
|
||||||
global __dcs_installation_directory
|
global __dcs_installation_directory
|
||||||
|
|
||||||
PREFERENCES_FILE_PATH = "liberation_preferences.json"
|
PREFERENCES_FILE_PATH = "liberation_preferences.json"
|
||||||
|
|
||||||
|
|
||||||
def init():
|
def init():
|
||||||
global __dcs_saved_game_directory
|
global __dcs_saved_game_directory
|
||||||
global __dcs_installation_directory
|
global __dcs_installation_directory
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from typing import Dict
|
||||||
import qt_ui.uiconstants as CONST
|
|
||||||
|
|
||||||
global __theme_index
|
global __theme_index
|
||||||
|
|
||||||
@ -10,29 +10,44 @@ THEME_PREFERENCES_FILE_PATH = "liberation_theme.json"
|
|||||||
DEFAULT_THEME_INDEX = 1
|
DEFAULT_THEME_INDEX = 1
|
||||||
|
|
||||||
|
|
||||||
|
# new themes can be added here
|
||||||
|
THEMES: Dict[int, Dict[str, str]] = {
|
||||||
|
0: {'themeName': 'Vanilla',
|
||||||
|
'themeFile': 'windows-style.css',
|
||||||
|
'themeIcons': 'medium',
|
||||||
|
},
|
||||||
|
|
||||||
|
1: {'themeName': 'DCS World',
|
||||||
|
'themeFile': 'style-dcs.css',
|
||||||
|
'themeIcons': 'light',
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def init():
|
def init():
|
||||||
global __theme_index
|
global __theme_index
|
||||||
|
|
||||||
__theme_index = DEFAULT_THEME_INDEX
|
__theme_index = DEFAULT_THEME_INDEX
|
||||||
print("init setting theme index to " + str(__theme_index))
|
|
||||||
|
|
||||||
if os.path.isfile(THEME_PREFERENCES_FILE_PATH):
|
if os.path.isfile(THEME_PREFERENCES_FILE_PATH):
|
||||||
try:
|
try:
|
||||||
with(open(THEME_PREFERENCES_FILE_PATH)) as prefs:
|
with(open(THEME_PREFERENCES_FILE_PATH)) as prefs:
|
||||||
pref_data = json.loads(prefs.read())
|
pref_data = json.loads(prefs.read())
|
||||||
__theme_index = pref_data["theme_index"]
|
__theme_index = pref_data["theme_index"]
|
||||||
print(__theme_index)
|
|
||||||
set_theme_index(__theme_index)
|
set_theme_index(__theme_index)
|
||||||
save_theme_config()
|
save_theme_config()
|
||||||
print("file setting theme index to " + str(__theme_index))
|
|
||||||
except:
|
except:
|
||||||
# is this necessary?
|
# is this necessary?
|
||||||
set_theme_index(DEFAULT_THEME_INDEX)
|
set_theme_index(DEFAULT_THEME_INDEX)
|
||||||
print("except setting theme index to " + str(__theme_index))
|
logging.exception("Unable to change theme")
|
||||||
else:
|
else:
|
||||||
# is this necessary?
|
# is this necessary?
|
||||||
set_theme_index(DEFAULT_THEME_INDEX)
|
set_theme_index(DEFAULT_THEME_INDEX)
|
||||||
print("else setting theme index to " + str(__theme_index))
|
logging.error(
|
||||||
|
f"Using default theme because {THEME_PREFERENCES_FILE_PATH} "
|
||||||
|
"does not exist"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# set theme index then use save_theme_config to save to file
|
# set theme index then use save_theme_config to save to file
|
||||||
@ -49,19 +64,19 @@ def get_theme_index():
|
|||||||
|
|
||||||
# get theme name based on current index
|
# get theme name based on current index
|
||||||
def get_theme_name():
|
def get_theme_name():
|
||||||
theme_name = CONST.THEMES[get_theme_index()]['themeName']
|
theme_name = THEMES[get_theme_index()]['themeName']
|
||||||
return theme_name
|
return theme_name
|
||||||
|
|
||||||
|
|
||||||
# get theme icon sub-folder name based on current index
|
# get theme icon sub-folder name based on current index
|
||||||
def get_theme_icons():
|
def get_theme_icons():
|
||||||
theme_icons = CONST.THEMES[get_theme_index()]['themeIcons']
|
theme_icons = THEMES[get_theme_index()]['themeIcons']
|
||||||
return str(theme_icons)
|
return str(theme_icons)
|
||||||
|
|
||||||
|
|
||||||
# get theme stylesheet css based on current index
|
# get theme stylesheet css based on current index
|
||||||
def get_theme_css_file():
|
def get_theme_css_file():
|
||||||
theme_file = CONST.THEMES[get_theme_index()]['themeFile']
|
theme_file = THEMES[get_theme_index()]['themeFile']
|
||||||
return str(theme_file)
|
return str(theme_file)
|
||||||
|
|
||||||
|
|
||||||
22
qt_ui/logging_config.py
Normal file
22
qt_ui/logging_config.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
"""Logging APIs."""
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
|
||||||
|
|
||||||
|
def init_logging(version: str) -> None:
|
||||||
|
"""Initializes the logging configuration."""
|
||||||
|
if not os.path.isdir("./logs"):
|
||||||
|
os.mkdir("logs")
|
||||||
|
|
||||||
|
fmt = "%(asctime)s :: %(levelname)s :: %(message)s"
|
||||||
|
logging.basicConfig(level=logging.DEBUG, format=fmt)
|
||||||
|
logger = logging.getLogger()
|
||||||
|
|
||||||
|
handler = RotatingFileHandler('./logs/liberation.log', 'a', 5000000, 1)
|
||||||
|
handler.setLevel(logging.INFO)
|
||||||
|
handler.setFormatter(logging.Formatter(fmt))
|
||||||
|
|
||||||
|
logger.addHandler(handler)
|
||||||
|
|
||||||
|
logger.info(f"DCS Liberation {version}")
|
||||||
@ -1,5 +1,3 @@
|
|||||||
from userdata import logging_config
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@ -9,11 +7,17 @@ from PySide2 import QtWidgets
|
|||||||
from PySide2.QtGui import QPixmap
|
from PySide2.QtGui import QPixmap
|
||||||
from PySide2.QtWidgets import QApplication, QSplashScreen
|
from PySide2.QtWidgets import QApplication, QSplashScreen
|
||||||
|
|
||||||
from qt_ui import uiconstants
|
from game import persistency
|
||||||
|
from qt_ui import (
|
||||||
|
liberation_install,
|
||||||
|
liberation_theme,
|
||||||
|
logging_config,
|
||||||
|
uiconstants,
|
||||||
|
)
|
||||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||||
from qt_ui.windows.QLiberationWindow import QLiberationWindow
|
from qt_ui.windows.QLiberationWindow import QLiberationWindow
|
||||||
from qt_ui.windows.preferences.QLiberationFirstStartWindow import QLiberationFirstStartWindow
|
from qt_ui.windows.preferences.QLiberationFirstStartWindow import \
|
||||||
from userdata import liberation_install, persistency, liberation_theme
|
QLiberationFirstStartWindow
|
||||||
|
|
||||||
# Logging setup
|
# Logging setup
|
||||||
logging_config.init_logging(uiconstants.VERSION_STRING)
|
logging_config.init_logging(uiconstants.VERSION_STRING)
|
||||||
|
|||||||
268
qt_ui/models.py
Normal file
268
qt_ui/models.py
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
"""Qt data models for game objects."""
|
||||||
|
from typing import Any, Callable, Dict, Iterator, TypeVar, Optional
|
||||||
|
|
||||||
|
from PySide2.QtCore import (
|
||||||
|
QAbstractListModel,
|
||||||
|
QModelIndex,
|
||||||
|
Qt,
|
||||||
|
Signal,
|
||||||
|
)
|
||||||
|
from PySide2.QtGui import QIcon
|
||||||
|
|
||||||
|
from game import db
|
||||||
|
from game.game import Game
|
||||||
|
from gen.ato import AirTaskingOrder, Package
|
||||||
|
from gen.flights.flight import Flight
|
||||||
|
from qt_ui.uiconstants import AIRCRAFT_ICONS
|
||||||
|
from theater.missiontarget import MissionTarget
|
||||||
|
|
||||||
|
|
||||||
|
class DeletableChildModelManager:
|
||||||
|
"""Manages lifetimes for child models.
|
||||||
|
|
||||||
|
Qt's data models don't have a good way of modeling related data aside from
|
||||||
|
lists, tables, or trees of similar objects. We could build one monolithic
|
||||||
|
GameModel that tracks all of the data in the game and use the parent/child
|
||||||
|
relationships of that model to index down into the ATO, packages, flights,
|
||||||
|
etc, but doing so is error prone because it requires us to manually manage
|
||||||
|
that relationship tree and keep our own mappings from row/column into
|
||||||
|
specific members.
|
||||||
|
|
||||||
|
However, creating child models outside of the tree means that removing an
|
||||||
|
item from the parent will not signal the child's deletion to any views, so
|
||||||
|
we must track this explicitly.
|
||||||
|
|
||||||
|
Any model which has child data types should use this class to track the
|
||||||
|
deletion of child models. All child model types must define a signal named
|
||||||
|
`deleted`. This signal will be emitted when the child model is being
|
||||||
|
deleted. Any views displaying such data should subscribe to those events and
|
||||||
|
update their display accordingly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: The type of data owned by models created by this class.
|
||||||
|
DataType = TypeVar("DataType")
|
||||||
|
|
||||||
|
#: The type of model managed by this class.
|
||||||
|
ModelType = TypeVar("ModelType")
|
||||||
|
|
||||||
|
ModelDict = Dict[DataType, ModelType]
|
||||||
|
|
||||||
|
def __init__(self, create_model: Callable[[DataType], ModelType]) -> None:
|
||||||
|
self.create_model = create_model
|
||||||
|
self.models: DeletableChildModelManager.ModelDict = {}
|
||||||
|
|
||||||
|
def acquire(self, data: DataType) -> ModelType:
|
||||||
|
"""Returns a model for the given child data.
|
||||||
|
|
||||||
|
If a model has already been created for the given data, it will be
|
||||||
|
returned. The data type must be hashable.
|
||||||
|
"""
|
||||||
|
if data in self.models:
|
||||||
|
return self.models[data]
|
||||||
|
model = self.create_model(data)
|
||||||
|
self.models[data] = model
|
||||||
|
return model
|
||||||
|
|
||||||
|
def release(self, data: DataType) -> None:
|
||||||
|
"""Releases the model matching the given data, if one exists.
|
||||||
|
|
||||||
|
If the given data has had a model created for it, that model will be
|
||||||
|
deleted and its `deleted` signal will be emitted.
|
||||||
|
"""
|
||||||
|
if data in self.models:
|
||||||
|
model = self.models[data]
|
||||||
|
del self.models[data]
|
||||||
|
model.deleted.emit()
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
"""Deletes all managed models."""
|
||||||
|
for data in list(self.models.keys()):
|
||||||
|
self.release(data)
|
||||||
|
|
||||||
|
|
||||||
|
class NullListModel(QAbstractListModel):
|
||||||
|
"""Generic empty list model."""
|
||||||
|
|
||||||
|
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> Any:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class PackageModel(QAbstractListModel):
|
||||||
|
"""The model for an ATO package."""
|
||||||
|
|
||||||
|
#: Emitted when this package is being deleted from the ATO.
|
||||||
|
deleted = Signal()
|
||||||
|
|
||||||
|
def __init__(self, package: Package) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.package = package
|
||||||
|
|
||||||
|
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
|
||||||
|
return len(self.package.flights)
|
||||||
|
|
||||||
|
def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> Any:
|
||||||
|
if not index.isValid():
|
||||||
|
return None
|
||||||
|
flight = self.flight_at_index(index)
|
||||||
|
if role == Qt.DisplayRole:
|
||||||
|
return self.text_for_flight(flight)
|
||||||
|
if role == Qt.DecorationRole:
|
||||||
|
return self.icon_for_flight(flight)
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def text_for_flight(flight: Flight) -> str:
|
||||||
|
"""Returns the text that should be displayed for the flight."""
|
||||||
|
task = flight.flight_type.name
|
||||||
|
count = flight.count
|
||||||
|
name = db.unit_type_name(flight.unit_type)
|
||||||
|
delay = flight.scheduled_in
|
||||||
|
origin = flight.from_cp.name
|
||||||
|
return f"[{task}] {count} x {name} from {origin} in {delay} minutes"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def icon_for_flight(flight: Flight) -> Optional[QIcon]:
|
||||||
|
"""Returns the icon that should be displayed for the flight."""
|
||||||
|
name = db.unit_type_name(flight.unit_type)
|
||||||
|
if name in AIRCRAFT_ICONS:
|
||||||
|
return QIcon(AIRCRAFT_ICONS[name])
|
||||||
|
return None
|
||||||
|
|
||||||
|
def add_flight(self, flight: Flight) -> None:
|
||||||
|
"""Adds the given flight to the package."""
|
||||||
|
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
|
||||||
|
self.package.add_flight(flight)
|
||||||
|
self.endInsertRows()
|
||||||
|
|
||||||
|
def delete_flight_at_index(self, index: QModelIndex) -> None:
|
||||||
|
"""Removes the flight at the given index from the package."""
|
||||||
|
self.delete_flight(self.flight_at_index(index))
|
||||||
|
|
||||||
|
def delete_flight(self, flight: Flight) -> None:
|
||||||
|
"""Removes the given flight from the package.
|
||||||
|
|
||||||
|
If the flight is using claimed inventory, the caller is responsible for
|
||||||
|
returning that inventory.
|
||||||
|
"""
|
||||||
|
index = self.package.flights.index(flight)
|
||||||
|
self.beginRemoveRows(QModelIndex(), index, index)
|
||||||
|
self.package.remove_flight(flight)
|
||||||
|
self.endRemoveRows()
|
||||||
|
|
||||||
|
def flight_at_index(self, index: QModelIndex) -> Flight:
|
||||||
|
"""Returns the flight located at the given index."""
|
||||||
|
return self.package.flights[index.row()]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mission_target(self) -> MissionTarget:
|
||||||
|
"""Returns the mission target of the package."""
|
||||||
|
package = self.package
|
||||||
|
target = package.target
|
||||||
|
return target
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
"""Returns the description of the package."""
|
||||||
|
return self.package.package_description
|
||||||
|
|
||||||
|
@property
|
||||||
|
def flights(self) -> Iterator[Flight]:
|
||||||
|
"""Iterates over the flights in the package."""
|
||||||
|
for flight in self.package.flights:
|
||||||
|
yield flight
|
||||||
|
|
||||||
|
|
||||||
|
class AtoModel(QAbstractListModel):
|
||||||
|
"""The model for an AirTaskingOrder."""
|
||||||
|
|
||||||
|
def __init__(self, game: Optional[Game], ato: AirTaskingOrder) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.game = game
|
||||||
|
self.ato = ato
|
||||||
|
self.package_models = DeletableChildModelManager(PackageModel)
|
||||||
|
|
||||||
|
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
|
||||||
|
return len(self.ato.packages)
|
||||||
|
|
||||||
|
def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> Any:
|
||||||
|
if not index.isValid():
|
||||||
|
return None
|
||||||
|
if role == Qt.DisplayRole:
|
||||||
|
package = self.ato.packages[index.row()]
|
||||||
|
return f"{package.package_description} {package.target.name}"
|
||||||
|
return None
|
||||||
|
|
||||||
|
def add_package(self, package: Package) -> None:
|
||||||
|
"""Adds a package to the ATO."""
|
||||||
|
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
|
||||||
|
self.ato.add_package(package)
|
||||||
|
self.endInsertRows()
|
||||||
|
|
||||||
|
def delete_package_at_index(self, index: QModelIndex) -> None:
|
||||||
|
"""Removes the package at the given index from the ATO."""
|
||||||
|
self.delete_package(self.package_at_index(index))
|
||||||
|
|
||||||
|
def delete_package(self, package: Package) -> None:
|
||||||
|
"""Removes the given package from the ATO."""
|
||||||
|
self.package_models.release(package)
|
||||||
|
index = self.ato.packages.index(package)
|
||||||
|
self.beginRemoveRows(QModelIndex(), index, index)
|
||||||
|
self.ato.remove_package(package)
|
||||||
|
for flight in package.flights:
|
||||||
|
self.game.aircraft_inventory.return_from_flight(flight)
|
||||||
|
self.endRemoveRows()
|
||||||
|
|
||||||
|
def package_at_index(self, index: QModelIndex) -> Package:
|
||||||
|
"""Returns the package at the given index."""
|
||||||
|
return self.ato.packages[index.row()]
|
||||||
|
|
||||||
|
def replace_from_game(self, game: Optional[Game]) -> None:
|
||||||
|
"""Updates the ATO object to match the updated game object.
|
||||||
|
|
||||||
|
If the game is None (as is the case when no game has been loaded), an
|
||||||
|
empty ATO will be used.
|
||||||
|
"""
|
||||||
|
self.beginResetModel()
|
||||||
|
self.game = game
|
||||||
|
self.package_models.clear()
|
||||||
|
if self.game is not None:
|
||||||
|
self.ato = game.blue_ato
|
||||||
|
else:
|
||||||
|
self.ato = AirTaskingOrder()
|
||||||
|
self.endResetModel()
|
||||||
|
|
||||||
|
def get_package_model(self, index: QModelIndex) -> PackageModel:
|
||||||
|
"""Returns a model for the package at the given index."""
|
||||||
|
return self.package_models.acquire(self.package_at_index(index))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def packages(self) -> Iterator[PackageModel]:
|
||||||
|
"""Iterates over all the packages in the ATO."""
|
||||||
|
for package in self.ato.packages:
|
||||||
|
yield self.package_models.acquire(package)
|
||||||
|
|
||||||
|
|
||||||
|
class GameModel:
|
||||||
|
"""A model for the Game object.
|
||||||
|
|
||||||
|
This isn't a real Qt data model, but simplifies management of the game and
|
||||||
|
its ATO objects.
|
||||||
|
"""
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.game: Optional[Game] = None
|
||||||
|
# TODO: Add red ATO model, add cheat option to show red flight plan.
|
||||||
|
self.ato_model = AtoModel(self.game, AirTaskingOrder())
|
||||||
|
|
||||||
|
def set(self, game: Optional[Game]) -> None:
|
||||||
|
"""Updates the managed Game object.
|
||||||
|
|
||||||
|
The argument will be None when no game has been loaded. In this state,
|
||||||
|
much of the UI is still visible and needs to handle that behavior. To
|
||||||
|
simplify that case, the AtoModel will model an empty ATO when no game is
|
||||||
|
loaded.
|
||||||
|
"""
|
||||||
|
self.game = game
|
||||||
|
self.ato_model.replace_from_game(self.game)
|
||||||
@ -1,14 +1,12 @@
|
|||||||
# URL for UI links
|
|
||||||
import os
|
import os
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from PySide2.QtGui import QColor, QFont, QPixmap
|
from PySide2.QtGui import QColor, QFont, QPixmap
|
||||||
|
|
||||||
from game.event import UnitsDeliveryEvent, FrontlineAttackEvent
|
|
||||||
from theater.theatergroundobject import CATEGORY_MAP
|
from theater.theatergroundobject import CATEGORY_MAP
|
||||||
from userdata.liberation_theme import get_theme_icons
|
from .liberation_theme import get_theme_icons
|
||||||
|
|
||||||
VERSION_STRING = "2.1.3"
|
VERSION_STRING = "2.1.4"
|
||||||
|
|
||||||
URLS : Dict[str, str] = {
|
URLS : Dict[str, str] = {
|
||||||
"Manual": "https://github.com/khopa/dcs_liberation/wiki",
|
"Manual": "https://github.com/khopa/dcs_liberation/wiki",
|
||||||
@ -28,20 +26,6 @@ FONT_PRIMARY_I = QFont(FONT_NAME, FONT_SIZE, weight=5, italic=True)
|
|||||||
FONT_PRIMARY_B = QFont(FONT_NAME, FONT_SIZE, weight=75, italic=False)
|
FONT_PRIMARY_B = QFont(FONT_NAME, FONT_SIZE, weight=75, italic=False)
|
||||||
FONT_MAP = QFont(FONT_NAME, 10, weight=75, italic=False)
|
FONT_MAP = QFont(FONT_NAME, 10, weight=75, italic=False)
|
||||||
|
|
||||||
# new themes can be added here
|
|
||||||
THEMES: Dict[int, Dict[str, str]] = {
|
|
||||||
0: {'themeName': 'Vanilla',
|
|
||||||
'themeFile': 'windows-style.css',
|
|
||||||
'themeIcons': 'medium',
|
|
||||||
},
|
|
||||||
|
|
||||||
1: {'themeName': 'DCS World',
|
|
||||||
'themeFile': 'style-dcs.css',
|
|
||||||
'themeIcons': 'light',
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
COLORS: Dict[str, QColor] = {
|
COLORS: Dict[str, QColor] = {
|
||||||
"white": QColor(255, 255, 255),
|
"white": QColor(255, 255, 255),
|
||||||
"white_transparent": QColor(255, 255, 255, 35),
|
"white_transparent": QColor(255, 255, 255, 35),
|
||||||
@ -85,10 +69,10 @@ def load_icons():
|
|||||||
ICONS["Hangar"] = QPixmap("./resources/ui/misc/hangar.png")
|
ICONS["Hangar"] = QPixmap("./resources/ui/misc/hangar.png")
|
||||||
|
|
||||||
ICONS["Terrain_Caucasus"] = QPixmap("./resources/ui/terrain_caucasus.gif")
|
ICONS["Terrain_Caucasus"] = QPixmap("./resources/ui/terrain_caucasus.gif")
|
||||||
ICONS["Terrain_Persian_Gulf"] = QPixmap("./resources/ui/terrain_pg.gif")
|
ICONS["Terrain_PersianGulf"] = QPixmap("./resources/ui/terrain_pg.gif")
|
||||||
ICONS["Terrain_Nevada"] = QPixmap("./resources/ui/terrain_nevada.gif")
|
ICONS["Terrain_Nevada"] = QPixmap("./resources/ui/terrain_nevada.gif")
|
||||||
ICONS["Terrain_Normandy"] = QPixmap("./resources/ui/terrain_normandy.gif")
|
ICONS["Terrain_Normandy"] = QPixmap("./resources/ui/terrain_normandy.gif")
|
||||||
ICONS["Terrain_Channel"] = QPixmap("./resources/ui/terrain_channel.gif")
|
ICONS["Terrain_TheChannel"] = QPixmap("./resources/ui/terrain_channel.gif")
|
||||||
ICONS["Terrain_Syria"] = QPixmap("./resources/ui/terrain_syria.gif")
|
ICONS["Terrain_Syria"] = QPixmap("./resources/ui/terrain_syria.gif")
|
||||||
|
|
||||||
ICONS["Dawn"] = QPixmap("./resources/ui/daytime/dawn.png")
|
ICONS["Dawn"] = QPixmap("./resources/ui/daytime/dawn.png")
|
||||||
@ -127,15 +111,12 @@ EVENT_ICONS: Dict[str, QPixmap] = {}
|
|||||||
|
|
||||||
def load_event_icons():
|
def load_event_icons():
|
||||||
for image in os.listdir("./resources/ui/events/"):
|
for image in os.listdir("./resources/ui/events/"):
|
||||||
print(image)
|
|
||||||
if image.endswith(".PNG"):
|
if image.endswith(".PNG"):
|
||||||
EVENT_ICONS[image[:-4]] = QPixmap(os.path.join("./resources/ui/events/", image))
|
EVENT_ICONS[image[:-4]] = QPixmap(os.path.join("./resources/ui/events/", image))
|
||||||
|
|
||||||
def load_aircraft_icons():
|
def load_aircraft_icons():
|
||||||
for aircraft in os.listdir("./resources/ui/units/aircrafts/"):
|
for aircraft in os.listdir("./resources/ui/units/aircrafts/"):
|
||||||
print(aircraft)
|
|
||||||
if aircraft.endswith(".jpg"):
|
if aircraft.endswith(".jpg"):
|
||||||
print(aircraft[:-7] + " : " + os.path.join("./resources/ui/units/aircrafts/", aircraft) + " ")
|
|
||||||
AIRCRAFT_ICONS[aircraft[:-7]] = QPixmap(os.path.join("./resources/ui/units/aircrafts/", aircraft))
|
AIRCRAFT_ICONS[aircraft[:-7]] = QPixmap(os.path.join("./resources/ui/units/aircrafts/", aircraft))
|
||||||
AIRCRAFT_ICONS["F-16C_50"] = AIRCRAFT_ICONS["F-16C"]
|
AIRCRAFT_ICONS["F-16C_50"] = AIRCRAFT_ICONS["F-16C"]
|
||||||
AIRCRAFT_ICONS["FA-18C_hornet"] = AIRCRAFT_ICONS["FA-18C"]
|
AIRCRAFT_ICONS["FA-18C_hornet"] = AIRCRAFT_ICONS["FA-18C"]
|
||||||
@ -144,7 +125,5 @@ def load_aircraft_icons():
|
|||||||
|
|
||||||
def load_vehicle_icons():
|
def load_vehicle_icons():
|
||||||
for vehicle in os.listdir("./resources/ui/units/vehicles/"):
|
for vehicle in os.listdir("./resources/ui/units/vehicles/"):
|
||||||
print(vehicle)
|
|
||||||
if vehicle.endswith(".jpg"):
|
if vehicle.endswith(".jpg"):
|
||||||
print(vehicle[:-7] + " : " + os.path.join("./resources/ui/units/vehicles/", vehicle) + " ")
|
|
||||||
VEHICLES_ICONS[vehicle[:-7]] = QPixmap(os.path.join("./resources/ui/units/vehicles/", vehicle))
|
VEHICLES_ICONS[vehicle[:-7]] = QPixmap(os.path.join("./resources/ui/units/vehicles/", vehicle))
|
||||||
|
|||||||
13
qt_ui/widgets/QFlightSizeSpinner.py
Normal file
13
qt_ui/widgets/QFlightSizeSpinner.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
"""Spin box for selecting the number of aircraft in a flight."""
|
||||||
|
from PySide2.QtWidgets import QSpinBox
|
||||||
|
|
||||||
|
|
||||||
|
class QFlightSizeSpinner(QSpinBox):
|
||||||
|
"""Spin box for selecting the number of aircraft in a flight."""
|
||||||
|
|
||||||
|
def __init__(self, min_size: int = 1, max_size: int = 4,
|
||||||
|
default_size: int = 2) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.setMinimum(min_size)
|
||||||
|
self.setMaximum(max_size)
|
||||||
|
self.setValue(default_size)
|
||||||
17
qt_ui/widgets/QLabeledWidget.py
Normal file
17
qt_ui/widgets/QLabeledWidget.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
"""A layout containing a widget with an associated label."""
|
||||||
|
from PySide2.QtCore import Qt
|
||||||
|
from PySide2.QtWidgets import QHBoxLayout, QLabel, QWidget
|
||||||
|
|
||||||
|
|
||||||
|
class QLabeledWidget(QHBoxLayout):
|
||||||
|
"""A layout containing a widget with an associated label.
|
||||||
|
|
||||||
|
Best used for vertical forms, where the given widget is the input and the
|
||||||
|
label is used to name the input.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, text: str, widget: QWidget) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.addWidget(QLabel(text))
|
||||||
|
self.addStretch()
|
||||||
|
self.addWidget(widget, alignment=Qt.AlignRight)
|
||||||
@ -1,16 +1,17 @@
|
|||||||
from PySide2.QtWidgets import QFrame, QHBoxLayout, QPushButton, QVBoxLayout, QGroupBox
|
from typing import Optional
|
||||||
|
|
||||||
from game import Game
|
from PySide2.QtWidgets import QFrame, QGroupBox, QHBoxLayout, QPushButton
|
||||||
from qt_ui.widgets.QBudgetBox import QBudgetBox
|
|
||||||
from qt_ui.widgets.QFactionsInfos import QFactionsInfos
|
|
||||||
from qt_ui.windows.finances.QFinancesMenu import QFinancesMenu
|
|
||||||
from qt_ui.windows.stats.QStatsWindow import QStatsWindow
|
|
||||||
from qt_ui.widgets.QTurnCounter import QTurnCounter
|
|
||||||
|
|
||||||
import qt_ui.uiconstants as CONST
|
import qt_ui.uiconstants as CONST
|
||||||
|
from game import Game
|
||||||
|
from game.event import CAP, CAS, FrontlineAttackEvent
|
||||||
|
from qt_ui.widgets.QBudgetBox import QBudgetBox
|
||||||
|
from qt_ui.widgets.QFactionsInfos import QFactionsInfos
|
||||||
|
from qt_ui.widgets.QTurnCounter import QTurnCounter
|
||||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||||
from qt_ui.windows.mission.QMissionPlanning import QMissionPlanning
|
|
||||||
from qt_ui.windows.settings.QSettingsWindow import QSettingsWindow
|
from qt_ui.windows.settings.QSettingsWindow import QSettingsWindow
|
||||||
|
from qt_ui.windows.stats.QStatsWindow import QStatsWindow
|
||||||
|
from qt_ui.windows.QWaitingForMissionResultWindow import QWaitingForMissionResultWindow
|
||||||
|
|
||||||
|
|
||||||
class QTopPanel(QFrame):
|
class QTopPanel(QFrame):
|
||||||
@ -33,10 +34,10 @@ class QTopPanel(QFrame):
|
|||||||
self.passTurnButton.setProperty("style", "btn-primary")
|
self.passTurnButton.setProperty("style", "btn-primary")
|
||||||
self.passTurnButton.clicked.connect(self.passTurn)
|
self.passTurnButton.clicked.connect(self.passTurn)
|
||||||
|
|
||||||
self.proceedButton = QPushButton("Mission Planning")
|
self.proceedButton = QPushButton("Take off")
|
||||||
self.proceedButton.setIcon(CONST.ICONS["Proceed"])
|
self.proceedButton.setIcon(CONST.ICONS["Proceed"])
|
||||||
self.proceedButton.setProperty("style", "btn-success")
|
self.proceedButton.setProperty("style", "start-button")
|
||||||
self.proceedButton.clicked.connect(self.proceed)
|
self.proceedButton.clicked.connect(self.launch_mission)
|
||||||
if self.game and self.game.turn == 0:
|
if self.game and self.game.turn == 0:
|
||||||
self.proceedButton.setEnabled(False)
|
self.proceedButton.setEnabled(False)
|
||||||
|
|
||||||
@ -75,7 +76,7 @@ class QTopPanel(QFrame):
|
|||||||
self.layout.setContentsMargins(0,0,0,0)
|
self.layout.setContentsMargins(0,0,0,0)
|
||||||
self.setLayout(self.layout)
|
self.setLayout(self.layout)
|
||||||
|
|
||||||
def setGame(self, game:Game):
|
def setGame(self, game: Optional[Game]):
|
||||||
self.game = game
|
self.game = game
|
||||||
if game is not None:
|
if game is not None:
|
||||||
self.turnCounter.setCurrentTurn(self.game.turn, self.game.current_day)
|
self.turnCounter.setCurrentTurn(self.game.turn, self.game.current_day)
|
||||||
@ -100,9 +101,31 @@ class QTopPanel(QFrame):
|
|||||||
GameUpdateSignal.get_instance().updateGame(self.game)
|
GameUpdateSignal.get_instance().updateGame(self.game)
|
||||||
self.proceedButton.setEnabled(True)
|
self.proceedButton.setEnabled(True)
|
||||||
|
|
||||||
def proceed(self):
|
def launch_mission(self):
|
||||||
self.subwindow = QMissionPlanning(self.game)
|
"""Finishes planning and waits for mission completion."""
|
||||||
self.subwindow.show()
|
# TODO: Refactor this nonsense.
|
||||||
|
game_event = None
|
||||||
|
for event in self.game.events:
|
||||||
|
if isinstance(event,
|
||||||
|
FrontlineAttackEvent) and event.is_player_attacking:
|
||||||
|
game_event = event
|
||||||
|
if game_event is None:
|
||||||
|
game_event = FrontlineAttackEvent(
|
||||||
|
self.game,
|
||||||
|
self.game.theater.controlpoints[0],
|
||||||
|
self.game.theater.controlpoints[0],
|
||||||
|
self.game.theater.controlpoints[0].position,
|
||||||
|
self.game.player_name,
|
||||||
|
self.game.enemy_name)
|
||||||
|
game_event.is_awacs_enabled = True
|
||||||
|
game_event.ca_slots = 1
|
||||||
|
game_event.departure_cp = self.game.theater.controlpoints[0]
|
||||||
|
game_event.player_attacking({CAS: {}, CAP: {}})
|
||||||
|
game_event.depart_from = self.game.theater.controlpoints[0]
|
||||||
|
|
||||||
|
self.game.initiate_event(game_event)
|
||||||
|
waiting = QWaitingForMissionResultWindow(game_event, self.game)
|
||||||
|
waiting.show()
|
||||||
|
|
||||||
def budget_update(self, game:Game):
|
def budget_update(self, game:Game):
|
||||||
self.budgetBox.setGame(game)
|
self.budgetBox.setGame(game)
|
||||||
|
|||||||
252
qt_ui/widgets/ato.py
Normal file
252
qt_ui/widgets/ato.py
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
"""Widgets for displaying air tasking orders."""
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from PySide2.QtCore import QItemSelectionModel, QModelIndex, QSize, Qt
|
||||||
|
from PySide2.QtWidgets import (
|
||||||
|
QAbstractItemView,
|
||||||
|
QGroupBox,
|
||||||
|
QHBoxLayout,
|
||||||
|
QListView,
|
||||||
|
QPushButton,
|
||||||
|
QSplitter,
|
||||||
|
QVBoxLayout,
|
||||||
|
)
|
||||||
|
|
||||||
|
from gen.ato import Package
|
||||||
|
from gen.flights.flight import Flight
|
||||||
|
from ..models import AtoModel, GameModel, NullListModel, PackageModel
|
||||||
|
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||||
|
|
||||||
|
|
||||||
|
class QFlightList(QListView):
|
||||||
|
"""List view for displaying the flights of a package."""
|
||||||
|
|
||||||
|
def __init__(self, model: Optional[PackageModel]) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.package_model = model
|
||||||
|
self.set_package(model)
|
||||||
|
self.setIconSize(QSize(91, 24))
|
||||||
|
self.setSelectionBehavior(QAbstractItemView.SelectItems)
|
||||||
|
|
||||||
|
def set_package(self, model: Optional[PackageModel]) -> None:
|
||||||
|
"""Sets the package model to display."""
|
||||||
|
if model is None:
|
||||||
|
self.disconnect_model()
|
||||||
|
else:
|
||||||
|
self.package_model = model
|
||||||
|
self.setModel(model)
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
model.deleted.connect(self.disconnect_model)
|
||||||
|
self.selectionModel().setCurrentIndex(
|
||||||
|
model.index(0, 0, QModelIndex()),
|
||||||
|
QItemSelectionModel.Select
|
||||||
|
)
|
||||||
|
|
||||||
|
def disconnect_model(self) -> None:
|
||||||
|
"""Clears the listview of any model attachments.
|
||||||
|
|
||||||
|
Displays an empty list until set_package is called with a valid model.
|
||||||
|
"""
|
||||||
|
model = self.model()
|
||||||
|
if model is not None and isinstance(model, PackageModel):
|
||||||
|
model.deleted.disconnect(self.disconnect_model)
|
||||||
|
self.setModel(NullListModel())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def selected_item(self) -> Optional[Flight]:
|
||||||
|
"""Returns the selected flight, if any."""
|
||||||
|
index = self.currentIndex()
|
||||||
|
if not index.isValid():
|
||||||
|
return None
|
||||||
|
return self.package_model.flight_at_index(index)
|
||||||
|
|
||||||
|
|
||||||
|
class QFlightPanel(QGroupBox):
|
||||||
|
"""The flight display portion of the ATO panel.
|
||||||
|
|
||||||
|
Displays the flights assigned to the selected package, and includes edit and
|
||||||
|
delete buttons for flight management.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, game_model: GameModel,
|
||||||
|
package_model: Optional[PackageModel] = None) -> None:
|
||||||
|
super().__init__("Flights")
|
||||||
|
self.game_model = game_model
|
||||||
|
self.package_model = package_model
|
||||||
|
|
||||||
|
self.vbox = QVBoxLayout()
|
||||||
|
self.setLayout(self.vbox)
|
||||||
|
|
||||||
|
self.flight_list = QFlightList(package_model)
|
||||||
|
self.vbox.addWidget(self.flight_list)
|
||||||
|
|
||||||
|
self.button_row = QHBoxLayout()
|
||||||
|
self.vbox.addLayout(self.button_row)
|
||||||
|
|
||||||
|
self.edit_button = QPushButton("Edit")
|
||||||
|
self.edit_button.clicked.connect(self.on_edit)
|
||||||
|
self.button_row.addWidget(self.edit_button)
|
||||||
|
|
||||||
|
self.delete_button = QPushButton("Delete")
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
self.delete_button.setProperty("style", "btn-danger")
|
||||||
|
self.delete_button.clicked.connect(self.on_delete)
|
||||||
|
self.button_row.addWidget(self.delete_button)
|
||||||
|
|
||||||
|
self.selection_changed.connect(self.on_selection_changed)
|
||||||
|
self.on_selection_changed()
|
||||||
|
|
||||||
|
def set_package(self, model: Optional[PackageModel]) -> None:
|
||||||
|
"""Sets the package model to display."""
|
||||||
|
self.package_model = model
|
||||||
|
self.flight_list.set_package(model)
|
||||||
|
self.on_selection_changed()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def selection_changed(self):
|
||||||
|
"""Returns the signal emitted when the flight selection changes."""
|
||||||
|
return self.flight_list.selectionModel().selectionChanged
|
||||||
|
|
||||||
|
def on_selection_changed(self) -> None:
|
||||||
|
"""Updates the status of the edit and delete buttons."""
|
||||||
|
index = self.flight_list.currentIndex()
|
||||||
|
enabled = index.isValid()
|
||||||
|
self.edit_button.setEnabled(enabled)
|
||||||
|
self.delete_button.setEnabled(enabled)
|
||||||
|
|
||||||
|
def on_edit(self) -> None:
|
||||||
|
"""Opens the flight edit dialog."""
|
||||||
|
index = self.flight_list.currentIndex()
|
||||||
|
if not index.isValid():
|
||||||
|
logging.error(f"Cannot edit flight when no flight is selected.")
|
||||||
|
return
|
||||||
|
from qt_ui.dialogs import Dialog
|
||||||
|
Dialog.open_edit_flight_dialog(
|
||||||
|
self.package_model, self.package_model.flight_at_index(index)
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_delete(self) -> None:
|
||||||
|
"""Removes the selected flight from the package."""
|
||||||
|
index = self.flight_list.currentIndex()
|
||||||
|
if not index.isValid():
|
||||||
|
logging.error(f"Cannot delete flight when no flight is selected.")
|
||||||
|
return
|
||||||
|
self.game_model.game.aircraft_inventory.return_from_flight(
|
||||||
|
self.flight_list.selected_item)
|
||||||
|
self.package_model.delete_flight_at_index(index)
|
||||||
|
GameUpdateSignal.get_instance().redraw_flight_paths()
|
||||||
|
|
||||||
|
|
||||||
|
class QPackageList(QListView):
|
||||||
|
"""List view for displaying the packages of an ATO."""
|
||||||
|
|
||||||
|
def __init__(self, model: AtoModel) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.ato_model = model
|
||||||
|
self.setModel(model)
|
||||||
|
self.setIconSize(QSize(91, 24))
|
||||||
|
self.setSelectionBehavior(QAbstractItemView.SelectItems)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def selected_item(self) -> Optional[Package]:
|
||||||
|
"""Returns the selected package, if any."""
|
||||||
|
index = self.currentIndex()
|
||||||
|
if not index.isValid():
|
||||||
|
return None
|
||||||
|
return self.ato_model.package_at_index(index)
|
||||||
|
|
||||||
|
|
||||||
|
class QPackagePanel(QGroupBox):
|
||||||
|
"""The package display portion of the ATO panel.
|
||||||
|
|
||||||
|
Displays the package assigned to the player's ATO, and includes edit and
|
||||||
|
delete buttons for package management.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, model: AtoModel) -> None:
|
||||||
|
super().__init__("Packages")
|
||||||
|
self.ato_model = model
|
||||||
|
self.ato_model.layoutChanged.connect(self.on_selection_changed)
|
||||||
|
|
||||||
|
self.vbox = QVBoxLayout()
|
||||||
|
self.setLayout(self.vbox)
|
||||||
|
|
||||||
|
self.package_list = QPackageList(self.ato_model)
|
||||||
|
self.vbox.addWidget(self.package_list)
|
||||||
|
|
||||||
|
self.button_row = QHBoxLayout()
|
||||||
|
self.vbox.addLayout(self.button_row)
|
||||||
|
|
||||||
|
self.edit_button = QPushButton("Edit")
|
||||||
|
self.edit_button.clicked.connect(self.on_edit)
|
||||||
|
self.button_row.addWidget(self.edit_button)
|
||||||
|
|
||||||
|
self.delete_button = QPushButton("Delete")
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
self.delete_button.setProperty("style", "btn-danger")
|
||||||
|
self.delete_button.clicked.connect(self.on_delete)
|
||||||
|
self.button_row.addWidget(self.delete_button)
|
||||||
|
|
||||||
|
self.selection_changed.connect(self.on_selection_changed)
|
||||||
|
self.on_selection_changed()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def selection_changed(self):
|
||||||
|
"""Returns the signal emitted when the flight selection changes."""
|
||||||
|
return self.package_list.selectionModel().selectionChanged
|
||||||
|
|
||||||
|
def on_selection_changed(self) -> None:
|
||||||
|
"""Updates the status of the edit and delete buttons."""
|
||||||
|
index = self.package_list.currentIndex()
|
||||||
|
enabled = index.isValid()
|
||||||
|
self.edit_button.setEnabled(enabled)
|
||||||
|
self.delete_button.setEnabled(enabled)
|
||||||
|
|
||||||
|
def on_edit(self) -> None:
|
||||||
|
"""Opens the package edit dialog."""
|
||||||
|
index = self.package_list.currentIndex()
|
||||||
|
if not index.isValid():
|
||||||
|
logging.error(f"Cannot edit package when no package is selected.")
|
||||||
|
return
|
||||||
|
from qt_ui.dialogs import Dialog
|
||||||
|
Dialog.open_edit_package_dialog(self.ato_model.get_package_model(index))
|
||||||
|
|
||||||
|
def on_delete(self) -> None:
|
||||||
|
"""Removes the package from the ATO."""
|
||||||
|
index = self.package_list.currentIndex()
|
||||||
|
if not index.isValid():
|
||||||
|
logging.error(f"Cannot delete package when no package is selected.")
|
||||||
|
return
|
||||||
|
self.ato_model.delete_package_at_index(index)
|
||||||
|
GameUpdateSignal.get_instance().redraw_flight_paths()
|
||||||
|
|
||||||
|
|
||||||
|
class QAirTaskingOrderPanel(QSplitter):
|
||||||
|
"""A split panel for displaying the packages and flights of an ATO.
|
||||||
|
|
||||||
|
Used as the left-bar of the main UI. The top half of the panel displays the
|
||||||
|
packages of the player's ATO, and the bottom half displays the flights of
|
||||||
|
the selected package.
|
||||||
|
"""
|
||||||
|
def __init__(self, game_model: GameModel) -> None:
|
||||||
|
super().__init__(Qt.Vertical)
|
||||||
|
self.ato_model = game_model.ato_model
|
||||||
|
|
||||||
|
self.package_panel = QPackagePanel(self.ato_model)
|
||||||
|
self.package_panel.selection_changed.connect(self.on_package_change)
|
||||||
|
self.ato_model.rowsInserted.connect(self.on_package_change)
|
||||||
|
self.addWidget(self.package_panel)
|
||||||
|
|
||||||
|
self.flight_panel = QFlightPanel(game_model)
|
||||||
|
self.addWidget(self.flight_panel)
|
||||||
|
|
||||||
|
def on_package_change(self) -> None:
|
||||||
|
"""Sets the newly selected flight for display in the bottom panel."""
|
||||||
|
index = self.package_panel.package_list.currentIndex()
|
||||||
|
if index.isValid():
|
||||||
|
self.flight_panel.set_package(
|
||||||
|
self.ato_model.get_package_model(index)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.flight_panel.set_package(None)
|
||||||
16
qt_ui/widgets/combos/QAircraftTypeSelector.py
Normal file
16
qt_ui/widgets/combos/QAircraftTypeSelector.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""Combo box for selecting aircraft types."""
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
|
from PySide2.QtWidgets import QComboBox
|
||||||
|
|
||||||
|
from dcs.planes import PlaneType
|
||||||
|
|
||||||
|
|
||||||
|
class QAircraftTypeSelector(QComboBox):
|
||||||
|
"""Combo box for selecting among the given aircraft types."""
|
||||||
|
|
||||||
|
def __init__(self, aircraft_types: Iterable[PlaneType]) -> None:
|
||||||
|
super().__init__()
|
||||||
|
for aircraft in aircraft_types:
|
||||||
|
self.addItem(f"{aircraft.id}", userData=aircraft)
|
||||||
|
self.model().sort(0)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user