mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Merge branch 'develop' into faction_refactor
# Conflicts: # game/factions/bluefor_coldwar.py # game/factions/bluefor_coldwar_a4.py # game/factions/bluefor_coldwar_mods.py # game/factions/bluefor_modern.py
This commit is contained in:
commit
f962fd55bc
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@ -30,6 +30,9 @@ jobs:
|
|||||||
- name: Build binaries
|
- name: Build binaries
|
||||||
run: |
|
run: |
|
||||||
./venv/scripts/activate
|
./venv/scripts/activate
|
||||||
|
mypy game
|
||||||
|
mypy gen
|
||||||
|
mypy theater
|
||||||
$env:PYTHONPATH=".;./pydcs"
|
$env:PYTHONPATH=".;./pydcs"
|
||||||
pyinstaller pyinstaller.spec
|
pyinstaller pyinstaller.spec
|
||||||
|
|
||||||
|
|||||||
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@ -32,6 +32,9 @@ jobs:
|
|||||||
- name: Build binaries
|
- name: Build binaries
|
||||||
run: |
|
run: |
|
||||||
./venv/scripts/activate
|
./venv/scripts/activate
|
||||||
|
mypy game
|
||||||
|
mypy gen
|
||||||
|
mypy theater
|
||||||
$env:PYTHONPATH=".;./pydcs"
|
$env:PYTHONPATH=".;./pydcs"
|
||||||
pyinstaller pyinstaller.spec
|
pyinstaller pyinstaller.spec
|
||||||
|
|
||||||
|
|||||||
107
game/db.py
107
game/db.py
@ -43,6 +43,7 @@ from dcs.planes import (
|
|||||||
FA_18C_hornet,
|
FA_18C_hornet,
|
||||||
FW_190A8,
|
FW_190A8,
|
||||||
FW_190D9,
|
FW_190D9,
|
||||||
|
F_117A,
|
||||||
F_14B,
|
F_14B,
|
||||||
F_15C,
|
F_15C,
|
||||||
F_15E,
|
F_15E,
|
||||||
@ -97,6 +98,9 @@ from dcs.planes import (
|
|||||||
Su_34,
|
Su_34,
|
||||||
Tornado_GR4,
|
Tornado_GR4,
|
||||||
Tornado_IDS,
|
Tornado_IDS,
|
||||||
|
Tu_160,
|
||||||
|
Tu_22M3,
|
||||||
|
Tu_95MS,
|
||||||
WingLoong_I,
|
WingLoong_I,
|
||||||
Yak_40,
|
Yak_40,
|
||||||
plane_map,
|
plane_map,
|
||||||
@ -304,6 +308,10 @@ PRICES = {
|
|||||||
# Bombers
|
# Bombers
|
||||||
B_52H: 35,
|
B_52H: 35,
|
||||||
B_1B: 50,
|
B_1B: 50,
|
||||||
|
F_117A: 100,
|
||||||
|
Tu_160: 50,
|
||||||
|
Tu_22M3: 40,
|
||||||
|
Tu_95MS: 35,
|
||||||
|
|
||||||
# special
|
# special
|
||||||
IL_76MD: 30,
|
IL_76MD: 30,
|
||||||
@ -584,49 +592,53 @@ UNIT_BY_TASK = {
|
|||||||
SA342Mistral
|
SA342Mistral
|
||||||
],
|
],
|
||||||
CAS: [
|
CAS: [
|
||||||
F_15E,
|
AH_1W,
|
||||||
F_86F_Sabre,
|
AH_64A,
|
||||||
MiG_15bis,
|
AH_64D,
|
||||||
L_39ZA,
|
|
||||||
AV8BNA,
|
|
||||||
AJS37,
|
AJS37,
|
||||||
|
AV8BNA,
|
||||||
A_10A,
|
A_10A,
|
||||||
A_10C,
|
A_10C,
|
||||||
A_10C_2,
|
A_10C_2,
|
||||||
Su_17M4,
|
|
||||||
Su_25,
|
|
||||||
Su_25T,
|
|
||||||
Su_34,
|
|
||||||
Ka_50,
|
|
||||||
SA342M,
|
|
||||||
SA342L,
|
|
||||||
SA342Minigun,
|
|
||||||
Su_24M,
|
|
||||||
Su_24MR,
|
|
||||||
AH_64A,
|
|
||||||
AH_64D,
|
|
||||||
OH_58D,
|
|
||||||
B_52H,
|
|
||||||
B_1B,
|
|
||||||
Tornado_IDS,
|
|
||||||
Tornado_GR4,
|
|
||||||
UH_1H,
|
|
||||||
Mi_8MT,
|
|
||||||
Mi_28N,
|
|
||||||
Mi_24V,
|
|
||||||
MiG_27K,
|
|
||||||
A_20G,
|
A_20G,
|
||||||
|
B_17G,
|
||||||
|
B_1B,
|
||||||
|
B_52H,
|
||||||
|
F_117A,
|
||||||
|
F_15E,
|
||||||
|
F_86F_Sabre,
|
||||||
|
Ju_88A4,
|
||||||
|
Ka_50,
|
||||||
|
L_39ZA,
|
||||||
|
MB_339PAN,
|
||||||
|
MQ_9_Reaper,
|
||||||
|
MiG_15bis,
|
||||||
|
MiG_27K,
|
||||||
|
Mi_24V,
|
||||||
|
Mi_28N,
|
||||||
|
Mi_8MT,
|
||||||
|
OH_58D,
|
||||||
P_47D_30,
|
P_47D_30,
|
||||||
P_47D_30bl1,
|
P_47D_30bl1,
|
||||||
P_47D_40,
|
P_47D_40,
|
||||||
Ju_88A4,
|
|
||||||
B_17G,
|
|
||||||
MB_339PAN,
|
|
||||||
Rafale_A_S,
|
|
||||||
WingLoong_I,
|
|
||||||
MQ_9_Reaper,
|
|
||||||
RQ_1A_Predator,
|
RQ_1A_Predator,
|
||||||
AH_1W
|
Rafale_A_S,
|
||||||
|
SA342L,
|
||||||
|
SA342M,
|
||||||
|
SA342Minigun,
|
||||||
|
Su_17M4,
|
||||||
|
Su_24M,
|
||||||
|
Su_24MR,
|
||||||
|
Su_25,
|
||||||
|
Su_25T,
|
||||||
|
Su_34,
|
||||||
|
Tornado_GR4,
|
||||||
|
Tornado_IDS,
|
||||||
|
Tu_160,
|
||||||
|
Tu_22M3,
|
||||||
|
Tu_95MS,
|
||||||
|
UH_1H,
|
||||||
|
WingLoong_I,
|
||||||
],
|
],
|
||||||
Transport: [
|
Transport: [
|
||||||
IL_76MD,
|
IL_76MD,
|
||||||
@ -942,6 +954,23 @@ COMMON_OVERRIDE = {
|
|||||||
|
|
||||||
PLANE_PAYLOAD_OVERRIDES: Dict[Type[PlaneType], Dict[Type[Task], str]] = {
|
PLANE_PAYLOAD_OVERRIDES: Dict[Type[PlaneType], Dict[Type[Task], str]] = {
|
||||||
|
|
||||||
|
B_1B: {
|
||||||
|
CAS: "GBU-38*16, CBU-97*20",
|
||||||
|
PinpointStrike: "GBU-31*8, GBU-38*32",
|
||||||
|
GroundAttack: "GBU-31*8, GBU-38*32",
|
||||||
|
},
|
||||||
|
B_52H: {
|
||||||
|
PinpointStrike: "AGM-86C*20",
|
||||||
|
GroundAttack: "Mk 82*51",
|
||||||
|
},
|
||||||
|
F_117A: {
|
||||||
|
PinpointStrike: "GBU-10*2",
|
||||||
|
},
|
||||||
|
F_15E: {
|
||||||
|
CAS: "AIM-120B*2,AIM-9M*2,FUEL,GBU-12*4,GBU-38*4,AGM-65D*2",
|
||||||
|
GroundAttack: "AIM-120B*2,AIM-9M*2,FUEL*3,CBU-97*12",
|
||||||
|
PinpointStrike: "AIM-120B*2,AIM-9M*2,FUEL,GBU-31*4,AGM-154C*2",
|
||||||
|
},
|
||||||
FA_18C_hornet: {
|
FA_18C_hornet: {
|
||||||
CAP: "CAP HEAVY",
|
CAP: "CAP HEAVY",
|
||||||
Intercept: "CAP HEAVY",
|
Intercept: "CAP HEAVY",
|
||||||
@ -962,6 +991,15 @@ PLANE_PAYLOAD_OVERRIDES: Dict[Type[PlaneType], Dict[Type[Task], str]] = {
|
|||||||
GroundAttack: "STRIKE",
|
GroundAttack: "STRIKE",
|
||||||
Escort: "CAP HEAVY",
|
Escort: "CAP HEAVY",
|
||||||
},
|
},
|
||||||
|
Tu_160: {
|
||||||
|
PinpointStrike: "Kh-65*12",
|
||||||
|
},
|
||||||
|
Tu_22M3: {
|
||||||
|
GroundAttack: "FAB-500*33, FAB-250*36",
|
||||||
|
},
|
||||||
|
Tu_95MS: {
|
||||||
|
PinpointStrike: "Kh-65*6",
|
||||||
|
},
|
||||||
A_10A: COMMON_OVERRIDE,
|
A_10A: COMMON_OVERRIDE,
|
||||||
A_10C: COMMON_OVERRIDE,
|
A_10C: COMMON_OVERRIDE,
|
||||||
A_10C_2: COMMON_OVERRIDE,
|
A_10C_2: COMMON_OVERRIDE,
|
||||||
@ -970,7 +1008,6 @@ PLANE_PAYLOAD_OVERRIDES: Dict[Type[PlaneType], Dict[Type[Task], str]] = {
|
|||||||
F_5E_3: COMMON_OVERRIDE,
|
F_5E_3: COMMON_OVERRIDE,
|
||||||
F_14B: COMMON_OVERRIDE,
|
F_14B: COMMON_OVERRIDE,
|
||||||
F_15C: COMMON_OVERRIDE,
|
F_15C: COMMON_OVERRIDE,
|
||||||
F_15E: COMMON_OVERRIDE,
|
|
||||||
F_16C_50: COMMON_OVERRIDE,
|
F_16C_50: COMMON_OVERRIDE,
|
||||||
JF_17: COMMON_OVERRIDE,
|
JF_17: COMMON_OVERRIDE,
|
||||||
M_2000C: COMMON_OVERRIDE,
|
M_2000C: COMMON_OVERRIDE,
|
||||||
|
|||||||
@ -1,4 +1,12 @@
|
|||||||
from dcs.planes import An_26B, An_30M, IL_76MD, IL_78M, MiG_15bis, Yak_40
|
from dcs.planes import (
|
||||||
|
An_26B,
|
||||||
|
An_30M,
|
||||||
|
IL_76MD,
|
||||||
|
IL_78M,
|
||||||
|
MiG_15bis,
|
||||||
|
Tu_95MS,
|
||||||
|
Yak_40,
|
||||||
|
)
|
||||||
from dcs.ships import (
|
from dcs.ships import (
|
||||||
Bulk_cargo_ship_Yakushev,
|
Bulk_cargo_ship_Yakushev,
|
||||||
CV_1143_5_Admiral_Kuznetsov,
|
CV_1143_5_Admiral_Kuznetsov,
|
||||||
@ -19,6 +27,8 @@ Russia_1955 = {
|
|||||||
An_30M,
|
An_30M,
|
||||||
Yak_40,
|
Yak_40,
|
||||||
|
|
||||||
|
Tu_95MS,
|
||||||
|
|
||||||
AirDefence.AAA_ZU_23_Closed,
|
AirDefence.AAA_ZU_23_Closed,
|
||||||
AirDefence.AAA_ZU_23_on_Ural_375,
|
AirDefence.AAA_ZU_23_on_Ural_375,
|
||||||
Armor.ARV_BRDM_2,
|
Armor.ARV_BRDM_2,
|
||||||
|
|||||||
@ -8,6 +8,7 @@ from dcs.planes import (
|
|||||||
MiG_15bis,
|
MiG_15bis,
|
||||||
MiG_19P,
|
MiG_19P,
|
||||||
MiG_21Bis,
|
MiG_21Bis,
|
||||||
|
Tu_95MS,
|
||||||
Yak_40,
|
Yak_40,
|
||||||
)
|
)
|
||||||
from dcs.ships import (
|
from dcs.ships import (
|
||||||
@ -32,6 +33,8 @@ Russia_1965 = {
|
|||||||
An_30M,
|
An_30M,
|
||||||
Yak_40,
|
Yak_40,
|
||||||
|
|
||||||
|
Tu_95MS,
|
||||||
|
|
||||||
A_50,
|
A_50,
|
||||||
|
|
||||||
Mi_8MT,
|
Mi_8MT,
|
||||||
|
|||||||
@ -15,6 +15,8 @@ from dcs.planes import (
|
|||||||
Su_17M4,
|
Su_17M4,
|
||||||
Su_24M,
|
Su_24M,
|
||||||
Su_25,
|
Su_25,
|
||||||
|
Tu_22M3,
|
||||||
|
Tu_95MS,
|
||||||
Yak_40,
|
Yak_40,
|
||||||
)
|
)
|
||||||
from dcs.ships import (
|
from dcs.ships import (
|
||||||
@ -41,6 +43,9 @@ Russia_1975 = {
|
|||||||
Su_24M,
|
Su_24M,
|
||||||
Su_25,
|
Su_25,
|
||||||
|
|
||||||
|
Tu_22M3,
|
||||||
|
Tu_95MS,
|
||||||
|
|
||||||
IL_76MD,
|
IL_76MD,
|
||||||
IL_78M,
|
IL_78M,
|
||||||
An_26B,
|
An_26B,
|
||||||
|
|||||||
@ -17,6 +17,9 @@ from dcs.planes import (
|
|||||||
Su_24M,
|
Su_24M,
|
||||||
Su_25,
|
Su_25,
|
||||||
Su_27,
|
Su_27,
|
||||||
|
Tu_160,
|
||||||
|
Tu_22M3,
|
||||||
|
Tu_95MS,
|
||||||
Yak_40,
|
Yak_40,
|
||||||
)
|
)
|
||||||
from dcs.ships import (
|
from dcs.ships import (
|
||||||
@ -51,6 +54,10 @@ Russia_1990 = {
|
|||||||
Su_25,
|
Su_25,
|
||||||
Ka_50,
|
Ka_50,
|
||||||
|
|
||||||
|
Tu_160,
|
||||||
|
Tu_22M3,
|
||||||
|
Tu_95MS,
|
||||||
|
|
||||||
IL_76MD,
|
IL_76MD,
|
||||||
IL_78M,
|
IL_78M,
|
||||||
An_26B,
|
An_26B,
|
||||||
|
|||||||
@ -20,6 +20,9 @@ from dcs.planes import (
|
|||||||
Su_30,
|
Su_30,
|
||||||
Su_33,
|
Su_33,
|
||||||
Su_34,
|
Su_34,
|
||||||
|
Tu_160,
|
||||||
|
Tu_22M3,
|
||||||
|
Tu_95MS,
|
||||||
Yak_40,
|
Yak_40,
|
||||||
)
|
)
|
||||||
from dcs.ships import (
|
from dcs.ships import (
|
||||||
@ -55,6 +58,10 @@ Russia_2010 = {
|
|||||||
Su_24M,
|
Su_24M,
|
||||||
L_39ZA,
|
L_39ZA,
|
||||||
|
|
||||||
|
Tu_160,
|
||||||
|
Tu_22M3,
|
||||||
|
Tu_95MS,
|
||||||
|
|
||||||
IL_76MD,
|
IL_76MD,
|
||||||
IL_78M,
|
IL_78M,
|
||||||
An_26B,
|
An_26B,
|
||||||
|
|||||||
@ -20,6 +20,9 @@ from dcs.planes import (
|
|||||||
Su_30,
|
Su_30,
|
||||||
Su_33,
|
Su_33,
|
||||||
Su_34,
|
Su_34,
|
||||||
|
Tu_160,
|
||||||
|
Tu_22M3,
|
||||||
|
Tu_95MS,
|
||||||
Yak_40,
|
Yak_40,
|
||||||
)
|
)
|
||||||
from dcs.ships import (
|
from dcs.ships import (
|
||||||
@ -58,6 +61,10 @@ Russia_2020 = {
|
|||||||
Su_24M,
|
Su_24M,
|
||||||
L_39ZA,
|
L_39ZA,
|
||||||
|
|
||||||
|
Tu_160,
|
||||||
|
Tu_22M3,
|
||||||
|
Tu_95MS,
|
||||||
|
|
||||||
IL_76MD,
|
IL_76MD,
|
||||||
IL_78M,
|
IL_78M,
|
||||||
An_26B,
|
An_26B,
|
||||||
|
|||||||
@ -6,10 +6,13 @@ from dcs.helicopters import (
|
|||||||
UH_1H,
|
UH_1H,
|
||||||
)
|
)
|
||||||
from dcs.planes import (
|
from dcs.planes import (
|
||||||
|
B_1B,
|
||||||
|
B_52H,
|
||||||
C_130,
|
C_130,
|
||||||
E_3A,
|
E_3A,
|
||||||
FA_18C_hornet,
|
FA_18C_hornet,
|
||||||
F_15C,
|
F_15C,
|
||||||
|
F_15E,
|
||||||
F_16C_50,
|
F_16C_50,
|
||||||
F_5E_3,
|
F_5E_3,
|
||||||
KC130,
|
KC130,
|
||||||
@ -38,11 +41,15 @@ US_Aggressors = {
|
|||||||
"units": [
|
"units": [
|
||||||
|
|
||||||
F_15C,
|
F_15C,
|
||||||
|
F_15E,
|
||||||
F_5E_3,
|
F_5E_3,
|
||||||
FA_18C_hornet,
|
FA_18C_hornet,
|
||||||
F_16C_50,
|
F_16C_50,
|
||||||
Su_27,
|
Su_27,
|
||||||
|
|
||||||
|
B_1B,
|
||||||
|
B_52H,
|
||||||
|
|
||||||
KC_135,
|
KC_135,
|
||||||
KC130,
|
KC130,
|
||||||
C_130,
|
C_130,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
from dcs.planes import (
|
from dcs.planes import (
|
||||||
|
B_52H,
|
||||||
C_130,
|
C_130,
|
||||||
E_3A,
|
E_3A,
|
||||||
F_86F_Sabre,
|
F_86F_Sabre,
|
||||||
@ -25,6 +26,8 @@ USA_1955 = {
|
|||||||
F_86F_Sabre,
|
F_86F_Sabre,
|
||||||
P_51D,
|
P_51D,
|
||||||
|
|
||||||
|
B_52H,
|
||||||
|
|
||||||
KC_135,
|
KC_135,
|
||||||
KC130,
|
KC130,
|
||||||
C_130,
|
C_130,
|
||||||
|
|||||||
@ -2,6 +2,7 @@ from dcs.helicopters import (
|
|||||||
UH_1H,
|
UH_1H,
|
||||||
)
|
)
|
||||||
from dcs.planes import (
|
from dcs.planes import (
|
||||||
|
B_52H,
|
||||||
C_130,
|
C_130,
|
||||||
E_3A,
|
E_3A,
|
||||||
F_86F_Sabre,
|
F_86F_Sabre,
|
||||||
@ -28,6 +29,8 @@ USA_1960 = {
|
|||||||
F_86F_Sabre,
|
F_86F_Sabre,
|
||||||
P_51D,
|
P_51D,
|
||||||
|
|
||||||
|
B_52H,
|
||||||
|
|
||||||
KC_135,
|
KC_135,
|
||||||
KC130,
|
KC130,
|
||||||
C_130,
|
C_130,
|
||||||
|
|||||||
@ -5,9 +5,12 @@ from dcs.helicopters import (
|
|||||||
from dcs.planes import (
|
from dcs.planes import (
|
||||||
AV8BNA,
|
AV8BNA,
|
||||||
A_10A,
|
A_10A,
|
||||||
|
B_1B,
|
||||||
|
B_52H,
|
||||||
C_130,
|
C_130,
|
||||||
E_3A,
|
E_3A,
|
||||||
FA_18C_hornet,
|
FA_18C_hornet,
|
||||||
|
F_117A,
|
||||||
F_14B,
|
F_14B,
|
||||||
F_15C,
|
F_15C,
|
||||||
F_15E,
|
F_15E,
|
||||||
@ -43,6 +46,10 @@ USA_1990 = {
|
|||||||
A_10A,
|
A_10A,
|
||||||
AV8BNA,
|
AV8BNA,
|
||||||
|
|
||||||
|
B_1B,
|
||||||
|
B_52H,
|
||||||
|
F_117A,
|
||||||
|
|
||||||
KC_135,
|
KC_135,
|
||||||
KC130,
|
KC130,
|
||||||
C_130,
|
C_130,
|
||||||
|
|||||||
@ -6,9 +6,12 @@ from dcs.planes import (
|
|||||||
AV8BNA,
|
AV8BNA,
|
||||||
A_10C,
|
A_10C,
|
||||||
A_10C_2,
|
A_10C_2,
|
||||||
|
B_1B,
|
||||||
|
B_52H,
|
||||||
C_130,
|
C_130,
|
||||||
E_3A,
|
E_3A,
|
||||||
FA_18C_hornet,
|
FA_18C_hornet,
|
||||||
|
F_117A,
|
||||||
F_14B,
|
F_14B,
|
||||||
F_15C,
|
F_15C,
|
||||||
F_15E,
|
F_15E,
|
||||||
@ -46,6 +49,10 @@ USA_2005 = {
|
|||||||
AV8BNA,
|
AV8BNA,
|
||||||
MQ_9_Reaper,
|
MQ_9_Reaper,
|
||||||
|
|
||||||
|
B_1B,
|
||||||
|
B_52H,
|
||||||
|
F_117A,
|
||||||
|
|
||||||
KC_135,
|
KC_135,
|
||||||
KC130,
|
KC130,
|
||||||
C_130,
|
C_130,
|
||||||
|
|||||||
@ -14,7 +14,7 @@ from dcs.translation import String
|
|||||||
from dcs.triggers import TriggerStart
|
from dcs.triggers import TriggerStart
|
||||||
from dcs.unittype import UnitType
|
from dcs.unittype import UnitType
|
||||||
|
|
||||||
from gen import Conflict, VisualGenerator, FlightType
|
from gen import Conflict, FlightType, VisualGenerator
|
||||||
from gen.aircraft import AIRCRAFT_DATA, AircraftConflictGenerator, FlightData
|
from gen.aircraft import AIRCRAFT_DATA, AircraftConflictGenerator, FlightData
|
||||||
from gen.airfields import AIRFIELD_DATA
|
from gen.airfields import AIRFIELD_DATA
|
||||||
from gen.airsupportgen import AirSupport, AirSupportConflictGenerator
|
from gen.airsupportgen import AirSupport, AirSupportConflictGenerator
|
||||||
@ -28,10 +28,11 @@ from gen.kneeboard import KneeboardGenerator
|
|||||||
from gen.radios import RadioFrequency, RadioRegistry
|
from gen.radios import RadioFrequency, RadioRegistry
|
||||||
from gen.tacan import TacanRegistry
|
from gen.tacan import TacanRegistry
|
||||||
from gen.triggergen import TRIGGER_RADIUS_MEDIUM, TriggersGenerator
|
from gen.triggergen import TRIGGER_RADIUS_MEDIUM, TriggersGenerator
|
||||||
|
from plugin import LuaPluginManager
|
||||||
from theater import ControlPoint
|
from theater import ControlPoint
|
||||||
from .. import db
|
from .. import db
|
||||||
from ..debriefing import Debriefing
|
from ..debriefing import Debriefing
|
||||||
from plugin import LuaPluginManager
|
|
||||||
|
|
||||||
class Operation:
|
class Operation:
|
||||||
attackers_starting_position = None # type: db.StartingPosition
|
attackers_starting_position = None # type: db.StartingPosition
|
||||||
@ -74,7 +75,7 @@ class Operation:
|
|||||||
self.departure_cp = departure_cp
|
self.departure_cp = departure_cp
|
||||||
self.to_cp = to_cp
|
self.to_cp = to_cp
|
||||||
self.is_quick = False
|
self.is_quick = False
|
||||||
self.listOfPluginsScripts = []
|
self.plugin_scripts: List[str] = []
|
||||||
|
|
||||||
def units_of(self, country_name: str) -> List[UnitType]:
|
def units_of(self, country_name: str) -> List[UnitType]:
|
||||||
return []
|
return []
|
||||||
@ -133,33 +134,37 @@ class Operation:
|
|||||||
else:
|
else:
|
||||||
self.defenders_starting_position = None
|
self.defenders_starting_position = None
|
||||||
|
|
||||||
def injectLuaTrigger(self, luascript, comment = "LUA script"):
|
def inject_lua_trigger(self, contents: str, comment: str) -> None:
|
||||||
trigger = TriggerStart(comment=comment)
|
trigger = TriggerStart(comment=comment)
|
||||||
trigger.add_action(DoScript(String(luascript)))
|
trigger.add_action(DoScript(String(contents)))
|
||||||
self.current_mission.triggerrules.triggers.append(trigger)
|
self.current_mission.triggerrules.triggers.append(trigger)
|
||||||
|
|
||||||
def bypassPluginScript(self, pluginName, scriptFileMnemonic):
|
def bypass_plugin_script(self, mnemonic: str) -> None:
|
||||||
self.listOfPluginsScripts.append(scriptFileMnemonic)
|
self.plugin_scripts.append(mnemonic)
|
||||||
|
|
||||||
def injectPluginScript(self, pluginName, scriptFile, scriptFileMnemonic):
|
def inject_plugin_script(self, plugin_mnemonic: str, script: str,
|
||||||
if not scriptFileMnemonic in self.listOfPluginsScripts:
|
script_mnemonic: str) -> None:
|
||||||
self.listOfPluginsScripts.append(scriptFileMnemonic)
|
if script_mnemonic in self.plugin_scripts:
|
||||||
|
logging.debug(
|
||||||
|
f"Skipping already loaded {script} for {plugin_mnemonic}"
|
||||||
|
)
|
||||||
|
|
||||||
plugin_path = Path("./resources/plugins",pluginName)
|
self.plugin_scripts.append(script_mnemonic)
|
||||||
|
|
||||||
if scriptFile != None:
|
plugin_path = Path("./resources/plugins", plugin_mnemonic)
|
||||||
scriptFile_path = Path(plugin_path, scriptFile)
|
|
||||||
if scriptFile_path.exists():
|
|
||||||
trigger = TriggerStart(comment="Load " + scriptFileMnemonic)
|
|
||||||
filename = scriptFile_path.resolve()
|
|
||||||
fileref = self.current_mission.map_resource.add_resource_file(filename)
|
|
||||||
trigger.add_action(DoScriptFile(fileref))
|
|
||||||
self.current_mission.triggerrules.triggers.append(trigger)
|
|
||||||
else:
|
|
||||||
logging.error(f"Cannot find script file {scriptFile} for plugin {pluginName}")
|
|
||||||
|
|
||||||
else:
|
script_path = Path(plugin_path, script)
|
||||||
logging.debug(f"Skipping script file {scriptFile} for plugin {pluginName}")
|
if not script_path.exists():
|
||||||
|
logging.error(
|
||||||
|
f"Cannot find {script_path} for plugin {plugin_mnemonic}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
trigger = TriggerStart(comment=f"Load {script_mnemonic}")
|
||||||
|
filename = script_path.resolve()
|
||||||
|
fileref = self.current_mission.map_resource.add_resource_file(filename)
|
||||||
|
trigger.add_action(DoScriptFile(fileref))
|
||||||
|
self.current_mission.triggerrules.triggers.append(trigger)
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
radio_registry = RadioRegistry()
|
radio_registry = RadioRegistry()
|
||||||
@ -334,7 +339,7 @@ class Operation:
|
|||||||
kneeboard_generator.add_flight(flight)
|
kneeboard_generator.add_flight(flight)
|
||||||
if flight.friendly and flight.flight_type in [FlightType.ANTISHIP, FlightType.DEAD, FlightType.SEAD, FlightType.STRIKE]:
|
if flight.friendly and flight.flight_type in [FlightType.ANTISHIP, FlightType.DEAD, FlightType.SEAD, FlightType.STRIKE]:
|
||||||
flightType = flight.flight_type.name
|
flightType = flight.flight_type.name
|
||||||
flightTarget = flight.targetPoint
|
flightTarget = flight.package.target
|
||||||
if flightTarget:
|
if flightTarget:
|
||||||
flightTargetName = None
|
flightTargetName = None
|
||||||
flightTargetType = None
|
flightTargetType = None
|
||||||
@ -453,8 +458,6 @@ dcsLiberation.TargetPoints = {
|
|||||||
self.current_mission.triggerrules.triggers.append(trigger)
|
self.current_mission.triggerrules.triggers.append(trigger)
|
||||||
|
|
||||||
# Inject Plugins Lua Scripts and data
|
# Inject Plugins Lua Scripts and data
|
||||||
self.listOfPluginsScripts = []
|
|
||||||
|
|
||||||
for plugin in LuaPluginManager().getPlugins():
|
for plugin in LuaPluginManager().getPlugins():
|
||||||
plugin.injectScripts(self)
|
plugin.injectScripts(self)
|
||||||
plugin.injectConfiguration(self)
|
plugin.injectConfiguration(self)
|
||||||
|
|||||||
108
gen/aircraft.py
108
gen/aircraft.py
@ -64,7 +64,6 @@ 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.airsupportgen import AirSupport
|
from gen.airsupportgen import AirSupport
|
||||||
from gen.ato import AirTaskingOrder, Package
|
from gen.ato import AirTaskingOrder, Package
|
||||||
from gen.callsigns import create_group_callsign_from_unit
|
from gen.callsigns import create_group_callsign_from_unit
|
||||||
@ -75,11 +74,13 @@ from gen.flights.flight import (
|
|||||||
FlightWaypointType,
|
FlightWaypointType,
|
||||||
)
|
)
|
||||||
from gen.radios import MHz, Radio, RadioFrequency, RadioRegistry, get_radio
|
from gen.radios import MHz, Radio, RadioFrequency, RadioRegistry, get_radio
|
||||||
|
from gen.runways import RunwayData
|
||||||
from theater import TheaterGroundObject
|
from theater import TheaterGroundObject
|
||||||
from theater.controlpoint import ControlPoint, ControlPointType
|
from theater.controlpoint import ControlPoint, ControlPointType
|
||||||
from .conflictgen import Conflict
|
from .conflictgen import Conflict
|
||||||
from .flights.traveltime import PackageWaypointTiming, TotEstimator
|
from .flights.traveltime import PackageWaypointTiming, TotEstimator
|
||||||
from .naming import namegen
|
from .naming import namegen
|
||||||
|
from .runways import RunwayAssigner
|
||||||
|
|
||||||
WARM_START_HELI_AIRSPEED = 120
|
WARM_START_HELI_AIRSPEED = 120
|
||||||
WARM_START_HELI_ALT = 500
|
WARM_START_HELI_ALT = 500
|
||||||
@ -204,6 +205,9 @@ class ChannelAssignment:
|
|||||||
class FlightData:
|
class FlightData:
|
||||||
"""Details of a planned flight."""
|
"""Details of a planned flight."""
|
||||||
|
|
||||||
|
#: The package that the flight belongs to.
|
||||||
|
package: Package
|
||||||
|
|
||||||
flight_type: FlightType
|
flight_type: FlightType
|
||||||
|
|
||||||
#: All units in the flight.
|
#: All units in the flight.
|
||||||
@ -236,14 +240,13 @@ class FlightData:
|
|||||||
#: Map of radio frequencies to their assigned radio and channel, if any.
|
#: Map of radio frequencies to their assigned radio and channel, if any.
|
||||||
frequency_to_channel_map: Dict[RadioFrequency, ChannelAssignment]
|
frequency_to_channel_map: Dict[RadioFrequency, ChannelAssignment]
|
||||||
|
|
||||||
#: Data concerning the target of a CAS/Strike/SEAD flight, or None else
|
def __init__(self, package: Package, flight_type: FlightType,
|
||||||
targetPoint = None
|
units: List[FlyingUnit], size: int, friendly: bool,
|
||||||
|
departure_delay: int, departure: RunwayData,
|
||||||
def __init__(self, flight_type: FlightType, units: List[FlyingUnit],
|
arrival: RunwayData, divert: Optional[RunwayData],
|
||||||
size: int, friendly: bool, departure_delay: int,
|
waypoints: List[FlightWaypoint],
|
||||||
departure: RunwayData, arrival: RunwayData,
|
intra_flight_channel: RadioFrequency) -> None:
|
||||||
divert: Optional[RunwayData], waypoints: List[FlightWaypoint],
|
self.package = package
|
||||||
intra_flight_channel: RadioFrequency, targetPoint: Optional) -> None:
|
|
||||||
self.flight_type = flight_type
|
self.flight_type = flight_type
|
||||||
self.units = units
|
self.units = units
|
||||||
self.size = size
|
self.size = size
|
||||||
@ -256,7 +259,6 @@ class FlightData:
|
|||||||
self.intra_flight_channel = intra_flight_channel
|
self.intra_flight_channel = intra_flight_channel
|
||||||
self.frequency_to_channel_map = {}
|
self.frequency_to_channel_map = {}
|
||||||
self.callsign = create_group_callsign_from_unit(self.units[0])
|
self.callsign = create_group_callsign_from_unit(self.units[0])
|
||||||
self.targetPoint = targetPoint
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def client_units(self) -> List[FlyingUnit]:
|
def client_units(self) -> List[FlyingUnit]:
|
||||||
@ -574,12 +576,12 @@ class AircraftConflictGenerator:
|
|||||||
return StartType.Warm
|
return StartType.Warm
|
||||||
|
|
||||||
def _setup_group(self, group: FlyingGroup, for_task: Type[Task],
|
def _setup_group(self, group: FlyingGroup, for_task: Type[Task],
|
||||||
flight: Flight, dynamic_runways: Dict[str, RunwayData]):
|
package: Package, flight: Flight,
|
||||||
|
dynamic_runways: Dict[str, RunwayData]) -> None:
|
||||||
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]
|
|
||||||
# Clear pylons
|
# Clear pylons
|
||||||
for p in group.units:
|
for p in group.units:
|
||||||
p.pylons.clear()
|
p.pylons.clear()
|
||||||
@ -622,9 +624,12 @@ class AircraftConflictGenerator:
|
|||||||
|
|
||||||
# TODO: Support for different departure/arrival airfields.
|
# TODO: Support for different departure/arrival airfields.
|
||||||
cp = flight.from_cp
|
cp = flight.from_cp
|
||||||
fallback_runway = RunwayData(cp.full_name, runway_name="")
|
fallback_runway = RunwayData(cp.full_name, runway_heading=0,
|
||||||
|
runway_name="")
|
||||||
if cp.cptype == ControlPointType.AIRBASE:
|
if cp.cptype == ControlPointType.AIRBASE:
|
||||||
departure_runway = self.get_preferred_runway(flight.from_cp.airport)
|
assigner = RunwayAssigner(self.game.conditions)
|
||||||
|
departure_runway = assigner.get_preferred_runway(
|
||||||
|
flight.from_cp.airport)
|
||||||
elif cp.is_fleet:
|
elif cp.is_fleet:
|
||||||
departure_runway = dynamic_runways.get(cp.name, fallback_runway)
|
departure_runway = dynamic_runways.get(cp.name, fallback_runway)
|
||||||
else:
|
else:
|
||||||
@ -632,6 +637,7 @@ class AircraftConflictGenerator:
|
|||||||
departure_runway = fallback_runway
|
departure_runway = fallback_runway
|
||||||
|
|
||||||
self.flights.append(FlightData(
|
self.flights.append(FlightData(
|
||||||
|
package=package,
|
||||||
flight_type=flight.flight_type,
|
flight_type=flight.flight_type,
|
||||||
units=group.units,
|
units=group.units,
|
||||||
size=len(group.units),
|
size=len(group.units),
|
||||||
@ -643,8 +649,7 @@ class AircraftConflictGenerator:
|
|||||||
divert=None,
|
divert=None,
|
||||||
# Waypoints are added later, after they've had their TOTs set.
|
# Waypoints are added later, after they've had their TOTs set.
|
||||||
waypoints=[],
|
waypoints=[],
|
||||||
intra_flight_channel=channel,
|
intra_flight_channel=channel
|
||||||
targetPoint=flight.targetPoint,
|
|
||||||
))
|
))
|
||||||
|
|
||||||
# Special case so Su 33 carrier take off
|
# Special case so Su 33 carrier take off
|
||||||
@ -656,22 +661,6 @@ class AircraftConflictGenerator:
|
|||||||
for unit in group.units:
|
for unit in group.units:
|
||||||
unit.fuel = Su_33.fuel_max * 0.8
|
unit.fuel = Su_33.fuel_max * 0.8
|
||||||
|
|
||||||
def get_preferred_runway(self, airport: Airport) -> RunwayData:
|
|
||||||
"""Returns the preferred runway for the given airport.
|
|
||||||
|
|
||||||
Right now we're only selecting runways based on whether or not they have
|
|
||||||
ILS, but we could also choose based on wind conditions, or which
|
|
||||||
direction flight plans should follow.
|
|
||||||
"""
|
|
||||||
runways = list(RunwayData.for_pydcs_airport(airport))
|
|
||||||
for runway in runways:
|
|
||||||
# Prefer any runway with ILS.
|
|
||||||
if runway.ils is not None:
|
|
||||||
return runway
|
|
||||||
# Otherwise we lack the mission information to pick more usefully,
|
|
||||||
# so just use the first runway.
|
|
||||||
return runways[0]
|
|
||||||
|
|
||||||
def _generate_at_airport(self, name: str, side: Country,
|
def _generate_at_airport(self, name: str, side: Country,
|
||||||
unit_type: FlyingType, count: int, start_type: str,
|
unit_type: FlyingType, count: int, start_type: str,
|
||||||
airport: Optional[Airport] = None) -> FlyingGroup:
|
airport: Optional[Airport] = None) -> FlyingGroup:
|
||||||
@ -802,7 +791,7 @@ class AircraftConflictGenerator:
|
|||||||
logging.info(f"Generating flight: {flight.unit_type}")
|
logging.info(f"Generating flight: {flight.unit_type}")
|
||||||
group = self.generate_planned_flight(flight.from_cp, country,
|
group = self.generate_planned_flight(flight.from_cp, country,
|
||||||
flight)
|
flight)
|
||||||
self.setup_flight_group(group, flight, dynamic_runways)
|
self.setup_flight_group(group, package, flight, dynamic_runways)
|
||||||
self.create_waypoints(group, package, flight, timing)
|
self.create_waypoints(group, package, flight, timing)
|
||||||
|
|
||||||
def set_activation_time(self, flight: Flight, group: FlyingGroup,
|
def set_activation_time(self, flight: Flight, group: FlyingGroup,
|
||||||
@ -919,10 +908,11 @@ class AircraftConflictGenerator:
|
|||||||
if flight.unit_type.eplrs:
|
if flight.unit_type.eplrs:
|
||||||
group.points[0].tasks.append(EPLRS(group.id))
|
group.points[0].tasks.append(EPLRS(group.id))
|
||||||
|
|
||||||
def configure_cap(self, group: FlyingGroup, flight: Flight,
|
def configure_cap(self, group: FlyingGroup, package: Package,
|
||||||
|
flight: Flight,
|
||||||
dynamic_runways: Dict[str, RunwayData]) -> None:
|
dynamic_runways: Dict[str, RunwayData]) -> None:
|
||||||
group.task = CAP.name
|
group.task = CAP.name
|
||||||
self._setup_group(group, CAP, flight, dynamic_runways)
|
self._setup_group(group, CAP, package, flight, dynamic_runways)
|
||||||
|
|
||||||
if flight.unit_type not in GUNFIGHTERS:
|
if flight.unit_type not in GUNFIGHTERS:
|
||||||
ammo_type = OptRTBOnOutOfAmmo.Values.AAM
|
ammo_type = OptRTBOnOutOfAmmo.Values.AAM
|
||||||
@ -934,10 +924,11 @@ class AircraftConflictGenerator:
|
|||||||
group.points[0].tasks.append(EngageTargets(max_distance=nm_to_meter(50),
|
group.points[0].tasks.append(EngageTargets(max_distance=nm_to_meter(50),
|
||||||
targets=[Targets.All.Air]))
|
targets=[Targets.All.Air]))
|
||||||
|
|
||||||
def configure_cas(self, group: FlyingGroup, flight: Flight,
|
def configure_cas(self, group: FlyingGroup, package: Package,
|
||||||
|
flight: Flight,
|
||||||
dynamic_runways: Dict[str, RunwayData]) -> None:
|
dynamic_runways: Dict[str, RunwayData]) -> None:
|
||||||
group.task = CAS.name
|
group.task = CAS.name
|
||||||
self._setup_group(group, CAS, flight, dynamic_runways)
|
self._setup_group(group, CAS, package, flight, dynamic_runways)
|
||||||
self.configure_behavior(
|
self.configure_behavior(
|
||||||
group,
|
group,
|
||||||
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
||||||
@ -949,10 +940,11 @@ class AircraftConflictGenerator:
|
|||||||
targets=[Targets.All.GroundUnits.GroundVehicles])
|
targets=[Targets.All.GroundUnits.GroundVehicles])
|
||||||
)
|
)
|
||||||
|
|
||||||
def configure_sead(self, group: FlyingGroup, flight: Flight,
|
def configure_sead(self, group: FlyingGroup, package: Package,
|
||||||
dynamic_runways: Dict[str, RunwayData]) -> None:
|
flight: Flight,
|
||||||
|
dynamic_runways: Dict[str, RunwayData]) -> None:
|
||||||
group.task = SEAD.name
|
group.task = SEAD.name
|
||||||
self._setup_group(group, SEAD, flight, dynamic_runways)
|
self._setup_group(group, SEAD, package, flight, dynamic_runways)
|
||||||
self.configure_behavior(
|
self.configure_behavior(
|
||||||
group,
|
group,
|
||||||
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
||||||
@ -960,33 +952,37 @@ class AircraftConflictGenerator:
|
|||||||
rtb_winchester=OptRTBOnOutOfAmmo.Values.ASM,
|
rtb_winchester=OptRTBOnOutOfAmmo.Values.ASM,
|
||||||
restrict_jettison=True)
|
restrict_jettison=True)
|
||||||
|
|
||||||
def configure_strike(self, group: FlyingGroup, flight: Flight,
|
def configure_strike(self, group: FlyingGroup, package: Package,
|
||||||
|
flight: Flight,
|
||||||
dynamic_runways: Dict[str, RunwayData]) -> None:
|
dynamic_runways: Dict[str, RunwayData]) -> None:
|
||||||
group.task = PinpointStrike.name
|
group.task = PinpointStrike.name
|
||||||
self._setup_group(group, GroundAttack, flight, dynamic_runways)
|
self._setup_group(group, GroundAttack, package, flight, dynamic_runways)
|
||||||
self.configure_behavior(
|
self.configure_behavior(
|
||||||
group,
|
group,
|
||||||
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
||||||
roe=OptROE.Values.OpenFire,
|
roe=OptROE.Values.OpenFire,
|
||||||
restrict_jettison=True)
|
restrict_jettison=True)
|
||||||
|
|
||||||
def configure_anti_ship(self, group: FlyingGroup, flight: Flight,
|
def configure_anti_ship(self, group: FlyingGroup, package: Package,
|
||||||
|
flight: Flight,
|
||||||
dynamic_runways: Dict[str, RunwayData]) -> None:
|
dynamic_runways: Dict[str, RunwayData]) -> None:
|
||||||
group.task = AntishipStrike.name
|
group.task = AntishipStrike.name
|
||||||
self._setup_group(group, AntishipStrike, flight, dynamic_runways)
|
self._setup_group(group, AntishipStrike, package, flight,
|
||||||
|
dynamic_runways)
|
||||||
self.configure_behavior(
|
self.configure_behavior(
|
||||||
group,
|
group,
|
||||||
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
react_on_threat=OptReactOnThreat.Values.EvadeFire,
|
||||||
roe=OptROE.Values.OpenFire,
|
roe=OptROE.Values.OpenFire,
|
||||||
restrict_jettison=True)
|
restrict_jettison=True)
|
||||||
|
|
||||||
def configure_escort(self, group: FlyingGroup, flight: Flight,
|
def configure_escort(self, group: FlyingGroup, package: Package,
|
||||||
|
flight: Flight,
|
||||||
dynamic_runways: Dict[str, RunwayData]) -> None:
|
dynamic_runways: Dict[str, RunwayData]) -> None:
|
||||||
# Escort groups are actually given the CAP task so they can perform the
|
# Escort groups are actually given the CAP task so they can perform the
|
||||||
# Search Then Engage task, which we have to use instead of the Escort
|
# Search Then Engage task, which we have to use instead of the Escort
|
||||||
# task for the reasons explained in JoinPointBuilder.
|
# task for the reasons explained in JoinPointBuilder.
|
||||||
group.task = CAP.name
|
group.task = CAP.name
|
||||||
self._setup_group(group, CAP, flight, dynamic_runways)
|
self._setup_group(group, CAP, package, flight, dynamic_runways)
|
||||||
self.configure_behavior(group, roe=OptROE.Values.OpenFire,
|
self.configure_behavior(group, roe=OptROE.Values.OpenFire,
|
||||||
restrict_jettison=True)
|
restrict_jettison=True)
|
||||||
|
|
||||||
@ -995,22 +991,23 @@ class AircraftConflictGenerator:
|
|||||||
logging.error(f"Unhandled flight type: {flight.flight_type.name}")
|
logging.error(f"Unhandled flight type: {flight.flight_type.name}")
|
||||||
self.configure_behavior(group)
|
self.configure_behavior(group)
|
||||||
|
|
||||||
def setup_flight_group(self, group: FlyingGroup, flight: Flight,
|
def setup_flight_group(self, group: FlyingGroup, package: Package,
|
||||||
|
flight: Flight,
|
||||||
dynamic_runways: Dict[str, RunwayData]) -> None:
|
dynamic_runways: Dict[str, RunwayData]) -> None:
|
||||||
flight_type = flight.flight_type
|
flight_type = flight.flight_type
|
||||||
if flight_type in [FlightType.BARCAP, FlightType.TARCAP,
|
if flight_type in [FlightType.BARCAP, FlightType.TARCAP,
|
||||||
FlightType.INTERCEPTION]:
|
FlightType.INTERCEPTION]:
|
||||||
self.configure_cap(group, flight, dynamic_runways)
|
self.configure_cap(group, package, flight, dynamic_runways)
|
||||||
elif flight_type in [FlightType.CAS, FlightType.BAI]:
|
elif flight_type in [FlightType.CAS, FlightType.BAI]:
|
||||||
self.configure_cas(group, flight, dynamic_runways)
|
self.configure_cas(group, package, flight, dynamic_runways)
|
||||||
elif flight_type in [FlightType.SEAD, FlightType.DEAD]:
|
elif flight_type in [FlightType.SEAD, FlightType.DEAD]:
|
||||||
self.configure_sead(group, flight, dynamic_runways)
|
self.configure_sead(group, package, flight, dynamic_runways)
|
||||||
elif flight_type in [FlightType.STRIKE]:
|
elif flight_type in [FlightType.STRIKE]:
|
||||||
self.configure_strike(group, flight, dynamic_runways)
|
self.configure_strike(group, package, flight, dynamic_runways)
|
||||||
elif flight_type in [FlightType.ANTISHIP]:
|
elif flight_type in [FlightType.ANTISHIP]:
|
||||||
self.configure_anti_ship(group, flight, dynamic_runways)
|
self.configure_anti_ship(group, package, flight, dynamic_runways)
|
||||||
elif flight_type == FlightType.ESCORT:
|
elif flight_type == FlightType.ESCORT:
|
||||||
self.configure_escort(group, flight, dynamic_runways)
|
self.configure_escort(group, package, flight, dynamic_runways)
|
||||||
else:
|
else:
|
||||||
self.configure_unknown_task(group, flight)
|
self.configure_unknown_task(group, flight)
|
||||||
|
|
||||||
@ -1301,8 +1298,9 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
|
|||||||
pattern=OrbitAction.OrbitPattern.RaceTrack
|
pattern=OrbitAction.OrbitPattern.RaceTrack
|
||||||
))
|
))
|
||||||
|
|
||||||
self.set_waypoint_tot(waypoint, self.timing.race_track_start)
|
self.set_waypoint_tot(waypoint,
|
||||||
racetrack.stop_after_time(self.timing.race_track_end)
|
self.timing.race_track_start(self.flight))
|
||||||
|
racetrack.stop_after_time(self.timing.race_track_end(self.flight))
|
||||||
waypoint.add_task(racetrack)
|
waypoint.add_task(racetrack)
|
||||||
return waypoint
|
return waypoint
|
||||||
|
|
||||||
|
|||||||
@ -3,11 +3,11 @@
|
|||||||
Remove once https://github.com/pydcs/dcs/issues/69 tracks getting the missing
|
Remove once https://github.com/pydcs/dcs/issues/69 tracks getting the missing
|
||||||
data added to pydcs. Until then, missing data can be manually filled in here.
|
data added to pydcs. Until then, missing data can be manually filled in here.
|
||||||
"""
|
"""
|
||||||
from dataclasses import dataclass, field
|
from __future__ import annotations
|
||||||
import logging
|
|
||||||
from typing import Dict, Iterator, Optional, Tuple
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Dict, Optional, Tuple
|
||||||
|
|
||||||
from dcs.terrain.terrain import Airport
|
|
||||||
from .radios import MHz, RadioFrequency
|
from .radios import MHz, RadioFrequency
|
||||||
from .tacan import TacanBand, TacanChannel
|
from .tacan import TacanBand, TacanChannel
|
||||||
|
|
||||||
@ -1503,61 +1503,3 @@ AIRFIELD_DATA = {
|
|||||||
atc=AtcData(MHz(3, 775), MHz(118, 50), MHz(38, 450), MHz(250, 50)),
|
atc=AtcData(MHz(3, 775), MHz(118, 50), MHz(38, 450), MHz(250, 50)),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class RunwayData:
|
|
||||||
airfield_name: str
|
|
||||||
runway_name: str
|
|
||||||
atc: Optional[RadioFrequency] = None
|
|
||||||
tacan: Optional[TacanChannel] = None
|
|
||||||
tacan_callsign: Optional[str] = None
|
|
||||||
ils: Optional[RadioFrequency] = None
|
|
||||||
icls: Optional[int] = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def for_airfield(cls, airport: Airport, runway: str) -> "RunwayData":
|
|
||||||
"""Creates RunwayData for the given runway of an airfield.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
airport: The airfield the runway belongs to.
|
|
||||||
runway: Identifier of the runway to use. e.g. "03" or "20L".
|
|
||||||
"""
|
|
||||||
atc: Optional[RadioFrequency] = None
|
|
||||||
tacan: Optional[TacanChannel] = None
|
|
||||||
tacan_callsign: Optional[str] = None
|
|
||||||
ils: Optional[RadioFrequency] = None
|
|
||||||
try:
|
|
||||||
airfield = AIRFIELD_DATA[airport.name]
|
|
||||||
if airfield.atc is not None:
|
|
||||||
atc = airfield.atc.uhf
|
|
||||||
else:
|
|
||||||
atc = None
|
|
||||||
tacan = airfield.tacan
|
|
||||||
tacan_callsign = airfield.tacan_callsign
|
|
||||||
ils = airfield.ils_freq(runway)
|
|
||||||
except KeyError:
|
|
||||||
logging.warning(f"No airfield data for {airport.name}")
|
|
||||||
return cls(
|
|
||||||
airfield_name=airport.name,
|
|
||||||
runway_name=runway,
|
|
||||||
atc=atc,
|
|
||||||
tacan=tacan,
|
|
||||||
tacan_callsign=tacan_callsign,
|
|
||||||
ils=ils
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def for_pydcs_airport(cls, airport: Airport) -> Iterator["RunwayData"]:
|
|
||||||
for runway in airport.runways:
|
|
||||||
runway_number = runway.heading // 10
|
|
||||||
runway_side = ["", "L", "R"][runway.leftright]
|
|
||||||
runway_name = f"{runway_number:02}{runway_side}"
|
|
||||||
yield cls.for_airfield(airport, runway_name)
|
|
||||||
|
|
||||||
# pydcs only exposes one runway per physical runway, so to expose
|
|
||||||
# both sides of the runway we need to generate the other.
|
|
||||||
runway_number = ((runway.heading + 180) % 360) // 10
|
|
||||||
runway_side = ["", "R", "L"][runway.leftright]
|
|
||||||
runway_name = f"{runway_number:02}{runway_side}"
|
|
||||||
yield cls.for_airfield(airport, runway_name)
|
|
||||||
|
|||||||
@ -1,19 +1,20 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import random
|
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from game import db
|
|
||||||
from dcs.mission import Mission
|
from dcs.mission import Mission
|
||||||
|
|
||||||
|
from game import db
|
||||||
from .aircraft import FlightData
|
from .aircraft import FlightData
|
||||||
from .airfields import RunwayData
|
|
||||||
from .airsupportgen import AwacsInfo, TankerInfo
|
from .airsupportgen import AwacsInfo, TankerInfo
|
||||||
from .armor import JtacInfo
|
from .armor import JtacInfo
|
||||||
from .conflictgen import Conflict
|
from .conflictgen import Conflict
|
||||||
from .ground_forces.combat_stance import CombatStance
|
from .ground_forces.combat_stance import CombatStance
|
||||||
from .radios import RadioFrequency
|
from .radios import RadioFrequency
|
||||||
|
from .runways import RunwayData
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@ -206,10 +206,9 @@ class PackageBuilder:
|
|||||||
if assignment is None:
|
if assignment is None:
|
||||||
return False
|
return False
|
||||||
airfield, aircraft = assignment
|
airfield, aircraft = assignment
|
||||||
flight = Flight(aircraft, plan.num_aircraft, airfield, plan.task,
|
flight = Flight(self.package, aircraft, plan.num_aircraft, airfield,
|
||||||
self.start_type)
|
plan.task, self.start_type)
|
||||||
self.package.add_flight(flight)
|
self.package.add_flight(flight)
|
||||||
flight.targetPoint = self.package.target
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def build(self) -> Package:
|
def build(self) -> Package:
|
||||||
@ -222,7 +221,7 @@ class PackageBuilder:
|
|||||||
for flight in flights:
|
for flight in flights:
|
||||||
self.global_inventory.return_from_flight(flight)
|
self.global_inventory.return_from_flight(flight)
|
||||||
self.package.remove_flight(flight)
|
self.package.remove_flight(flight)
|
||||||
flight.targetPoint = None
|
|
||||||
|
|
||||||
class ObjectiveFinder:
|
class ObjectiveFinder:
|
||||||
"""Identifies potential objectives for the mission planner."""
|
"""Identifies potential objectives for the mission planner."""
|
||||||
|
|||||||
@ -19,11 +19,14 @@ from dcs.planes import (
|
|||||||
A_10C_2,
|
A_10C_2,
|
||||||
A_20G,
|
A_20G,
|
||||||
B_17G,
|
B_17G,
|
||||||
|
B_1B,
|
||||||
|
B_52H,
|
||||||
Bf_109K_4,
|
Bf_109K_4,
|
||||||
C_101CC,
|
C_101CC,
|
||||||
FA_18C_hornet,
|
FA_18C_hornet,
|
||||||
FW_190A8,
|
FW_190A8,
|
||||||
FW_190D9,
|
FW_190D9,
|
||||||
|
F_117A,
|
||||||
F_14B,
|
F_14B,
|
||||||
F_15C,
|
F_15C,
|
||||||
F_15E,
|
F_15E,
|
||||||
@ -71,6 +74,9 @@ from dcs.planes import (
|
|||||||
Su_34,
|
Su_34,
|
||||||
Tornado_GR4,
|
Tornado_GR4,
|
||||||
Tornado_IDS,
|
Tornado_IDS,
|
||||||
|
Tu_160,
|
||||||
|
Tu_22M3,
|
||||||
|
Tu_95MS,
|
||||||
WingLoong_I,
|
WingLoong_I,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -226,6 +232,8 @@ CAS_CAPABLE = [
|
|||||||
F_16C_50,
|
F_16C_50,
|
||||||
FA_18C_hornet,
|
FA_18C_hornet,
|
||||||
|
|
||||||
|
B_1B,
|
||||||
|
|
||||||
Tornado_IDS,
|
Tornado_IDS,
|
||||||
Tornado_GR4,
|
Tornado_GR4,
|
||||||
|
|
||||||
@ -367,6 +375,10 @@ STRIKE_CAPABLE = [
|
|||||||
Su_25T,
|
Su_25T,
|
||||||
Su_34,
|
Su_34,
|
||||||
|
|
||||||
|
Tu_160,
|
||||||
|
Tu_22M3,
|
||||||
|
Tu_95MS,
|
||||||
|
|
||||||
JF_17,
|
JF_17,
|
||||||
|
|
||||||
M_2000C,
|
M_2000C,
|
||||||
@ -384,6 +396,10 @@ STRIKE_CAPABLE = [
|
|||||||
F_16C_50,
|
F_16C_50,
|
||||||
FA_18C_hornet,
|
FA_18C_hornet,
|
||||||
|
|
||||||
|
B_1B,
|
||||||
|
B_52H,
|
||||||
|
F_117A,
|
||||||
|
|
||||||
Tornado_IDS,
|
Tornado_IDS,
|
||||||
Tornado_GR4,
|
Tornado_GR4,
|
||||||
|
|
||||||
@ -413,11 +429,16 @@ STRIKE_CAPABLE = [
|
|||||||
|
|
||||||
STRIKE_PREFERRED = [
|
STRIKE_PREFERRED = [
|
||||||
AJS37,
|
AJS37,
|
||||||
F_15E,
|
|
||||||
Tornado_GR4,
|
|
||||||
|
|
||||||
A_20G,
|
A_20G,
|
||||||
B_17G,
|
B_17G,
|
||||||
|
B_1B,
|
||||||
|
B_52H,
|
||||||
|
F_117A,
|
||||||
|
F_15E,
|
||||||
|
Tornado_GR4,
|
||||||
|
Tu_160,
|
||||||
|
Tu_22M3,
|
||||||
|
Tu_95MS,
|
||||||
]
|
]
|
||||||
|
|
||||||
ANTISHIP_CAPABLE = [
|
ANTISHIP_CAPABLE = [
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Dict, Iterable, List, Optional
|
from typing import Dict, Iterable, List, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
from dcs.mapping import Point
|
from dcs.mapping import Point
|
||||||
from dcs.point import MovingPoint, PointAction
|
from dcs.point import MovingPoint, PointAction
|
||||||
@ -8,6 +10,9 @@ from dcs.unittype import UnitType
|
|||||||
from game import db
|
from game import db
|
||||||
from theater.controlpoint import ControlPoint, MissionTarget
|
from theater.controlpoint import ControlPoint, MissionTarget
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from gen.ato import Package
|
||||||
|
|
||||||
|
|
||||||
class FlightType(Enum):
|
class FlightType(Enum):
|
||||||
CAP = 0 # Do not use. Use BARCAP or TARCAP.
|
CAP = 0 # Do not use. Use BARCAP or TARCAP.
|
||||||
@ -138,10 +143,11 @@ class Flight:
|
|||||||
use_custom_loadout = False
|
use_custom_loadout = False
|
||||||
preset_loadout_name = ""
|
preset_loadout_name = ""
|
||||||
group = False # Contains DCS Mission group data after mission has been generated
|
group = False # Contains DCS Mission group data after mission has been generated
|
||||||
targetPoint = None # Contains either None or a Strike/SEAD target point location
|
|
||||||
|
|
||||||
def __init__(self, unit_type: UnitType, count: int, from_cp: ControlPoint,
|
def __init__(self, package: Package, unit_type: UnitType, count: int,
|
||||||
flight_type: FlightType, start_type: str) -> None:
|
from_cp: ControlPoint, flight_type: FlightType,
|
||||||
|
start_type: str) -> None:
|
||||||
|
self.package = package
|
||||||
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
|
||||||
|
|||||||
@ -132,7 +132,7 @@ class FlightPlanBuilder:
|
|||||||
if not isinstance(location, TheaterGroundObject):
|
if not isinstance(location, TheaterGroundObject):
|
||||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||||
|
|
||||||
builder = WaypointBuilder(flight, self.doctrine)
|
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
|
||||||
builder.ascent(flight.from_cp)
|
builder.ascent(flight.from_cp)
|
||||||
builder.hold(self._hold_point(flight))
|
builder.hold(self._hold_point(flight))
|
||||||
builder.join(self.package.waypoints.join)
|
builder.join(self.package.waypoints.join)
|
||||||
@ -157,11 +157,7 @@ class FlightPlanBuilder:
|
|||||||
if building.is_dead:
|
if building.is_dead:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
builder.strike_point(
|
builder.strike_point(building, building.category, location)
|
||||||
building,
|
|
||||||
f"{building.obj_name} {building.category}",
|
|
||||||
location
|
|
||||||
)
|
|
||||||
|
|
||||||
builder.egress(self.package.waypoints.egress, location)
|
builder.egress(self.package.waypoints.egress, location)
|
||||||
builder.split(self.package.waypoints.split)
|
builder.split(self.package.waypoints.split)
|
||||||
@ -222,7 +218,7 @@ class FlightPlanBuilder:
|
|||||||
)
|
)
|
||||||
start = end.point_from_heading(heading - 180, diameter)
|
start = end.point_from_heading(heading - 180, diameter)
|
||||||
|
|
||||||
builder = WaypointBuilder(flight, self.doctrine)
|
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
|
||||||
builder.ascent(flight.from_cp)
|
builder.ascent(flight.from_cp)
|
||||||
builder.race_track(start, end, patrol_alt)
|
builder.race_track(start, end, patrol_alt)
|
||||||
builder.rtb(flight.from_cp)
|
builder.rtb(flight.from_cp)
|
||||||
@ -264,7 +260,7 @@ class FlightPlanBuilder:
|
|||||||
orbit1p = orbit_center.point_from_heading(heading + 180, radius)
|
orbit1p = orbit_center.point_from_heading(heading + 180, radius)
|
||||||
|
|
||||||
# Create points
|
# Create points
|
||||||
builder = WaypointBuilder(flight, self.doctrine)
|
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
|
||||||
builder.ascent(flight.from_cp)
|
builder.ascent(flight.from_cp)
|
||||||
builder.hold(self._hold_point(flight))
|
builder.hold(self._hold_point(flight))
|
||||||
builder.join(self.package.waypoints.join)
|
builder.join(self.package.waypoints.join)
|
||||||
@ -290,7 +286,7 @@ class FlightPlanBuilder:
|
|||||||
if custom_targets is None:
|
if custom_targets is None:
|
||||||
custom_targets = []
|
custom_targets = []
|
||||||
|
|
||||||
builder = WaypointBuilder(flight, self.doctrine)
|
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
|
||||||
builder.ascent(flight.from_cp)
|
builder.ascent(flight.from_cp)
|
||||||
builder.hold(self._hold_point(flight))
|
builder.hold(self._hold_point(flight))
|
||||||
builder.join(self.package.waypoints.join)
|
builder.join(self.package.waypoints.join)
|
||||||
@ -328,7 +324,7 @@ class FlightPlanBuilder:
|
|||||||
def generate_escort(self, flight: Flight) -> None:
|
def generate_escort(self, flight: Flight) -> None:
|
||||||
assert self.package.waypoints is not None
|
assert self.package.waypoints is not None
|
||||||
|
|
||||||
builder = WaypointBuilder(flight, self.doctrine)
|
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
|
||||||
builder.ascent(flight.from_cp)
|
builder.ascent(flight.from_cp)
|
||||||
builder.hold(self._hold_point(flight))
|
builder.hold(self._hold_point(flight))
|
||||||
builder.join(self.package.waypoints.join)
|
builder.join(self.package.waypoints.join)
|
||||||
@ -351,9 +347,6 @@ class FlightPlanBuilder:
|
|||||||
if not isinstance(location, FrontLine):
|
if not isinstance(location, FrontLine):
|
||||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
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(
|
ingress, heading, distance = Conflict.frontline_vector(
|
||||||
location.control_points[0], location.control_points[1],
|
location.control_points[0], location.control_points[1],
|
||||||
self.game.theater
|
self.game.theater
|
||||||
@ -361,15 +354,15 @@ class FlightPlanBuilder:
|
|||||||
center = ingress.point_from_heading(heading, distance / 2)
|
center = ingress.point_from_heading(heading, distance / 2)
|
||||||
egress = ingress.point_from_heading(heading, distance)
|
egress = ingress.point_from_heading(heading, distance)
|
||||||
|
|
||||||
builder = WaypointBuilder(flight, self.doctrine)
|
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
|
||||||
builder.ascent(flight.from_cp, is_helo)
|
builder.ascent(flight.from_cp)
|
||||||
builder.hold(self._hold_point(flight))
|
builder.hold(self._hold_point(flight))
|
||||||
builder.join(self.package.waypoints.join)
|
builder.join(self.package.waypoints.join)
|
||||||
builder.ingress_cas(ingress, location)
|
builder.ingress_cas(ingress, location)
|
||||||
builder.cas(center, cap_alt)
|
builder.cas(center)
|
||||||
builder.egress(egress, location)
|
builder.egress(egress, location)
|
||||||
builder.split(self.package.waypoints.split)
|
builder.split(self.package.waypoints.split)
|
||||||
builder.rtb(flight.from_cp, is_helo)
|
builder.rtb(flight.from_cp)
|
||||||
|
|
||||||
flight.points = builder.build()
|
flight.points = builder.build()
|
||||||
|
|
||||||
@ -382,7 +375,7 @@ class FlightPlanBuilder:
|
|||||||
flight: The flight to generate the descend point for.
|
flight: The flight to generate the descend point for.
|
||||||
departure: Departure airfield or carrier.
|
departure: Departure airfield or carrier.
|
||||||
"""
|
"""
|
||||||
builder = WaypointBuilder(flight, self.doctrine)
|
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
|
||||||
builder.ascent(departure)
|
builder.ascent(departure)
|
||||||
return builder.build()[0]
|
return builder.build()[0]
|
||||||
|
|
||||||
@ -394,7 +387,7 @@ class FlightPlanBuilder:
|
|||||||
flight: The flight to generate the descend point for.
|
flight: The flight to generate the descend point for.
|
||||||
arrival: Arrival airfield or carrier.
|
arrival: Arrival airfield or carrier.
|
||||||
"""
|
"""
|
||||||
builder = WaypointBuilder(flight, self.doctrine)
|
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
|
||||||
builder.descent(arrival)
|
builder.descent(arrival)
|
||||||
return builder.build()[0]
|
return builder.build()[0]
|
||||||
|
|
||||||
@ -406,7 +399,7 @@ class FlightPlanBuilder:
|
|||||||
flight: The flight to generate the landing waypoint for.
|
flight: The flight to generate the landing waypoint for.
|
||||||
arrival: Arrival airfield or carrier.
|
arrival: Arrival airfield or carrier.
|
||||||
"""
|
"""
|
||||||
builder = WaypointBuilder(flight, self.doctrine)
|
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
|
||||||
builder.land(arrival)
|
builder.land(arrival)
|
||||||
return builder.build()[0]
|
return builder.build()[0]
|
||||||
|
|
||||||
|
|||||||
@ -27,21 +27,22 @@ INGRESS_TYPES = {
|
|||||||
FlightWaypointType.INGRESS_STRIKE,
|
FlightWaypointType.INGRESS_STRIKE,
|
||||||
}
|
}
|
||||||
|
|
||||||
IP_TYPES = {
|
|
||||||
FlightWaypointType.INGRESS_CAS,
|
|
||||||
FlightWaypointType.INGRESS_ESCORT,
|
|
||||||
FlightWaypointType.INGRESS_SEAD,
|
|
||||||
FlightWaypointType.INGRESS_STRIKE,
|
|
||||||
FlightWaypointType.PATROL_TRACK,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class GroundSpeed:
|
class GroundSpeed:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def mission_speed(package: Package) -> int:
|
def mission_speed(package: Package) -> int:
|
||||||
speeds = set()
|
speeds = set()
|
||||||
for flight in package.flights:
|
for flight in package.flights:
|
||||||
waypoint = flight.waypoint_with_type(IP_TYPES)
|
# Find a waypoint that matches the mission start waypoint and use
|
||||||
|
# that for the altitude of the mission. That may not be true for the
|
||||||
|
# whole mission, but it's probably good enough for now.
|
||||||
|
waypoint = flight.waypoint_with_type({
|
||||||
|
FlightWaypointType.INGRESS_CAS,
|
||||||
|
FlightWaypointType.INGRESS_ESCORT,
|
||||||
|
FlightWaypointType.INGRESS_SEAD,
|
||||||
|
FlightWaypointType.INGRESS_STRIKE,
|
||||||
|
FlightWaypointType.PATROL_TRACK,
|
||||||
|
})
|
||||||
if waypoint is None:
|
if waypoint is None:
|
||||||
logging.error(f"Could not find ingress point for {flight}.")
|
logging.error(f"Could not find ingress point for {flight}.")
|
||||||
if flight.points:
|
if flight.points:
|
||||||
@ -152,8 +153,10 @@ class TotEstimator:
|
|||||||
# Takeoff immediately.
|
# Takeoff immediately.
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if self.package.primary_task == FlightType.BARCAP:
|
# BARCAP flights do not coordinate with the rest of the package on join
|
||||||
start_time = self.timing.race_track_start
|
# or ingress points.
|
||||||
|
if flight.flight_type == FlightType.BARCAP:
|
||||||
|
start_time = self.timing.race_track_start(flight)
|
||||||
else:
|
else:
|
||||||
start_time = self.timing.join
|
start_time = self.timing.join
|
||||||
return start_time - travel_time - self.HOLD_TIME
|
return start_time - travel_time - self.HOLD_TIME
|
||||||
@ -166,7 +169,9 @@ class TotEstimator:
|
|||||||
def earliest_tot_for_flight(self, flight: Flight) -> int:
|
def earliest_tot_for_flight(self, flight: Flight) -> int:
|
||||||
"""Estimate fastest time from mission start to the target position.
|
"""Estimate fastest time from mission start to the target position.
|
||||||
|
|
||||||
For CAP missions, this is time to race track start.
|
For BARCAP flights, this is time to race track start. This ensures that
|
||||||
|
they are on station at the same time any other package members reach
|
||||||
|
their ingress point.
|
||||||
|
|
||||||
For other mission types this is the time to the mission target.
|
For other mission types this is the time to the mission target.
|
||||||
|
|
||||||
@ -177,27 +182,34 @@ class TotEstimator:
|
|||||||
The earliest possible TOT for the given flight in seconds. Returns 0
|
The earliest possible TOT for the given flight in seconds. Returns 0
|
||||||
if an ingress point cannot be found.
|
if an ingress point cannot be found.
|
||||||
"""
|
"""
|
||||||
time_to_ingress = self.estimate_waypoints_to_target(flight, IP_TYPES)
|
if flight.flight_type == FlightType.BARCAP:
|
||||||
if time_to_ingress is None:
|
time_to_target = self.estimate_waypoints_to_target(flight, {
|
||||||
logging.warning(
|
FlightWaypointType.PATROL_TRACK
|
||||||
f"Found no ingress types. Cannot estimate TOT for {flight}")
|
})
|
||||||
# Return 0 so this flight's travel time does not affect the rest of
|
if time_to_target is None:
|
||||||
# the package.
|
logging.warning(
|
||||||
return 0
|
f"Found no race track. Cannot estimate TOT for {flight}")
|
||||||
|
# Return 0 so this flight's travel time does not affect the rest
|
||||||
if self.package.primary_task == FlightType.BARCAP:
|
# of the package.
|
||||||
# The racetrack start *is* the target. The package target is the
|
return 0
|
||||||
# protected objective.
|
|
||||||
time_to_target = 0
|
|
||||||
else:
|
else:
|
||||||
|
time_to_ingress = self.estimate_waypoints_to_target(
|
||||||
|
flight, INGRESS_TYPES
|
||||||
|
)
|
||||||
|
if time_to_ingress is None:
|
||||||
|
logging.warning(
|
||||||
|
f"Found no ingress types. Cannot estimate TOT for {flight}")
|
||||||
|
# Return 0 so this flight's travel time does not affect the rest
|
||||||
|
# of the package.
|
||||||
|
return 0
|
||||||
|
|
||||||
assert self.package.waypoints is not None
|
assert self.package.waypoints is not None
|
||||||
time_to_target = TravelTime.between_points(
|
time_to_target = time_to_ingress + TravelTime.between_points(
|
||||||
self.package.waypoints.ingress, self.package.target.position,
|
self.package.waypoints.ingress, self.package.target.position,
|
||||||
GroundSpeed.mission_speed(self.package))
|
GroundSpeed.mission_speed(self.package))
|
||||||
return sum([
|
return sum([
|
||||||
self.estimate_startup(flight),
|
self.estimate_startup(flight),
|
||||||
self.estimate_ground_ops(flight),
|
self.estimate_ground_ops(flight),
|
||||||
time_to_ingress,
|
|
||||||
time_to_target,
|
time_to_target,
|
||||||
])
|
])
|
||||||
|
|
||||||
@ -281,18 +293,22 @@ class PackageWaypointTiming:
|
|||||||
assert self.package.time_over_target is not None
|
assert self.package.time_over_target is not None
|
||||||
return self.package.time_over_target
|
return self.package.time_over_target
|
||||||
|
|
||||||
@property
|
def race_track_start(self, flight: Flight) -> int:
|
||||||
def race_track_start(self) -> int:
|
if flight.flight_type == FlightType.BARCAP:
|
||||||
if self.package.primary_task == FlightType.BARCAP:
|
return self.target
|
||||||
return self.package.time_over_target
|
|
||||||
else:
|
else:
|
||||||
|
# The only other type that (currently) uses race tracks is TARCAP,
|
||||||
|
# which is sort of in need of cleanup. TARCAP is only valid on front
|
||||||
|
# lines and they participate in join points and patrol between the
|
||||||
|
# ingress and egress points rather than on a race track actually
|
||||||
|
# pointed at the enemy.
|
||||||
return self.ingress
|
return self.ingress
|
||||||
|
|
||||||
@property
|
def race_track_end(self, flight: Flight) -> int:
|
||||||
def race_track_end(self) -> int:
|
if flight.flight_type == FlightType.BARCAP:
|
||||||
if self.package.primary_task == FlightType.BARCAP:
|
|
||||||
return self.target + CAP_DURATION * 60
|
return self.target + CAP_DURATION * 60
|
||||||
else:
|
else:
|
||||||
|
# For TARCAP. See the explanation in race_track_start.
|
||||||
return self.egress
|
return self.egress
|
||||||
|
|
||||||
def push_time(self, flight: Flight, hold_point: FlightWaypoint) -> int:
|
def push_time(self, flight: Flight, hold_point: FlightWaypoint) -> int:
|
||||||
@ -303,7 +319,8 @@ class PackageWaypointTiming:
|
|||||||
GroundSpeed.for_flight(flight, hold_point.alt)
|
GroundSpeed.for_flight(flight, hold_point.alt)
|
||||||
)
|
)
|
||||||
|
|
||||||
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[int]:
|
def tot_for_waypoint(self, flight: Flight,
|
||||||
|
waypoint: FlightWaypoint) -> Optional[int]:
|
||||||
target_types = (
|
target_types = (
|
||||||
FlightWaypointType.TARGET_GROUP_LOC,
|
FlightWaypointType.TARGET_GROUP_LOC,
|
||||||
FlightWaypointType.TARGET_POINT,
|
FlightWaypointType.TARGET_POINT,
|
||||||
@ -321,7 +338,7 @@ class PackageWaypointTiming:
|
|||||||
elif waypoint.waypoint_type == FlightWaypointType.SPLIT:
|
elif waypoint.waypoint_type == FlightWaypointType.SPLIT:
|
||||||
return self.split
|
return self.split
|
||||||
elif waypoint.waypoint_type == FlightWaypointType.PATROL_TRACK:
|
elif waypoint.waypoint_type == FlightWaypointType.PATROL_TRACK:
|
||||||
return self.race_track_start
|
return self.race_track_start(flight)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def depart_time_for_waypoint(self, waypoint: FlightWaypoint,
|
def depart_time_for_waypoint(self, waypoint: FlightWaypoint,
|
||||||
@ -329,7 +346,7 @@ class PackageWaypointTiming:
|
|||||||
if waypoint.waypoint_type == FlightWaypointType.LOITER:
|
if waypoint.waypoint_type == FlightWaypointType.LOITER:
|
||||||
return self.push_time(flight, waypoint)
|
return self.push_time(flight, waypoint)
|
||||||
elif waypoint.waypoint_type == FlightWaypointType.PATROL:
|
elif waypoint.waypoint_type == FlightWaypointType.PATROL:
|
||||||
return self.race_track_end
|
return self.race_track_end(flight)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -7,29 +7,35 @@ from dcs.unit import Unit
|
|||||||
|
|
||||||
from game.data.doctrine import Doctrine
|
from game.data.doctrine import Doctrine
|
||||||
from game.utils import nm_to_meter
|
from game.utils import nm_to_meter
|
||||||
|
from game.weather import Conditions
|
||||||
from theater import ControlPoint, MissionTarget, TheaterGroundObject
|
from theater import ControlPoint, MissionTarget, TheaterGroundObject
|
||||||
from .flight import Flight, FlightWaypoint, FlightWaypointType
|
from .flight import Flight, FlightWaypoint, FlightWaypointType
|
||||||
|
from ..runways import RunwayAssigner
|
||||||
|
|
||||||
|
|
||||||
class WaypointBuilder:
|
class WaypointBuilder:
|
||||||
def __init__(self, flight: Flight, doctrine: Doctrine) -> None:
|
def __init__(self, conditions: Conditions, flight: Flight,
|
||||||
|
doctrine: Doctrine) -> None:
|
||||||
|
self.conditions = conditions
|
||||||
self.flight = flight
|
self.flight = flight
|
||||||
self.doctrine = doctrine
|
self.doctrine = doctrine
|
||||||
self.waypoints: List[FlightWaypoint] = []
|
self.waypoints: List[FlightWaypoint] = []
|
||||||
self.ingress_point: Optional[FlightWaypoint] = None
|
self.ingress_point: Optional[FlightWaypoint] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_helo(self) -> bool:
|
||||||
|
return getattr(self.flight.unit_type, "helicopter", False)
|
||||||
|
|
||||||
def build(self) -> List[FlightWaypoint]:
|
def build(self) -> List[FlightWaypoint]:
|
||||||
return self.waypoints
|
return self.waypoints
|
||||||
|
|
||||||
def ascent(self, departure: ControlPoint, is_helo: bool = False) -> None:
|
def ascent(self, departure: ControlPoint) -> None:
|
||||||
"""Create ascent waypoint for the given departure airfield or carrier.
|
"""Create ascent waypoint for the given departure airfield or carrier.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
departure: Departure airfield or carrier.
|
departure: Departure airfield or carrier.
|
||||||
is_helo: True if the flight is a helicopter.
|
|
||||||
"""
|
"""
|
||||||
# TODO: Pick runway based on wind direction.
|
heading = RunwayAssigner(self.conditions).takeoff_heading(departure)
|
||||||
heading = departure.heading
|
|
||||||
position = departure.position.point_from_heading(
|
position = departure.position.point_from_heading(
|
||||||
heading, nm_to_meter(5)
|
heading, nm_to_meter(5)
|
||||||
)
|
)
|
||||||
@ -37,7 +43,7 @@ class WaypointBuilder:
|
|||||||
FlightWaypointType.ASCEND_POINT,
|
FlightWaypointType.ASCEND_POINT,
|
||||||
position.x,
|
position.x,
|
||||||
position.y,
|
position.y,
|
||||||
500 if is_helo else self.doctrine.pattern_altitude
|
500 if self.is_helo else self.doctrine.pattern_altitude
|
||||||
)
|
)
|
||||||
waypoint.name = "ASCEND"
|
waypoint.name = "ASCEND"
|
||||||
waypoint.alt_type = "RADIO"
|
waypoint.alt_type = "RADIO"
|
||||||
@ -45,16 +51,15 @@ class WaypointBuilder:
|
|||||||
waypoint.pretty_name = "Ascend"
|
waypoint.pretty_name = "Ascend"
|
||||||
self.waypoints.append(waypoint)
|
self.waypoints.append(waypoint)
|
||||||
|
|
||||||
def descent(self, arrival: ControlPoint, is_helo: bool = False) -> None:
|
def descent(self, arrival: ControlPoint) -> None:
|
||||||
"""Create descent waypoint for the given arrival airfield or carrier.
|
"""Create descent waypoint for the given arrival airfield or carrier.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
arrival: Arrival airfield or carrier.
|
arrival: Arrival airfield or carrier.
|
||||||
is_helo: True if the flight is a helicopter.
|
|
||||||
"""
|
"""
|
||||||
# TODO: Pick runway based on wind direction.
|
landing_heading = RunwayAssigner(self.conditions).landing_heading(
|
||||||
# ControlPoint.heading is the departure heading.
|
arrival)
|
||||||
heading = (arrival.heading + 180) % 360
|
heading = (landing_heading + 180) % 360
|
||||||
position = arrival.position.point_from_heading(
|
position = arrival.position.point_from_heading(
|
||||||
heading, nm_to_meter(5)
|
heading, nm_to_meter(5)
|
||||||
)
|
)
|
||||||
@ -62,7 +67,7 @@ class WaypointBuilder:
|
|||||||
FlightWaypointType.DESCENT_POINT,
|
FlightWaypointType.DESCENT_POINT,
|
||||||
position.x,
|
position.x,
|
||||||
position.y,
|
position.y,
|
||||||
300 if is_helo else self.doctrine.pattern_altitude
|
300 if self.is_helo else self.doctrine.pattern_altitude
|
||||||
)
|
)
|
||||||
waypoint.name = "DESCEND"
|
waypoint.name = "DESCEND"
|
||||||
waypoint.alt_type = "RADIO"
|
waypoint.alt_type = "RADIO"
|
||||||
@ -94,7 +99,7 @@ class WaypointBuilder:
|
|||||||
FlightWaypointType.LOITER,
|
FlightWaypointType.LOITER,
|
||||||
position.x,
|
position.x,
|
||||||
position.y,
|
position.y,
|
||||||
self.doctrine.rendezvous_altitude
|
500 if self.is_helo else self.doctrine.rendezvous_altitude
|
||||||
)
|
)
|
||||||
waypoint.pretty_name = "Hold"
|
waypoint.pretty_name = "Hold"
|
||||||
waypoint.description = "Wait until push time"
|
waypoint.description = "Wait until push time"
|
||||||
@ -106,7 +111,7 @@ class WaypointBuilder:
|
|||||||
FlightWaypointType.JOIN,
|
FlightWaypointType.JOIN,
|
||||||
position.x,
|
position.x,
|
||||||
position.y,
|
position.y,
|
||||||
self.doctrine.ingress_altitude
|
500 if self.is_helo else self.doctrine.ingress_altitude
|
||||||
)
|
)
|
||||||
waypoint.pretty_name = "Join"
|
waypoint.pretty_name = "Join"
|
||||||
waypoint.description = "Rendezvous with package"
|
waypoint.description = "Rendezvous with package"
|
||||||
@ -118,7 +123,7 @@ class WaypointBuilder:
|
|||||||
FlightWaypointType.SPLIT,
|
FlightWaypointType.SPLIT,
|
||||||
position.x,
|
position.x,
|
||||||
position.y,
|
position.y,
|
||||||
self.doctrine.ingress_altitude
|
500 if self.is_helo else self.doctrine.ingress_altitude
|
||||||
)
|
)
|
||||||
waypoint.pretty_name = "Split"
|
waypoint.pretty_name = "Split"
|
||||||
waypoint.description = "Depart from package"
|
waypoint.description = "Depart from package"
|
||||||
@ -146,7 +151,7 @@ class WaypointBuilder:
|
|||||||
ingress_type,
|
ingress_type,
|
||||||
position.x,
|
position.x,
|
||||||
position.y,
|
position.y,
|
||||||
self.doctrine.ingress_altitude
|
500 if self.is_helo else self.doctrine.ingress_altitude
|
||||||
)
|
)
|
||||||
waypoint.pretty_name = "INGRESS on " + objective.name
|
waypoint.pretty_name = "INGRESS on " + objective.name
|
||||||
waypoint.description = "INGRESS on " + objective.name
|
waypoint.description = "INGRESS on " + objective.name
|
||||||
@ -159,7 +164,7 @@ class WaypointBuilder:
|
|||||||
FlightWaypointType.EGRESS,
|
FlightWaypointType.EGRESS,
|
||||||
position.x,
|
position.x,
|
||||||
position.y,
|
position.y,
|
||||||
self.doctrine.ingress_altitude
|
500 if self.is_helo else self.doctrine.ingress_altitude
|
||||||
)
|
)
|
||||||
waypoint.pretty_name = "EGRESS from " + target.name
|
waypoint.pretty_name = "EGRESS from " + target.name
|
||||||
waypoint.description = "EGRESS from " + target.name
|
waypoint.description = "EGRESS from " + target.name
|
||||||
@ -168,24 +173,21 @@ class WaypointBuilder:
|
|||||||
|
|
||||||
def dead_point(self, target: Union[TheaterGroundObject, Unit], name: str,
|
def dead_point(self, target: Union[TheaterGroundObject, Unit], name: str,
|
||||||
location: MissionTarget) -> None:
|
location: MissionTarget) -> None:
|
||||||
self._target_point(target, name, f"STRIKE [{location.name}]: {name}",
|
self._target_point(target, name, f"STRIKE {name}", location)
|
||||||
location)
|
|
||||||
# TODO: Seems fishy.
|
# TODO: Seems fishy.
|
||||||
if self.ingress_point is not None:
|
if self.ingress_point is not None:
|
||||||
self.ingress_point.targetGroup = location
|
self.ingress_point.targetGroup = location
|
||||||
|
|
||||||
def sead_point(self, target: Union[TheaterGroundObject, Unit], name: str,
|
def sead_point(self, target: Union[TheaterGroundObject, Unit], name: str,
|
||||||
location: MissionTarget) -> None:
|
location: MissionTarget) -> None:
|
||||||
self._target_point(target, name, f"STRIKE [{location.name}]: {name}",
|
self._target_point(target, name, f"STRIKE {name}", location)
|
||||||
location)
|
|
||||||
# TODO: Seems fishy.
|
# TODO: Seems fishy.
|
||||||
if self.ingress_point is not None:
|
if self.ingress_point is not None:
|
||||||
self.ingress_point.targetGroup = location
|
self.ingress_point.targetGroup = location
|
||||||
|
|
||||||
def strike_point(self, target: Union[TheaterGroundObject, Unit], name: str,
|
def strike_point(self, target: Union[TheaterGroundObject, Unit], name: str,
|
||||||
location: MissionTarget) -> None:
|
location: MissionTarget) -> None:
|
||||||
self._target_point(target, name, f"STRIKE [{location.name}]: {name}",
|
self._target_point(target, name, f"STRIKE {name}", location)
|
||||||
location)
|
|
||||||
|
|
||||||
def _target_point(self, target: Union[TheaterGroundObject, Unit], name: str,
|
def _target_point(self, target: Union[TheaterGroundObject, Unit], name: str,
|
||||||
description: str, location: MissionTarget) -> None:
|
description: str, location: MissionTarget) -> None:
|
||||||
@ -246,12 +248,12 @@ class WaypointBuilder:
|
|||||||
# TODO: This seems wrong, but it's what was there before.
|
# TODO: This seems wrong, but it's what was there before.
|
||||||
self.ingress_point.targets.append(location)
|
self.ingress_point.targets.append(location)
|
||||||
|
|
||||||
def cas(self, position: Point, altitude: int) -> None:
|
def cas(self, position: Point) -> None:
|
||||||
waypoint = FlightWaypoint(
|
waypoint = FlightWaypoint(
|
||||||
FlightWaypointType.CAS,
|
FlightWaypointType.CAS,
|
||||||
position.x,
|
position.x,
|
||||||
position.y,
|
position.y,
|
||||||
altitude
|
500 if self.is_helo else 1000
|
||||||
)
|
)
|
||||||
waypoint.alt_type = "RADIO"
|
waypoint.alt_type = "RADIO"
|
||||||
waypoint.description = "Provide CAS"
|
waypoint.description = "Provide CAS"
|
||||||
@ -306,14 +308,13 @@ class WaypointBuilder:
|
|||||||
self.race_track_start(start, altitude)
|
self.race_track_start(start, altitude)
|
||||||
self.race_track_end(end, altitude)
|
self.race_track_end(end, altitude)
|
||||||
|
|
||||||
def rtb(self, arrival: ControlPoint, is_helo: bool = False) -> None:
|
def rtb(self, arrival: ControlPoint) -> None:
|
||||||
"""Creates descent ant landing waypoints for the given control point.
|
"""Creates descent ant landing waypoints for the given control point.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
arrival: Arrival airfield or carrier.
|
arrival: Arrival airfield or carrier.
|
||||||
is_helo: True if the flight is a helicopter.
|
|
||||||
"""
|
"""
|
||||||
self.descent(arrival, is_helo)
|
self.descent(arrival)
|
||||||
self.land(arrival)
|
self.land(arrival)
|
||||||
|
|
||||||
def escort(self, ingress: Point, target: MissionTarget,
|
def escort(self, ingress: Point, target: MissionTarget,
|
||||||
@ -337,7 +338,7 @@ class WaypointBuilder:
|
|||||||
FlightWaypointType.TARGET_GROUP_LOC,
|
FlightWaypointType.TARGET_GROUP_LOC,
|
||||||
target.position.x,
|
target.position.x,
|
||||||
target.position.y,
|
target.position.y,
|
||||||
self.doctrine.ingress_altitude
|
500 if self.is_helo else self.doctrine.ingress_altitude
|
||||||
)
|
)
|
||||||
waypoint.name = "TARGET"
|
waypoint.name = "TARGET"
|
||||||
waypoint.description = "Escort the package"
|
waypoint.description = "Escort the package"
|
||||||
|
|||||||
@ -16,9 +16,9 @@ from dcs.unitgroup import StaticGroup
|
|||||||
from game import db
|
from game import db
|
||||||
from game.data.building_data import FORTIFICATION_UNITS, FORTIFICATION_UNITS_ID
|
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 .conflictgen import Conflict
|
from .conflictgen import Conflict
|
||||||
from .radios import RadioRegistry
|
from .radios import RadioRegistry
|
||||||
|
from .runways import RunwayData
|
||||||
from .tacan import TacanBand, TacanRegistry
|
from .tacan import TacanBand, TacanRegistry
|
||||||
|
|
||||||
FARP_FRONTLINE_DISTANCE = 10000
|
FARP_FRONTLINE_DISTANCE = 10000
|
||||||
@ -141,8 +141,9 @@ class GroundObjectsGenerator:
|
|||||||
# Find carrier direction (In the wind)
|
# Find carrier direction (In the wind)
|
||||||
found_carrier_destination = False
|
found_carrier_destination = False
|
||||||
attempt = 0
|
attempt = 0
|
||||||
|
brc = self.m.weather.wind_at_ground.direction + 180
|
||||||
while not found_carrier_destination and attempt < 5:
|
while not found_carrier_destination and attempt < 5:
|
||||||
point = sg.points[0].position.point_from_heading(self.m.weather.wind_at_ground.direction + 180, 100000-attempt*20000)
|
point = sg.points[0].position.point_from_heading(brc, 100000-attempt*20000)
|
||||||
if self.game.theater.is_in_sea(point):
|
if self.game.theater.is_in_sea(point):
|
||||||
found_carrier_destination = True
|
found_carrier_destination = True
|
||||||
sg.add_waypoint(point)
|
sg.add_waypoint(point)
|
||||||
@ -196,6 +197,7 @@ class GroundObjectsGenerator:
|
|||||||
# unit name since it's an arbitrary ID.
|
# unit name since it's an arbitrary ID.
|
||||||
self.runways[cp.name] = RunwayData(
|
self.runways[cp.name] = RunwayData(
|
||||||
cp.name,
|
cp.name,
|
||||||
|
brc,
|
||||||
"N/A",
|
"N/A",
|
||||||
atc=atc_channel,
|
atc=atc_channel,
|
||||||
tacan=tacan,
|
tacan=tacan,
|
||||||
|
|||||||
@ -22,14 +22,13 @@ https://forums.eagle.ru/showthread.php?t=206360 claims that kneeboard pages can
|
|||||||
only be added per airframe, so PvP missions where each side have the same
|
only be added per airframe, so PvP missions where each side have the same
|
||||||
aircraft will be able to see the enemy's kneeboard for the same airframe.
|
aircraft will be able to see the enemy's kneeboard for the same airframe.
|
||||||
"""
|
"""
|
||||||
|
import datetime
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import datetime
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Optional, Tuple
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
from dcs.mapping import Point
|
|
||||||
from dcs.mission import Mission
|
from dcs.mission import Mission
|
||||||
from dcs.unittype import FlyingType
|
from dcs.unittype import FlyingType
|
||||||
from tabulate import tabulate
|
from tabulate import tabulate
|
||||||
@ -37,12 +36,11 @@ from tabulate import tabulate
|
|||||||
from game.utils import meter_to_nm
|
from game.utils import meter_to_nm
|
||||||
from . import units
|
from . import units
|
||||||
from .aircraft import AIRCRAFT_DATA, FlightData
|
from .aircraft import AIRCRAFT_DATA, FlightData
|
||||||
from .airfields import RunwayData
|
|
||||||
from .airsupportgen import AwacsInfo, TankerInfo
|
from .airsupportgen import AwacsInfo, TankerInfo
|
||||||
from .briefinggen import CommInfo, JtacInfo, MissionInfoGenerator
|
from .briefinggen import CommInfo, JtacInfo, MissionInfoGenerator
|
||||||
from .flights.flight import FlightWaypoint, FlightWaypointType
|
from .flights.flight import FlightWaypoint, FlightWaypointType
|
||||||
from .flights.traveltime import TravelTime
|
|
||||||
from .radios import RadioFrequency
|
from .radios import RadioFrequency
|
||||||
|
from .runways import RunwayData
|
||||||
|
|
||||||
|
|
||||||
class KneeboardPageWriter:
|
class KneeboardPageWriter:
|
||||||
@ -126,6 +124,7 @@ class FlightPlanBuilder:
|
|||||||
self.target_points = []
|
self.target_points = []
|
||||||
|
|
||||||
self.add_waypoint_row(NumberedWaypoint(waypoint_num, waypoint))
|
self.add_waypoint_row(NumberedWaypoint(waypoint_num, waypoint))
|
||||||
|
self.last_waypoint = waypoint
|
||||||
|
|
||||||
def coalesce_target_points(self) -> None:
|
def coalesce_target_points(self) -> None:
|
||||||
if len(self.target_points) <= 4:
|
if len(self.target_points) <= 4:
|
||||||
@ -157,7 +156,6 @@ class FlightPlanBuilder:
|
|||||||
self._format_time(waypoint.waypoint.tot),
|
self._format_time(waypoint.waypoint.tot),
|
||||||
self._format_time(waypoint.waypoint.departure_time),
|
self._format_time(waypoint.waypoint.departure_time),
|
||||||
])
|
])
|
||||||
self.last_waypoint = waypoint.waypoint
|
|
||||||
|
|
||||||
def _format_time(self, time: Optional[int]) -> str:
|
def _format_time(self, time: Optional[int]) -> str:
|
||||||
if time is None:
|
if time is None:
|
||||||
|
|||||||
139
gen/runways.py
Normal file
139
gen/runways.py
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
"""Runway information and selection."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Iterator, Optional
|
||||||
|
|
||||||
|
from dcs.terrain.terrain import Airport
|
||||||
|
|
||||||
|
from game.weather import Conditions
|
||||||
|
from theater import ControlPoint, ControlPointType
|
||||||
|
from .airfields import AIRFIELD_DATA
|
||||||
|
from .radios import RadioFrequency
|
||||||
|
from .tacan import TacanChannel
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class RunwayData:
|
||||||
|
airfield_name: str
|
||||||
|
runway_heading: int
|
||||||
|
runway_name: str
|
||||||
|
atc: Optional[RadioFrequency] = None
|
||||||
|
tacan: Optional[TacanChannel] = None
|
||||||
|
tacan_callsign: Optional[str] = None
|
||||||
|
ils: Optional[RadioFrequency] = None
|
||||||
|
icls: Optional[int] = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def for_airfield(cls, airport: Airport, runway_heading: int,
|
||||||
|
runway_name: str) -> RunwayData:
|
||||||
|
"""Creates RunwayData for the given runway of an airfield.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
airport: The airfield the runway belongs to.
|
||||||
|
runway_heading: Heading of the runway.
|
||||||
|
runway_name: Identifier of the runway to use. e.g. "03" or "20L".
|
||||||
|
"""
|
||||||
|
atc: Optional[RadioFrequency] = None
|
||||||
|
tacan: Optional[TacanChannel] = None
|
||||||
|
tacan_callsign: Optional[str] = None
|
||||||
|
ils: Optional[RadioFrequency] = None
|
||||||
|
try:
|
||||||
|
airfield = AIRFIELD_DATA[airport.name]
|
||||||
|
if airfield.atc is not None:
|
||||||
|
atc = airfield.atc.uhf
|
||||||
|
else:
|
||||||
|
atc = None
|
||||||
|
tacan = airfield.tacan
|
||||||
|
tacan_callsign = airfield.tacan_callsign
|
||||||
|
ils = airfield.ils_freq(runway_name)
|
||||||
|
except KeyError:
|
||||||
|
logging.warning(f"No airfield data for {airport.name}")
|
||||||
|
return cls(
|
||||||
|
airfield_name=airport.name,
|
||||||
|
runway_heading=runway_heading,
|
||||||
|
runway_name=runway_name,
|
||||||
|
atc=atc,
|
||||||
|
tacan=tacan,
|
||||||
|
tacan_callsign=tacan_callsign,
|
||||||
|
ils=ils
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def for_pydcs_airport(cls, airport: Airport) -> Iterator[RunwayData]:
|
||||||
|
for runway in airport.runways:
|
||||||
|
runway_number = runway.heading // 10
|
||||||
|
runway_side = ["", "L", "R"][runway.leftright]
|
||||||
|
runway_name = f"{runway_number:02}{runway_side}"
|
||||||
|
yield cls.for_airfield(airport, runway.heading, runway_name)
|
||||||
|
|
||||||
|
# pydcs only exposes one runway per physical runway, so to expose
|
||||||
|
# both sides of the runway we need to generate the other.
|
||||||
|
heading = (runway.heading + 180) % 360
|
||||||
|
runway_number = heading // 10
|
||||||
|
runway_side = ["", "R", "L"][runway.leftright]
|
||||||
|
runway_name = f"{runway_number:02}{runway_side}"
|
||||||
|
yield cls.for_airfield(airport, heading, runway_name)
|
||||||
|
|
||||||
|
|
||||||
|
class RunwayAssigner:
|
||||||
|
def __init__(self, conditions: Conditions):
|
||||||
|
self.conditions = conditions
|
||||||
|
|
||||||
|
def angle_off_headwind(self, runway: RunwayData) -> int:
|
||||||
|
wind = self.conditions.weather.wind.at_0m.direction
|
||||||
|
ideal_heading = (wind + 180) % 360
|
||||||
|
return abs(runway.runway_heading - ideal_heading)
|
||||||
|
|
||||||
|
def get_preferred_runway(self, airport: Airport) -> RunwayData:
|
||||||
|
"""Returns the preferred runway for the given airport.
|
||||||
|
|
||||||
|
Right now we're only selecting runways based on whether or not
|
||||||
|
they have
|
||||||
|
ILS, but we could also choose based on wind conditions, or which
|
||||||
|
direction flight plans should follow.
|
||||||
|
"""
|
||||||
|
runways = list(RunwayData.for_pydcs_airport(airport))
|
||||||
|
|
||||||
|
# Find the runway with the best headwind first.
|
||||||
|
best_runways = [runways[0]]
|
||||||
|
best_angle_off_headwind = self.angle_off_headwind(best_runways[0])
|
||||||
|
for runway in runways[1:]:
|
||||||
|
angle_off_headwind = self.angle_off_headwind(runway)
|
||||||
|
if angle_off_headwind == best_angle_off_headwind:
|
||||||
|
best_runways.append(runway)
|
||||||
|
elif angle_off_headwind < best_angle_off_headwind:
|
||||||
|
best_runways = [runway]
|
||||||
|
best_angle_off_headwind = angle_off_headwind
|
||||||
|
|
||||||
|
for runway in best_runways:
|
||||||
|
# But if there are multiple runways with the same heading,
|
||||||
|
# prefer
|
||||||
|
# and ILS capable runway.
|
||||||
|
if runway.ils is not None:
|
||||||
|
return runway
|
||||||
|
|
||||||
|
# Otherwise the only difference between the two is the distance from
|
||||||
|
# parking, which we don't know, so just pick the first one.
|
||||||
|
return best_runways[0]
|
||||||
|
|
||||||
|
def takeoff_heading(self, departure: ControlPoint) -> int:
|
||||||
|
if departure.cptype == ControlPointType.AIRBASE:
|
||||||
|
return self.get_preferred_runway(departure.airport).runway_heading
|
||||||
|
elif departure.is_fleet:
|
||||||
|
# The carrier will be angled into the wind automatically.
|
||||||
|
return (self.conditions.weather.wind.at_0m.direction + 180) % 360
|
||||||
|
logging.warning(
|
||||||
|
f"Unhandled departure control point: {departure.cptype}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def landing_heading(self, arrival: ControlPoint) -> int:
|
||||||
|
if arrival.cptype == ControlPointType.AIRBASE:
|
||||||
|
return self.get_preferred_runway(arrival.airport).runway_heading
|
||||||
|
elif arrival.is_fleet:
|
||||||
|
# The carrier will be angled into the wind automatically.
|
||||||
|
return (self.conditions.weather.wind.at_0m.direction + 180) % 360
|
||||||
|
logging.warning(
|
||||||
|
f"Unhandled departure control point: {arrival.cptype}")
|
||||||
|
return 0
|
||||||
@ -1,44 +1,48 @@
|
|||||||
from typing import List
|
|
||||||
from pathlib import Path
|
|
||||||
from PySide2.QtCore import QSize, Qt, QItemSelectionModel, QPoint
|
|
||||||
from PySide2.QtWidgets import QLabel, QDialog, QGridLayout, QListView, QStackedLayout, QComboBox, QWidget, \
|
|
||||||
QAbstractItemView, QPushButton, QGroupBox, QCheckBox, QVBoxLayout, QSpinBox
|
|
||||||
import json
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
class LuaPluginWorkOrder():
|
from PySide2.QtCore import Qt
|
||||||
|
from PySide2.QtWidgets import QCheckBox, QGridLayout, QGroupBox, QLabel
|
||||||
def __init__(self, parent, filename:str, mnemonic:str, disable:bool):
|
|
||||||
|
|
||||||
|
class LuaPluginWorkOrder:
|
||||||
|
|
||||||
|
def __init__(self, parent, filename: str, mnemonic: str,
|
||||||
|
disable: bool) -> None:
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.mnemonic = mnemonic
|
self.mnemonic = mnemonic
|
||||||
self.disable = disable
|
self.disable = disable
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
|
|
||||||
def work(self, mnemonic:str, operation):
|
def work(self, operation):
|
||||||
if self.disable:
|
if self.disable:
|
||||||
operation.bypassPluginScript(self.parent.mnemonic, self.mnemonic)
|
operation.bypass_plugin_script(self.mnemonic)
|
||||||
else:
|
else:
|
||||||
operation.injectPluginScript(self.parent.mnemonic, self.filename, self.mnemonic)
|
operation.inject_plugin_script(self.parent.mnemonic, self.filename,
|
||||||
|
self.mnemonic)
|
||||||
|
|
||||||
class LuaPluginSpecificOption():
|
class LuaPluginSpecificOption:
|
||||||
|
|
||||||
def __init__(self, parent, mnemonic:str, nameInUI:str, defaultValue:bool):
|
def __init__(self, parent, mnemonic: str, nameInUI: str,
|
||||||
|
defaultValue: bool) -> None:
|
||||||
self.mnemonic = mnemonic
|
self.mnemonic = mnemonic
|
||||||
self.nameInUI = nameInUI
|
self.nameInUI = nameInUI
|
||||||
self.defaultValue = defaultValue
|
self.defaultValue = defaultValue
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
|
|
||||||
class LuaPlugin():
|
class LuaPlugin:
|
||||||
NAME_IN_SETTINGS_BASE:str = "plugins."
|
NAME_IN_SETTINGS_BASE:str = "plugins."
|
||||||
|
|
||||||
def __init__(self, jsonFilename:str):
|
def __init__(self, jsonFilename: str) -> None:
|
||||||
self.mnemonic:str = None
|
self.mnemonic: Optional[str] = None
|
||||||
self.skipUI:bool = False
|
self.skipUI: bool = False
|
||||||
self.nameInUI:str = None
|
self.nameInUI: Optional[str] = None
|
||||||
self.nameInSettings:str = None
|
self.nameInSettings: Optional[str] = None
|
||||||
self.defaultValue:bool = False
|
self.defaultValue: bool = False
|
||||||
self.specificOptions = []
|
self.specificOptions: List[LuaPluginSpecificOption] = []
|
||||||
self.scriptsWorkOrders: List[LuaPluginWorkOrder] = None
|
self.scriptsWorkOrders: List[LuaPluginWorkOrder] = []
|
||||||
self.configurationWorkOrders: List[LuaPluginWorkOrder] = None
|
self.configurationWorkOrders: List[LuaPluginWorkOrder] = []
|
||||||
self.initFromJson(jsonFilename)
|
self.initFromJson(jsonFilename)
|
||||||
self.enabled = self.defaultValue
|
self.enabled = self.defaultValue
|
||||||
self.settings = None
|
self.settings = None
|
||||||
@ -50,6 +54,7 @@ class LuaPlugin():
|
|||||||
self.mnemonic = jsonData.get("mnemonic")
|
self.mnemonic = jsonData.get("mnemonic")
|
||||||
self.skipUI = jsonData.get("skipUI", False)
|
self.skipUI = jsonData.get("skipUI", False)
|
||||||
self.nameInUI = jsonData.get("nameInUI")
|
self.nameInUI = jsonData.get("nameInUI")
|
||||||
|
assert self.mnemonic is not None
|
||||||
self.nameInSettings = LuaPlugin.NAME_IN_SETTINGS_BASE + self.mnemonic
|
self.nameInSettings = LuaPlugin.NAME_IN_SETTINGS_BASE + self.mnemonic
|
||||||
self.defaultValue = jsonData.get("defaultValue", False)
|
self.defaultValue = jsonData.get("defaultValue", False)
|
||||||
self.specificOptions = []
|
self.specificOptions = []
|
||||||
@ -76,6 +81,9 @@ class LuaPlugin():
|
|||||||
self.setSettings(settingsWindow.game.settings)
|
self.setSettings(settingsWindow.game.settings)
|
||||||
|
|
||||||
if not self.skipUI:
|
if not self.skipUI:
|
||||||
|
assert self.nameInSettings is not None
|
||||||
|
assert self.settings is not None
|
||||||
|
|
||||||
# create the plugin choice checkbox interface
|
# create the plugin choice checkbox interface
|
||||||
self.uiWidget: QCheckBox = QCheckBox()
|
self.uiWidget: QCheckBox = QCheckBox()
|
||||||
self.uiWidget.setChecked(self.isEnabled())
|
self.uiWidget.setChecked(self.isEnabled())
|
||||||
@ -95,6 +103,7 @@ class LuaPlugin():
|
|||||||
# browse each option in the specific options list
|
# browse each option in the specific options list
|
||||||
row = 0
|
row = 0
|
||||||
for specificOption in self.specificOptions:
|
for specificOption in self.specificOptions:
|
||||||
|
assert specificOption.mnemonic is not None
|
||||||
nameInSettings = self.nameInSettings + "." + specificOption.mnemonic
|
nameInSettings = self.nameInSettings + "." + specificOption.mnemonic
|
||||||
if not nameInSettings in self.settings.plugins:
|
if not nameInSettings in self.settings.plugins:
|
||||||
self.settings.plugins[nameInSettings] = specificOption.defaultValue
|
self.settings.plugins[nameInSettings] = specificOption.defaultValue
|
||||||
@ -149,7 +158,7 @@ class LuaPlugin():
|
|||||||
# execute the work order
|
# execute the work order
|
||||||
if self.scriptsWorkOrders != None:
|
if self.scriptsWorkOrders != None:
|
||||||
for workOrder in self.scriptsWorkOrders:
|
for workOrder in self.scriptsWorkOrders:
|
||||||
workOrder.work(self.mnemonic, operation)
|
workOrder.work(operation)
|
||||||
|
|
||||||
# serves for subclasses
|
# serves for subclasses
|
||||||
return self.isEnabled()
|
return self.isEnabled()
|
||||||
@ -177,12 +186,12 @@ class LuaPlugin():
|
|||||||
lua += defineAllOptions
|
lua += defineAllOptions
|
||||||
lua += "end"
|
lua += "end"
|
||||||
|
|
||||||
operation.injectLuaTrigger(lua, f"{self.mnemonic} plugin configuration")
|
operation.inject_lua_trigger(lua, f"{self.mnemonic} plugin configuration")
|
||||||
|
|
||||||
# execute the work order
|
# execute the work order
|
||||||
if self.configurationWorkOrders != None:
|
if self.configurationWorkOrders != None:
|
||||||
for workOrder in self.configurationWorkOrders:
|
for workOrder in self.configurationWorkOrders:
|
||||||
workOrder.work(self.mnemonic, operation)
|
workOrder.work(operation)
|
||||||
|
|
||||||
# serves for subclasses
|
# serves for subclasses
|
||||||
return self.isEnabled()
|
return self.isEnabled()
|
||||||
|
|||||||
@ -344,7 +344,7 @@ class QLiberationMap(QGraphicsView):
|
|||||||
altitude_type = "AGL" if waypoint.alt_type == "RADIO" else "MSL"
|
altitude_type = "AGL" if waypoint.alt_type == "RADIO" else "MSL"
|
||||||
|
|
||||||
prefix = "TOT"
|
prefix = "TOT"
|
||||||
time = timing.tot_for_waypoint(waypoint)
|
time = timing.tot_for_waypoint(flight, waypoint)
|
||||||
if time is None:
|
if time is None:
|
||||||
prefix = "Depart"
|
prefix = "Depart"
|
||||||
time = timing.depart_time_for_waypoint(waypoint, flight)
|
time = timing.depart_time_for_waypoint(waypoint, flight)
|
||||||
|
|||||||
@ -19,16 +19,12 @@ class QAirfieldCommand(QFrame):
|
|||||||
layout = QGridLayout()
|
layout = QGridLayout()
|
||||||
layout.addWidget(QAircraftRecruitmentMenu(self.cp, self.game_model), 0, 0)
|
layout.addWidget(QAircraftRecruitmentMenu(self.cp, self.game_model), 0, 0)
|
||||||
|
|
||||||
try:
|
planned = QGroupBox("Planned Flights")
|
||||||
planned = QGroupBox("Planned Flights")
|
planned_layout = QVBoxLayout()
|
||||||
planned_layout = QVBoxLayout()
|
planned_layout.addWidget(
|
||||||
planned_layout.addWidget(
|
QPlannedFlightsView(self.game_model, self.cp)
|
||||||
QPlannedFlightsView(self.game_model, self.cp)
|
)
|
||||||
)
|
planned.setLayout(planned_layout)
|
||||||
planned.setLayout(planned_layout)
|
layout.addWidget(planned, 0, 1)
|
||||||
layout.addWidget(planned, 0, 1)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
#layout.addWidget(QAirportInformation(self.cp, self.game.theater.terrain.airport_by_id(self.cp.id)), 0, 2)
|
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|||||||
@ -25,7 +25,7 @@ class QPlannedFlightsView(QListView):
|
|||||||
for package in self.game_model.ato_model.packages:
|
for package in self.game_model.ato_model.packages:
|
||||||
for flight in package.flights:
|
for flight in package.flights:
|
||||||
if flight.from_cp == self.cp:
|
if flight.from_cp == self.cp:
|
||||||
item = QFlightItem(flight)
|
item = QFlightItem(package.package, flight)
|
||||||
self.model.appendRow(item)
|
self.model.appendRow(item)
|
||||||
self.flight_items.append(item)
|
self.flight_items.append(item)
|
||||||
self.set_selected_flight(0)
|
self.set_selected_flight(0)
|
||||||
|
|||||||
@ -107,7 +107,7 @@ class QFlightCreator(QDialog):
|
|||||||
start_type = "Cold"
|
start_type = "Cold"
|
||||||
else:
|
else:
|
||||||
start_type = "Warm"
|
start_type = "Warm"
|
||||||
flight = Flight(aircraft, size, origin, task, start_type)
|
flight = Flight(self.package, aircraft, size, origin, task, start_type)
|
||||||
flight.scheduled_in = self.package.delay
|
flight.scheduled_in = self.package.delay
|
||||||
flight.client_count = self.client_slots_spinner.value()
|
flight.client_count = self.client_slots_spinner.value()
|
||||||
|
|
||||||
|
|||||||
@ -55,11 +55,12 @@ class QFlightWaypointList(QTableView):
|
|||||||
|
|
||||||
waypoints = itertools.chain([takeoff], self.flight.points)
|
waypoints = itertools.chain([takeoff], self.flight.points)
|
||||||
for row, waypoint in enumerate(waypoints):
|
for row, waypoint in enumerate(waypoints):
|
||||||
self.add_waypoint_row(row, waypoint, timing)
|
self.add_waypoint_row(row, self.flight, waypoint, timing)
|
||||||
self.selectionModel().setCurrentIndex(self.indexAt(QPoint(1, 1)),
|
self.selectionModel().setCurrentIndex(self.indexAt(QPoint(1, 1)),
|
||||||
QItemSelectionModel.Select)
|
QItemSelectionModel.Select)
|
||||||
|
|
||||||
def add_waypoint_row(self, row: int, waypoint: FlightWaypoint,
|
def add_waypoint_row(self, row: int, flight: Flight,
|
||||||
|
waypoint: FlightWaypoint,
|
||||||
timing: PackageWaypointTiming) -> None:
|
timing: PackageWaypointTiming) -> None:
|
||||||
self.model.insertRow(self.model.rowCount())
|
self.model.insertRow(self.model.rowCount())
|
||||||
|
|
||||||
@ -71,15 +72,15 @@ class QFlightWaypointList(QTableView):
|
|||||||
altitude_item.setEditable(False)
|
altitude_item.setEditable(False)
|
||||||
self.model.setItem(row, 1, altitude_item)
|
self.model.setItem(row, 1, altitude_item)
|
||||||
|
|
||||||
tot = self.tot_text(waypoint, timing)
|
tot = self.tot_text(flight, waypoint, timing)
|
||||||
tot_item = QStandardItem(tot)
|
tot_item = QStandardItem(tot)
|
||||||
tot_item.setEditable(False)
|
tot_item.setEditable(False)
|
||||||
self.model.setItem(row, 2, tot_item)
|
self.model.setItem(row, 2, tot_item)
|
||||||
|
|
||||||
def tot_text(self, waypoint: FlightWaypoint,
|
def tot_text(self, flight: Flight, waypoint: FlightWaypoint,
|
||||||
timing: PackageWaypointTiming) -> str:
|
timing: PackageWaypointTiming) -> str:
|
||||||
prefix = ""
|
prefix = ""
|
||||||
time = timing.tot_for_waypoint(waypoint)
|
time = timing.tot_for_waypoint(flight, waypoint)
|
||||||
if time is None:
|
if time is None:
|
||||||
prefix = "Depart "
|
prefix = "Depart "
|
||||||
time = timing.depart_time_for_waypoint(waypoint, self.flight)
|
time = timing.depart_time_for_waypoint(waypoint, self.flight)
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
#pydcs>=0.9.10
|
|
||||||
Pyside2>=5.13.0
|
Pyside2>=5.13.0
|
||||||
pyinstaller==3.6
|
pyinstaller==3.6
|
||||||
pyproj==2.6.1.post1
|
pyproj==2.6.1.post1
|
||||||
|
|
||||||
Pillow~=7.2.0
|
Pillow~=7.2.0
|
||||||
tabulate~=0.8.7
|
tabulate~=0.8.7
|
||||||
|
|
||||||
|
mypy==0.782
|
||||||
|
mypy-extensions==0.4.3
|
||||||
@ -1,5 +1,4 @@
|
|||||||
[
|
[
|
||||||
"veaf",
|
|
||||||
"jtacautolase",
|
"jtacautolase",
|
||||||
"base"
|
"base"
|
||||||
]
|
]
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import pickle
|
|||||||
from typing import Collection, Optional, Tuple
|
from typing import Collection, Optional, Tuple
|
||||||
|
|
||||||
Zone = Collection[Tuple[float, float]]
|
Zone = Collection[Tuple[float, float]]
|
||||||
Landmap = Tuple[Collection[Zone], Collection[Zone]]
|
Landmap = Tuple[Collection[Zone], Collection[Zone], Collection[Zone]]
|
||||||
|
|
||||||
|
|
||||||
def load_landmap(filename: str) -> Optional[Landmap]:
|
def load_landmap(filename: str) -> Optional[Landmap]:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user