mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Merge branch 'develop'
This commit is contained in:
commit
9053408e13
33
__init__.py
33
__init__.py
@ -1,5 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import dcs
|
import dcs
|
||||||
import logging
|
import logging
|
||||||
@ -17,10 +18,13 @@ from game.game import Game
|
|||||||
from theater import start_generator
|
from theater import start_generator
|
||||||
from userdata import persistency, logging as logging_module
|
from userdata import persistency, logging as logging_module
|
||||||
|
|
||||||
|
assert len(sys.argv) >= 3, "__init__.py should be started with two mandatory arguments: %UserProfile% location and application version"
|
||||||
|
|
||||||
persistency.setup(sys.argv[1])
|
persistency.setup(sys.argv[1])
|
||||||
dcs.planes.FlyingType.payload_dirs = [os.path.join(os.path.dirname(os.path.realpath(__file__)), "resources\\payloads")]
|
dcs.planes.FlyingType.payload_dirs = [os.path.join(os.path.dirname(os.path.realpath(__file__)), "resources\\payloads")]
|
||||||
|
|
||||||
logging_module.setup_version_string(sys.argv[2])
|
VERSION_STRING = sys.argv[2]
|
||||||
|
logging_module.setup_version_string(VERSION_STRING)
|
||||||
logging.info("Using {} as userdata folder".format(persistency.base_path()))
|
logging.info("Using {} as userdata folder".format(persistency.base_path()))
|
||||||
|
|
||||||
|
|
||||||
@ -29,10 +33,29 @@ def proceed_to_main_menu(game: Game):
|
|||||||
m.display()
|
m.display()
|
||||||
|
|
||||||
|
|
||||||
|
def is_version_compatible(save_version):
|
||||||
|
current_version_components = re.split(r"[\._]", VERSION_STRING)
|
||||||
|
save_version_components = re.split(r"[\._]", save_version)
|
||||||
|
|
||||||
|
if "--ignore-save" in sys.argv:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if current_version_components == save_version_components:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if save_version in ["1.4_rc1", "1.4_rc2", "1.4_rc3", "1.4_rc4", "1.4_rc5", "1.4_rc6"]:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if current_version_components[:2] == save_version_components[:2]:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
w = ui.window.Window()
|
w = ui.window.Window()
|
||||||
try:
|
try:
|
||||||
game = persistency.restore_game()
|
game = persistency.restore_game()
|
||||||
if not game:
|
if not game or not is_version_compatible(game.settings.version):
|
||||||
new_game_menu = None # type: NewGameMenu
|
new_game_menu = None # type: NewGameMenu
|
||||||
|
|
||||||
def start_new_game(player_name: str, enemy_name: str, terrain: str, sams: bool, midgame: bool, multiplier: float):
|
def start_new_game(player_name: str, enemy_name: str, terrain: str, sams: bool, midgame: bool, multiplier: float):
|
||||||
@ -47,13 +70,15 @@ try:
|
|||||||
for i in range(0, int(len(conflicttheater.controlpoints) / 2)):
|
for i in range(0, int(len(conflicttheater.controlpoints) / 2)):
|
||||||
conflicttheater.controlpoints[i].captured = True
|
conflicttheater.controlpoints[i].captured = True
|
||||||
|
|
||||||
start_generator.generate_initial(conflicttheater, enemy_name, sams, multiplier)
|
start_generator.generate_inital_units(conflicttheater, enemy_name, sams, multiplier)
|
||||||
|
start_generator.generate_groundobjects(conflicttheater)
|
||||||
game = Game(player_name=player_name,
|
game = Game(player_name=player_name,
|
||||||
enemy_name=enemy_name,
|
enemy_name=enemy_name,
|
||||||
theater=conflicttheater)
|
theater=conflicttheater)
|
||||||
game.budget = int(game.budget * multiplier)
|
game.budget = int(game.budget * multiplier)
|
||||||
game.settings.multiplier = multiplier
|
game.settings.multiplier = multiplier
|
||||||
game.settings.sams = sams
|
game.settings.sams = sams
|
||||||
|
game.settings.version = VERSION_STRING
|
||||||
|
|
||||||
if midgame:
|
if midgame:
|
||||||
game.budget = game.budget * 4 * len(list(conflicttheater.conflicts()))
|
game.budget = game.budget * 4 * len(list(conflicttheater.conflicts()))
|
||||||
@ -63,8 +88,10 @@ try:
|
|||||||
new_game_menu = ui.newgamemenu.NewGameMenu(w, start_new_game)
|
new_game_menu = ui.newgamemenu.NewGameMenu(w, start_new_game)
|
||||||
new_game_menu.display()
|
new_game_menu.display()
|
||||||
else:
|
else:
|
||||||
|
game.settings.version = VERSION_STRING
|
||||||
proceed_to_main_menu(game)
|
proceed_to_main_menu(game)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logging.exception(e)
|
||||||
ui.corruptedsavemenu.CorruptedSaveMenu(w).display()
|
ui.corruptedsavemenu.CorruptedSaveMenu(w).display()
|
||||||
|
|
||||||
w.run()
|
w.run()
|
||||||
|
|||||||
158
game/db.py
158
game/db.py
@ -1,12 +1,15 @@
|
|||||||
import typing
|
import typing
|
||||||
|
import enum
|
||||||
|
|
||||||
from dcs.vehicles import *
|
from dcs.vehicles import *
|
||||||
from dcs.unitgroup import *
|
|
||||||
from dcs.ships import *
|
from dcs.ships import *
|
||||||
from dcs.planes import *
|
from dcs.planes import *
|
||||||
from dcs.helicopters import *
|
from dcs.helicopters import *
|
||||||
|
|
||||||
from dcs.task import *
|
from dcs.task import *
|
||||||
|
from dcs.unit import *
|
||||||
from dcs.unittype import *
|
from dcs.unittype import *
|
||||||
|
from dcs.unitgroup import *
|
||||||
|
|
||||||
"""
|
"""
|
||||||
---------- BEGINNING OF CONFIGURATION SECTION
|
---------- BEGINNING OF CONFIGURATION SECTION
|
||||||
@ -38,10 +41,10 @@ PRICES = {
|
|||||||
# fighter
|
# fighter
|
||||||
C_101CC: 8,
|
C_101CC: 8,
|
||||||
MiG_23MLD: 18,
|
MiG_23MLD: 18,
|
||||||
Su_27: 24,
|
Su_27: 20,
|
||||||
Su_33: 25,
|
Su_33: 22,
|
||||||
MiG_29A: 24,
|
MiG_29A: 23,
|
||||||
MiG_29S: 26,
|
MiG_29S: 25,
|
||||||
|
|
||||||
F_5E_3: 6,
|
F_5E_3: 6,
|
||||||
MiG_15bis: 5,
|
MiG_15bis: 5,
|
||||||
@ -51,7 +54,7 @@ PRICES = {
|
|||||||
AV8BNA: 13,
|
AV8BNA: 13,
|
||||||
M_2000C: 13,
|
M_2000C: 13,
|
||||||
FA_18C_hornet: 18,
|
FA_18C_hornet: 18,
|
||||||
F_15C: 24,
|
F_15C: 20,
|
||||||
|
|
||||||
# bomber
|
# bomber
|
||||||
Su_25: 15,
|
Su_25: 15,
|
||||||
@ -64,7 +67,8 @@ PRICES = {
|
|||||||
|
|
||||||
# heli
|
# heli
|
||||||
Ka_50: 13,
|
Ka_50: 13,
|
||||||
UH_1H: 5,
|
SA342M: 8,
|
||||||
|
UH_1H: 4,
|
||||||
Mi_8MT: 5,
|
Mi_8MT: 5,
|
||||||
|
|
||||||
# special
|
# special
|
||||||
@ -99,20 +103,19 @@ PRICES = {
|
|||||||
Unarmed.Transport_M818: 3,
|
Unarmed.Transport_M818: 3,
|
||||||
|
|
||||||
AirDefence.AAA_Vulcan_M163: 5,
|
AirDefence.AAA_Vulcan_M163: 5,
|
||||||
AirDefence.SAM_Avenger_M1097: 10,
|
AirDefence.SAM_Linebacker_M6: 10,
|
||||||
AirDefence.SAM_Patriot_ICC: 15,
|
|
||||||
|
|
||||||
AirDefence.AAA_ZU_23_on_Ural_375: 5,
|
AirDefence.SPAAA_ZSU_23_4_Shilka: 8,
|
||||||
AirDefence.SAM_SA_18_Igla_S_MANPADS: 8,
|
AirDefence.SAM_SA_9_Strela_1_9P31: 13,
|
||||||
AirDefence.SAM_SA_19_Tunguska_2S6: 15,
|
AirDefence.SAM_SA_8_Osa_9A33: 18,
|
||||||
AirDefence.SAM_SA_8_Osa_9A33: 13,
|
|
||||||
|
|
||||||
# ship
|
# ship
|
||||||
CV_1143_5_Admiral_Kuznetsov: 100,
|
CV_1143_5_Admiral_Kuznetsov: 100,
|
||||||
CVN_74_John_C__Stennis: 100,
|
CVN_74_John_C__Stennis: 100,
|
||||||
|
LHA_1_Tarawa: 50,
|
||||||
|
|
||||||
LHA_1_Tarawa: 30,
|
|
||||||
Bulk_cargo_ship_Yakushev: 10,
|
Bulk_cargo_ship_Yakushev: 10,
|
||||||
|
Armed_speedboat: 10,
|
||||||
Dry_cargo_ship_Ivanov: 10,
|
Dry_cargo_ship_Ivanov: 10,
|
||||||
Tanker_Elnya_160: 10,
|
Tanker_Elnya_160: 10,
|
||||||
}
|
}
|
||||||
@ -158,6 +161,7 @@ UNIT_BY_TASK = {
|
|||||||
Su_25T,
|
Su_25T,
|
||||||
Su_34,
|
Su_34,
|
||||||
Ka_50,
|
Ka_50,
|
||||||
|
SA342M,
|
||||||
],
|
],
|
||||||
|
|
||||||
Transport: [
|
Transport: [
|
||||||
@ -166,13 +170,13 @@ UNIT_BY_TASK = {
|
|||||||
An_30M,
|
An_30M,
|
||||||
Yak_40,
|
Yak_40,
|
||||||
|
|
||||||
S_3B_Tanker,
|
|
||||||
C_130,
|
C_130,
|
||||||
],
|
],
|
||||||
|
|
||||||
Refueling: [
|
Refueling: [
|
||||||
IL_78M,
|
IL_78M,
|
||||||
KC_135,
|
KC_135,
|
||||||
|
S_3B_Tanker,
|
||||||
],
|
],
|
||||||
|
|
||||||
AWACS: [E_3A, A_50, ],
|
AWACS: [E_3A, A_50, ],
|
||||||
@ -182,35 +186,32 @@ UNIT_BY_TASK = {
|
|||||||
# those are listed multiple times here to balance prioritization more into lower tier AAs
|
# those are listed multiple times here to balance prioritization more into lower tier AAs
|
||||||
AirDefence.AAA_Vulcan_M163,
|
AirDefence.AAA_Vulcan_M163,
|
||||||
AirDefence.AAA_Vulcan_M163,
|
AirDefence.AAA_Vulcan_M163,
|
||||||
AirDefence.SAM_Avenger_M1097,
|
AirDefence.AAA_Vulcan_M163,
|
||||||
AirDefence.SAM_Avenger_M1097,
|
AirDefence.SAM_Linebacker_M6,
|
||||||
AirDefence.SAM_Avenger_M1097,
|
|
||||||
AirDefence.SAM_Patriot_ICC,
|
|
||||||
|
|
||||||
AirDefence.AAA_ZU_23_on_Ural_375,
|
AirDefence.SPAAA_ZSU_23_4_Shilka,
|
||||||
AirDefence.AAA_ZU_23_on_Ural_375,
|
AirDefence.SPAAA_ZSU_23_4_Shilka,
|
||||||
AirDefence.AAA_ZU_23_on_Ural_375,
|
AirDefence.SPAAA_ZSU_23_4_Shilka,
|
||||||
|
AirDefence.SAM_SA_9_Strela_1_9P31,
|
||||||
|
AirDefence.SAM_SA_9_Strela_1_9P31,
|
||||||
AirDefence.SAM_SA_8_Osa_9A33,
|
AirDefence.SAM_SA_8_Osa_9A33,
|
||||||
AirDefence.SAM_SA_8_Osa_9A33,
|
|
||||||
AirDefence.SAM_SA_19_Tunguska_2S6,
|
|
||||||
],
|
],
|
||||||
|
|
||||||
Reconnaissance: [Unarmed.Transport_M818, Unarmed.Transport_Ural_375, Unarmed.Transport_UAZ_469],
|
Reconnaissance: [Unarmed.Transport_M818, Unarmed.Transport_Ural_375, Unarmed.Transport_UAZ_469],
|
||||||
Nothing: [Infantry.Infantry_M4, Infantry.Soldier_AK, ],
|
Nothing: [Infantry.Infantry_M4, Infantry.Soldier_AK, ],
|
||||||
Embarking: [UH_1H, Mi_8MT, ],
|
Embarking: [UH_1H, Mi_8MT, ],
|
||||||
|
|
||||||
Carriage: [CVN_74_John_C__Stennis, CV_1143_5_Admiral_Kuznetsov, ],
|
Carriage: [CVN_74_John_C__Stennis, LHA_1_Tarawa, CV_1143_5_Admiral_Kuznetsov, ],
|
||||||
CargoTransportation: [Dry_cargo_ship_Ivanov, Bulk_cargo_ship_Yakushev, Tanker_Elnya_160, LHA_1_Tarawa],
|
CargoTransportation: [Dry_cargo_ship_Ivanov, Bulk_cargo_ship_Yakushev, Tanker_Elnya_160, Armed_speedboat, ],
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Units from AirDefense category of UNIT_BY_TASK that will be removed from use if "No SAM" option is checked at the start of the game
|
Units from AirDefense category of UNIT_BY_TASK that will be removed from use if "No SAM" option is checked at the start of the game
|
||||||
"""
|
"""
|
||||||
SAM_BAN = [
|
SAM_BAN = [
|
||||||
AirDefence.SAM_Avenger_M1097,
|
AirDefence.SAM_Linebacker_M6,
|
||||||
AirDefence.SAM_Patriot_ICC,
|
|
||||||
|
|
||||||
AirDefence.SAM_SA_19_Tunguska_2S6,
|
AirDefence.SAM_SA_9_Strela_1_9P31,
|
||||||
AirDefence.SAM_SA_8_Osa_9A33,
|
AirDefence.SAM_SA_8_Osa_9A33,
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -232,8 +233,8 @@ CARRIER_TAKEOFF_BAN = [
|
|||||||
AirDefense units that will be spawned at control points not related to the current operation
|
AirDefense units that will be spawned at control points not related to the current operation
|
||||||
"""
|
"""
|
||||||
EXTRA_AA = {
|
EXTRA_AA = {
|
||||||
"Russia": AirDefence.SAM_SA_9_Strela_1_9P31,
|
"Russia": AirDefence.SAM_SA_19_Tunguska_2S6,
|
||||||
"USA": AirDefence.SAM_Patriot_EPP_III,
|
"USA": AirDefence.SAM_Linebacker_M6,
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -267,13 +268,13 @@ UNIT_BY_COUNTRY = {
|
|||||||
A_50,
|
A_50,
|
||||||
|
|
||||||
Ka_50,
|
Ka_50,
|
||||||
|
SA342M,
|
||||||
UH_1H,
|
UH_1H,
|
||||||
Mi_8MT,
|
Mi_8MT,
|
||||||
|
|
||||||
AirDefence.AAA_ZU_23_on_Ural_375,
|
AirDefence.SPAAA_ZSU_23_4_Shilka,
|
||||||
AirDefence.SAM_SA_18_Igla_S_MANPADS,
|
AirDefence.SAM_SA_9_Strela_1_9P31,
|
||||||
AirDefence.SAM_SA_8_Osa_9A33,
|
AirDefence.SAM_SA_8_Osa_9A33,
|
||||||
AirDefence.SAM_SA_19_Tunguska_2S6,
|
|
||||||
|
|
||||||
Armor.APC_BTR_80,
|
Armor.APC_BTR_80,
|
||||||
Armor.MBT_T_90,
|
Armor.MBT_T_90,
|
||||||
@ -307,6 +308,7 @@ UNIT_BY_COUNTRY = {
|
|||||||
E_3A,
|
E_3A,
|
||||||
|
|
||||||
Ka_50,
|
Ka_50,
|
||||||
|
SA342M,
|
||||||
UH_1H,
|
UH_1H,
|
||||||
Mi_8MT,
|
Mi_8MT,
|
||||||
|
|
||||||
@ -317,14 +319,23 @@ UNIT_BY_COUNTRY = {
|
|||||||
Infantry.Infantry_M4,
|
Infantry.Infantry_M4,
|
||||||
|
|
||||||
AirDefence.AAA_Vulcan_M163,
|
AirDefence.AAA_Vulcan_M163,
|
||||||
AirDefence.SAM_Avenger_M1097,
|
AirDefence.SAM_Linebacker_M6,
|
||||||
AirDefence.SAM_Patriot_ICC,
|
|
||||||
|
|
||||||
CVN_74_John_C__Stennis,
|
CVN_74_John_C__Stennis,
|
||||||
LHA_1_Tarawa,
|
LHA_1_Tarawa,
|
||||||
|
Armed_speedboat,
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CARRIER_TYPE_BY_PLANE = {
|
||||||
|
FA_18C_hornet: CVN_74_John_C__Stennis,
|
||||||
|
Ka_50: LHA_1_Tarawa,
|
||||||
|
SA342M: LHA_1_Tarawa,
|
||||||
|
UH_1H: LHA_1_Tarawa,
|
||||||
|
Mi_8MT: LHA_1_Tarawa,
|
||||||
|
AV8BNA: LHA_1_Tarawa,
|
||||||
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Aircraft payload overrides. Usually default loadout for the task is loaded during the mission generation.
|
Aircraft payload overrides. Usually default loadout for the task is loaded during the mission generation.
|
||||||
Syntax goes as follows:
|
Syntax goes as follows:
|
||||||
@ -343,11 +354,17 @@ Payload will be used for operation of following type, "*" category will be used
|
|||||||
"""
|
"""
|
||||||
PLANE_PAYLOAD_OVERRIDES = {
|
PLANE_PAYLOAD_OVERRIDES = {
|
||||||
FA_18C_hornet: {
|
FA_18C_hornet: {
|
||||||
"*": "AIM-120*4,AIM-9*2,AIM-7*2,Fuel",
|
CAP: "AIM-120*4,AIM-9*2,AIM-7*2,Fuel",
|
||||||
|
PinpointStrike: "MK-82*8,AIM-9*2,AIM-7,FLIR Pod,Fuel",
|
||||||
|
AntishipStrike: "MK-82*8,AIM-9*2,AIM-7,FLIR Pod,Fuel",
|
||||||
|
},
|
||||||
|
|
||||||
|
Su_25T: {
|
||||||
|
CAS: "APU-8 Vikhr-M*2,Kh-25ML,R-73*2,SPPU-22*2,Mercury LLTV Pod,MPS-410",
|
||||||
},
|
},
|
||||||
|
|
||||||
Su_33: {
|
Su_33: {
|
||||||
"*": "R-73*4,R-27R*2,R-27ER*6",
|
CAP: "R-73*4,R-27R*2,R-27ER*6",
|
||||||
},
|
},
|
||||||
|
|
||||||
AJS37: {
|
AJS37: {
|
||||||
@ -360,18 +377,21 @@ PLANE_PAYLOAD_OVERRIDES = {
|
|||||||
|
|
||||||
A_10C: {
|
A_10C: {
|
||||||
CAS: "AGM-65D*2,AGM-65H*2,GBU-12*2,GBU-38*2,AIM-9*2,TGP,ECM,MK151*7",
|
CAS: "AGM-65D*2,AGM-65H*2,GBU-12*2,GBU-38*2,AIM-9*2,TGP,ECM,MK151*7",
|
||||||
|
GroundAttack: "AGM-65K*2,GBU-12*8,AIM-9M*2.ECM,TGP",
|
||||||
},
|
},
|
||||||
|
|
||||||
Ka_50: {
|
Ka_50: {
|
||||||
"*": "12x9A4172, 40xS-8",
|
CAS: "12x9A4172, 40xS-8",
|
||||||
|
GroundAttack: "12x9A4172, 40xS-8",
|
||||||
},
|
},
|
||||||
|
|
||||||
M_2000C: {
|
M_2000C: {
|
||||||
"*": "Combat Air Patrol",
|
CAP: "Combat Air Patrol",
|
||||||
|
GroundAttack: "MK-82S Heavy Strike",
|
||||||
},
|
},
|
||||||
|
|
||||||
MiG_21Bis: {
|
MiG_21Bis: {
|
||||||
"*": "Patrol, medium range",
|
CAP: "Patrol, medium range",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -397,7 +417,11 @@ HeliDict = typing.Dict[HelicopterType, int]
|
|||||||
ArmorDict = typing.Dict[VehicleType, int]
|
ArmorDict = typing.Dict[VehicleType, int]
|
||||||
ShipDict = typing.Dict[ShipType, int]
|
ShipDict = typing.Dict[ShipType, int]
|
||||||
AirDefenseDict = typing.Dict[AirDefence, int]
|
AirDefenseDict = typing.Dict[AirDefence, int]
|
||||||
StartingPosition = typing.Optional[typing.Union[ShipGroup, Airport, Point]]
|
|
||||||
|
AssignedUnitsDict = typing.Dict[typing.Type[UnitType], typing.Tuple[int, int]]
|
||||||
|
TaskForceDict = typing.Dict[typing.Type[Task], AssignedUnitsDict]
|
||||||
|
|
||||||
|
StartingPosition = typing.Optional[typing.Union[ShipGroup, StaticGroup, Airport, Point]]
|
||||||
|
|
||||||
|
|
||||||
def unit_task(unit: UnitType) -> Task:
|
def unit_task(unit: UnitType) -> Task:
|
||||||
@ -427,6 +451,15 @@ def unit_type_from_name(name: str) -> UnitType:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def unit_type_of(unit: Unit) -> UnitType:
|
||||||
|
if isinstance(unit, Vehicle):
|
||||||
|
return vehicle_map[unit.type]
|
||||||
|
elif isinstance(unit, Ship):
|
||||||
|
return ship_map[unit.type]
|
||||||
|
else:
|
||||||
|
return unit.unit_type
|
||||||
|
|
||||||
|
|
||||||
def task_name(task) -> str:
|
def task_name(task) -> str:
|
||||||
if task == AirDefence:
|
if task == AirDefence:
|
||||||
return "AirDefence"
|
return "AirDefence"
|
||||||
@ -452,6 +485,14 @@ def unitdict_append(unit_dict: UnitsDict, unit_type: UnitType, count: int):
|
|||||||
unit_dict[unit_type] = unit_dict.get(unit_type, 0) + 1
|
unit_dict[unit_type] = unit_dict.get(unit_type, 0) + 1
|
||||||
|
|
||||||
|
|
||||||
|
def unitdict_merge(a: UnitsDict, b: UnitsDict) -> UnitsDict:
|
||||||
|
b = b.copy()
|
||||||
|
for k, v in a.items():
|
||||||
|
b[k] = b.get(k, 0) + v
|
||||||
|
|
||||||
|
return b
|
||||||
|
|
||||||
|
|
||||||
def unitdict_split(unit_dict: UnitsDict, count: int):
|
def unitdict_split(unit_dict: UnitsDict, count: int):
|
||||||
buffer_dict = {}
|
buffer_dict = {}
|
||||||
for unit_type, unit_count in unit_dict.items():
|
for unit_type, unit_count in unit_dict.items():
|
||||||
@ -476,6 +517,39 @@ def unitdict_restrict_count(unit_dict: UnitsDict, total_count: int) -> UnitsDict
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def assigned_units_split(fd: AssignedUnitsDict) -> typing.Tuple[PlaneDict, PlaneDict]:
|
||||||
|
return {k: v1 for k, (v1, v2) in fd.items()}, {k: v2 for k, (v1, v2) in fd.items()},
|
||||||
|
|
||||||
|
|
||||||
|
def assigned_units_from(d: PlaneDict) -> AssignedUnitsDict:
|
||||||
|
return {k: (v, 0) for k, v in d.items()}
|
||||||
|
|
||||||
|
|
||||||
|
def assignedunits_split_to_count(dict: AssignedUnitsDict, count: int):
|
||||||
|
buffer_dict = {}
|
||||||
|
for unit_type, (unit_count, client_count) in dict.items():
|
||||||
|
for _ in range(unit_count):
|
||||||
|
new_count, new_client_count = buffer_dict.get(unit_type, (0, 0))
|
||||||
|
|
||||||
|
new_count += 1
|
||||||
|
|
||||||
|
if client_count > 0:
|
||||||
|
new_client_count += 1
|
||||||
|
client_count -= 1
|
||||||
|
|
||||||
|
buffer_dict[unit_type] = new_count, new_client_count
|
||||||
|
if new_count >= count:
|
||||||
|
yield buffer_dict
|
||||||
|
buffer_dict = {}
|
||||||
|
|
||||||
|
if len(buffer_dict):
|
||||||
|
yield buffer_dict
|
||||||
|
|
||||||
|
|
||||||
|
def unitdict_from(fd: AssignedUnitsDict) -> Dict:
|
||||||
|
return {k: v1 for k, (v1, v2) in fd.items()}
|
||||||
|
|
||||||
|
|
||||||
def _validate_db():
|
def _validate_db():
|
||||||
# check unit by task uniquity
|
# check unit by task uniquity
|
||||||
total_set = set()
|
total_set = set()
|
||||||
|
|||||||
@ -4,6 +4,6 @@ from .frontlinepatrol import *
|
|||||||
from .intercept import *
|
from .intercept import *
|
||||||
from .baseattack import *
|
from .baseattack import *
|
||||||
from .navalintercept import *
|
from .navalintercept import *
|
||||||
from .antiaastrike import *
|
|
||||||
from .insurgentattack import *
|
from .insurgentattack import *
|
||||||
from .infantrytransport import *
|
from .infantrytransport import *
|
||||||
|
from .strike import *
|
||||||
|
|||||||
@ -1,86 +0,0 @@
|
|||||||
import math
|
|
||||||
import random
|
|
||||||
|
|
||||||
from dcs.task import *
|
|
||||||
|
|
||||||
from game import *
|
|
||||||
from game.event import *
|
|
||||||
from game.operation.antiaastrike import AntiAAStrikeOperation
|
|
||||||
from userdata.debriefing import Debriefing
|
|
||||||
|
|
||||||
|
|
||||||
class AntiAAStrikeEvent(Event):
|
|
||||||
TARGET_AMOUNT_MAX = 2
|
|
||||||
STRENGTH_INFLUENCE = 0.3
|
|
||||||
SUCCESS_TARGETS_HIT_PERCENTAGE = 0.5
|
|
||||||
|
|
||||||
targets = None # type: db.ArmorDict
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Anti-AA strike"
|
|
||||||
|
|
||||||
def is_successfull(self, debriefing: Debriefing):
|
|
||||||
total_targets = sum(self.targets.values())
|
|
||||||
destroyed_targets = 0
|
|
||||||
for unit, count in debriefing.destroyed_units[self.defender_name].items():
|
|
||||||
if unit in self.targets:
|
|
||||||
destroyed_targets += count
|
|
||||||
|
|
||||||
if self.from_cp.captured:
|
|
||||||
return math.ceil(float(destroyed_targets) / total_targets) >= self.SUCCESS_TARGETS_HIT_PERCENTAGE
|
|
||||||
else:
|
|
||||||
return math.ceil(float(destroyed_targets) / total_targets) < self.SUCCESS_TARGETS_HIT_PERCENTAGE
|
|
||||||
|
|
||||||
def commit(self, debriefing: Debriefing):
|
|
||||||
super(AntiAAStrikeEvent, self).commit(debriefing)
|
|
||||||
|
|
||||||
if self.from_cp.captured:
|
|
||||||
if self.is_successfull(debriefing):
|
|
||||||
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
|
|
||||||
else:
|
|
||||||
self.to_cp.base.affect_strength(+self.STRENGTH_INFLUENCE)
|
|
||||||
else:
|
|
||||||
if self.is_successfull(debriefing):
|
|
||||||
self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
|
|
||||||
else:
|
|
||||||
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
|
|
||||||
|
|
||||||
def skip(self):
|
|
||||||
if self.to_cp.captured:
|
|
||||||
self.to_cp.base.affect_strength(-0.1)
|
|
||||||
|
|
||||||
def player_attacking(self, strikegroup: db.PlaneDict, clients: db.PlaneDict):
|
|
||||||
self.targets = self.to_cp.base.assemble_aa(count=self.to_cp.base.total_aa)
|
|
||||||
|
|
||||||
op = AntiAAStrikeOperation(game=self.game,
|
|
||||||
attacker_name=self.attacker_name,
|
|
||||||
defender_name=self.defender_name,
|
|
||||||
attacker_clients=clients,
|
|
||||||
defender_clients={},
|
|
||||||
from_cp=self.from_cp,
|
|
||||||
to_cp=self.to_cp)
|
|
||||||
op.setup(target=self.targets,
|
|
||||||
strikegroup=strikegroup,
|
|
||||||
interceptors={})
|
|
||||||
|
|
||||||
self.operation = op
|
|
||||||
|
|
||||||
def player_defending(self, interceptors: db.PlaneDict, clients: db.PlaneDict):
|
|
||||||
self.targets = self.to_cp.base.assemble_aa()
|
|
||||||
|
|
||||||
op = AntiAAStrikeOperation(
|
|
||||||
self.game,
|
|
||||||
attacker_name=self.attacker_name,
|
|
||||||
defender_name=self.defender_name,
|
|
||||||
attacker_clients={},
|
|
||||||
defender_clients=clients,
|
|
||||||
from_cp=self.from_cp,
|
|
||||||
to_cp=self.to_cp
|
|
||||||
)
|
|
||||||
|
|
||||||
strikegroup = self.from_cp.base.scramble_cas(self.game.settings.multiplier)
|
|
||||||
op.setup(target=self.targets,
|
|
||||||
strikegroup=strikegroup,
|
|
||||||
interceptors=interceptors)
|
|
||||||
|
|
||||||
self.operation = op
|
|
||||||
@ -1,13 +1,7 @@
|
|||||||
import math
|
|
||||||
import random
|
|
||||||
|
|
||||||
from dcs.task import *
|
|
||||||
|
|
||||||
from game import db
|
|
||||||
from game.operation.baseattack import BaseAttackOperation
|
from game.operation.baseattack import BaseAttackOperation
|
||||||
from userdata.debriefing import Debriefing
|
|
||||||
|
|
||||||
from .event import Event
|
from .event import *
|
||||||
|
from game.db import assigned_units_from
|
||||||
|
|
||||||
|
|
||||||
class BaseAttackEvent(Event):
|
class BaseAttackEvent(Event):
|
||||||
@ -18,6 +12,18 @@ class BaseAttackEvent(Event):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Base attack"
|
return "Base attack"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tasks(self):
|
||||||
|
return [CAP, CAS, PinpointStrike]
|
||||||
|
|
||||||
|
def flight_name(self, for_task: typing.Type[Task]) -> str:
|
||||||
|
if for_task == CAP:
|
||||||
|
return "Escort flight"
|
||||||
|
elif for_task == CAS:
|
||||||
|
return "CAS flight"
|
||||||
|
elif for_task == PinpointStrike:
|
||||||
|
return "Ground attack"
|
||||||
|
|
||||||
def is_successfull(self, debriefing: Debriefing):
|
def is_successfull(self, debriefing: Debriefing):
|
||||||
alive_attackers = sum([v for k, v in debriefing.alive_units[self.attacker_name].items() if db.unit_task(k) == PinpointStrike])
|
alive_attackers = sum([v for k, v in debriefing.alive_units[self.attacker_name].items() if db.unit_task(k) == PinpointStrike])
|
||||||
alive_defenders = sum([v for k, v in debriefing.alive_units[self.defender_name].items() if db.unit_task(k) == PinpointStrike])
|
alive_defenders = sum([v for k, v in debriefing.alive_units[self.defender_name].items() if db.unit_task(k) == PinpointStrike])
|
||||||
@ -32,6 +38,7 @@ class BaseAttackEvent(Event):
|
|||||||
if self.is_successfull(debriefing):
|
if self.is_successfull(debriefing):
|
||||||
if self.from_cp.captured:
|
if self.from_cp.captured:
|
||||||
self.to_cp.captured = True
|
self.to_cp.captured = True
|
||||||
|
self.to_cp.ground_objects = []
|
||||||
self.to_cp.base.filter_units(db.UNIT_BY_COUNTRY[self.attacker_name])
|
self.to_cp.base.filter_units(db.UNIT_BY_COUNTRY[self.attacker_name])
|
||||||
|
|
||||||
self.to_cp.base.affect_strength(+self.STRENGTH_RECOVERY)
|
self.to_cp.base.affect_strength(+self.STRENGTH_RECOVERY)
|
||||||
@ -44,7 +51,9 @@ class BaseAttackEvent(Event):
|
|||||||
if not self.is_player_attacking and self.to_cp.captured:
|
if not self.is_player_attacking and self.to_cp.captured:
|
||||||
self.to_cp.captured = False
|
self.to_cp.captured = False
|
||||||
|
|
||||||
def player_defending(self, interceptors: db.PlaneDict, clients: db.PlaneDict):
|
def player_defending(self, flights: db.TaskForceDict):
|
||||||
|
assert CAP in flights and len(flights) == 1, "Invalid scrambled flights"
|
||||||
|
|
||||||
cas = self.from_cp.base.scramble_cas(self.game.settings.multiplier)
|
cas = self.from_cp.base.scramble_cas(self.game.settings.multiplier)
|
||||||
escort = self.from_cp.base.scramble_sweep(self.game.settings.multiplier)
|
escort = self.from_cp.base.scramble_sweep(self.game.settings.multiplier)
|
||||||
attackers = self.from_cp.base.armor
|
attackers = self.from_cp.base.armor
|
||||||
@ -52,36 +61,34 @@ class BaseAttackEvent(Event):
|
|||||||
op = BaseAttackOperation(game=self.game,
|
op = BaseAttackOperation(game=self.game,
|
||||||
attacker_name=self.attacker_name,
|
attacker_name=self.attacker_name,
|
||||||
defender_name=self.defender_name,
|
defender_name=self.defender_name,
|
||||||
attacker_clients={},
|
|
||||||
defender_clients=clients,
|
|
||||||
from_cp=self.from_cp,
|
from_cp=self.from_cp,
|
||||||
to_cp=self.to_cp)
|
to_cp=self.to_cp)
|
||||||
|
|
||||||
op.setup(cas=cas,
|
op.setup(cas=assigned_units_from(cas),
|
||||||
escort=escort,
|
escort=assigned_units_from(escort),
|
||||||
|
intercept=flights[CAP],
|
||||||
attack=attackers,
|
attack=attackers,
|
||||||
intercept=interceptors,
|
|
||||||
defense=self.to_cp.base.armor,
|
defense=self.to_cp.base.armor,
|
||||||
aa=self.to_cp.base.aa)
|
aa=self.to_cp.base.aa)
|
||||||
|
|
||||||
self.operation = op
|
self.operation = op
|
||||||
|
|
||||||
def player_attacking(self, cas: db.PlaneDict, escort: db.PlaneDict, armor: db.ArmorDict, clients: db.PlaneDict):
|
def player_attacking(self, flights: db.TaskForceDict):
|
||||||
|
assert CAP in flights and CAS in flights and PinpointStrike in flights and len(flights) == 3, "Invalid flights"
|
||||||
|
|
||||||
op = BaseAttackOperation(game=self.game,
|
op = BaseAttackOperation(game=self.game,
|
||||||
attacker_name=self.attacker_name,
|
attacker_name=self.attacker_name,
|
||||||
defender_name=self.defender_name,
|
defender_name=self.defender_name,
|
||||||
attacker_clients=clients,
|
|
||||||
defender_clients={},
|
|
||||||
from_cp=self.from_cp,
|
from_cp=self.from_cp,
|
||||||
to_cp=self.to_cp)
|
to_cp=self.to_cp)
|
||||||
|
|
||||||
defenders = self.to_cp.base.scramble_sweep(self.game.settings.multiplier)
|
defenders = self.to_cp.base.scramble_sweep(self.game.settings.multiplier)
|
||||||
defenders.update(self.to_cp.base.scramble_cas(self.game.settings.multiplier))
|
defenders.update(self.to_cp.base.scramble_cas(self.game.settings.multiplier))
|
||||||
|
|
||||||
op.setup(cas=cas,
|
op.setup(cas=flights[CAS],
|
||||||
escort=escort,
|
escort=flights[CAP],
|
||||||
attack=armor,
|
attack=unitdict_from(flights[PinpointStrike]),
|
||||||
intercept=defenders,
|
intercept=assigned_units_from(defenders),
|
||||||
defense=self.to_cp.base.armor,
|
defense=self.to_cp.base.armor,
|
||||||
aa=self.to_cp.base.assemble_aa())
|
aa=self.to_cp.base.assemble_aa())
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,15 @@
|
|||||||
|
import typing
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from dcs.unittype import UnitType
|
||||||
|
from dcs.task import *
|
||||||
|
from dcs.vehicles import AirDefence
|
||||||
from dcs.unittype import UnitType
|
from dcs.unittype import UnitType
|
||||||
|
|
||||||
from game import *
|
from game import *
|
||||||
from theater import *
|
from theater import *
|
||||||
from gen.environmentgen import EnvironmentSettings
|
from gen.environmentgen import EnvironmentSettings
|
||||||
|
from game.db import assigned_units_from, unitdict_from
|
||||||
|
|
||||||
from userdata.debriefing import Debriefing
|
from userdata.debriefing import Debriefing
|
||||||
from userdata import persistency
|
from userdata import persistency
|
||||||
@ -14,6 +21,7 @@ class Event:
|
|||||||
silent = False
|
silent = False
|
||||||
informational = False
|
informational = False
|
||||||
is_awacs_enabled = False
|
is_awacs_enabled = False
|
||||||
|
ca_slots = 0
|
||||||
operation = None # type: Operation
|
operation = None # type: Operation
|
||||||
difficulty = 1 # type: int
|
difficulty = 1 # type: int
|
||||||
game = None # type: Game
|
game = None # type: Game
|
||||||
@ -42,18 +50,36 @@ class Event:
|
|||||||
def threat_description(self) -> str:
|
def threat_description(self) -> str:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def flight_name(self, for_task: typing.Type[typing.Type[Task]]) -> str:
|
||||||
|
return "Flight"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tasks(self) -> typing.Collection[typing.Type[Task]]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ai_banned_tasks(self) -> typing.Collection[typing.Type[Task]]:
|
||||||
|
return []
|
||||||
|
|
||||||
def bonus(self) -> int:
|
def bonus(self) -> int:
|
||||||
return int(math.log(self.to_cp.importance + 1, DIFFICULTY_LOG_BASE) * self.BONUS_BASE)
|
return int(math.log(self.to_cp.importance + 1, DIFFICULTY_LOG_BASE) * self.BONUS_BASE)
|
||||||
|
|
||||||
def is_successfull(self, debriefing: Debriefing) -> bool:
|
def is_successfull(self, debriefing: Debriefing) -> bool:
|
||||||
return self.operation.is_successfull(debriefing)
|
return self.operation.is_successfull(debriefing)
|
||||||
|
|
||||||
|
def player_attacking(self, flights: db.TaskForceDict):
|
||||||
|
assert False
|
||||||
|
|
||||||
|
def player_defending(self, flights: db.TaskForceDict):
|
||||||
|
assert False
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
self.operation.is_awacs_enabled = self.is_awacs_enabled
|
self.operation.is_awacs_enabled = self.is_awacs_enabled
|
||||||
|
self.operation.ca_slots = self.ca_slots
|
||||||
|
|
||||||
self.operation.prepare(self.game.theater.terrain, is_quick=False)
|
self.operation.prepare(self.game.theater.terrain, is_quick=False)
|
||||||
self.operation.generate()
|
self.operation.generate()
|
||||||
self.operation.mission.save(persistency.mission_path_for("liberation_nextturn.miz"))
|
self.operation.current_mission.save(persistency.mission_path_for("liberation_nextturn.miz"))
|
||||||
self.environment_settings = self.operation.environment_settings
|
self.environment_settings = self.operation.environment_settings
|
||||||
|
|
||||||
def generate_quick(self):
|
def generate_quick(self):
|
||||||
@ -62,7 +88,7 @@ class Event:
|
|||||||
|
|
||||||
self.operation.prepare(self.game.theater.terrain, is_quick=True)
|
self.operation.prepare(self.game.theater.terrain, is_quick=True)
|
||||||
self.operation.generate()
|
self.operation.generate()
|
||||||
self.operation.mission.save(persistency.mission_path_for("liberation_nextturn_quick.miz"))
|
self.operation.current_mission.save(persistency.mission_path_for("liberation_nextturn_quick.miz"))
|
||||||
|
|
||||||
def commit(self, debriefing: Debriefing):
|
def commit(self, debriefing: Debriefing):
|
||||||
for country, losses in debriefing.destroyed_units.items():
|
for country, losses in debriefing.destroyed_units.items():
|
||||||
@ -71,8 +97,22 @@ class Event:
|
|||||||
else:
|
else:
|
||||||
cp = self.to_cp
|
cp = self.to_cp
|
||||||
|
|
||||||
|
logging.info("base {} commit losses {}".format(cp.base, losses))
|
||||||
cp.base.commit_losses(losses)
|
cp.base.commit_losses(losses)
|
||||||
|
|
||||||
|
for object_identifier in debriefing.destroyed_objects:
|
||||||
|
for cp in self.game.theater.controlpoints:
|
||||||
|
if not cp.ground_objects:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for i, ground_object in enumerate(cp.ground_objects):
|
||||||
|
if ground_object.is_dead:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if ground_object.matches_string_identifier(object_identifier):
|
||||||
|
logging.info("cp {} killing ground object {}".format(cp, ground_object.string_identifier))
|
||||||
|
cp.ground_objects[i].is_dead = True
|
||||||
|
|
||||||
def skip(self):
|
def skip(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,3 @@
|
|||||||
import math
|
|
||||||
import random
|
|
||||||
|
|
||||||
from dcs.task import *
|
|
||||||
from dcs.vehicles import AirDefence
|
|
||||||
|
|
||||||
from game import *
|
|
||||||
from game.event import *
|
from game.event import *
|
||||||
from game.operation.frontlineattack import FrontlineAttackOperation
|
from game.operation.frontlineattack import FrontlineAttackOperation
|
||||||
from userdata.debriefing import Debriefing
|
from userdata.debriefing import Debriefing
|
||||||
@ -15,7 +8,7 @@ class FrontlineAttackEvent(Event):
|
|||||||
TARGET_AMOUNT_FACTOR = 0.5
|
TARGET_AMOUNT_FACTOR = 0.5
|
||||||
ATTACKER_AMOUNT_FACTOR = 0.4
|
ATTACKER_AMOUNT_FACTOR = 0.4
|
||||||
ATTACKER_DEFENDER_FACTOR = 0.7
|
ATTACKER_DEFENDER_FACTOR = 0.7
|
||||||
STRENGTH_INFLUENCE = 0.2
|
STRENGTH_INFLUENCE = 0.3
|
||||||
SUCCESS_FACTOR = 1.5
|
SUCCESS_FACTOR = 1.5
|
||||||
|
|
||||||
defenders = None # type: db.ArmorDict
|
defenders = None # type: db.ArmorDict
|
||||||
@ -24,6 +17,21 @@ class FrontlineAttackEvent(Event):
|
|||||||
def threat_description(self):
|
def threat_description(self):
|
||||||
return "{} vehicles".format(self.to_cp.base.assemble_count())
|
return "{} vehicles".format(self.to_cp.base.assemble_count())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tasks(self) -> typing.Collection[typing.Type[Task]]:
|
||||||
|
if self.is_player_attacking:
|
||||||
|
return [CAS, PinpointStrike]
|
||||||
|
else:
|
||||||
|
return [CAP, PinpointStrike]
|
||||||
|
|
||||||
|
def flight_name(self, for_task: typing.Type[Task]) -> str:
|
||||||
|
if for_task == CAS:
|
||||||
|
return "CAS flight"
|
||||||
|
elif for_task == CAP:
|
||||||
|
return "CAP flight"
|
||||||
|
elif for_task == PinpointStrike:
|
||||||
|
return "Ground attack"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Frontline attack"
|
return "Frontline attack"
|
||||||
|
|
||||||
@ -54,20 +62,21 @@ class FrontlineAttackEvent(Event):
|
|||||||
if self.to_cp.captured:
|
if self.to_cp.captured:
|
||||||
self.to_cp.base.affect_strength(-0.1)
|
self.to_cp.base.affect_strength(-0.1)
|
||||||
|
|
||||||
def player_attacking(self, armor: db.ArmorDict, strikegroup: db.PlaneDict, clients: db.PlaneDict):
|
def player_attacking(self, flights: db.TaskForceDict):
|
||||||
|
assert CAS in flights and PinpointStrike in flights and len(flights) == 2, "Invalid flights"
|
||||||
|
|
||||||
self.defenders = self.to_cp.base.assemble_attack()
|
self.defenders = self.to_cp.base.assemble_attack()
|
||||||
|
|
||||||
op = FrontlineAttackOperation(game=self.game,
|
op = FrontlineAttackOperation(game=self.game,
|
||||||
attacker_name=self.attacker_name,
|
attacker_name=self.attacker_name,
|
||||||
defender_name=self.defender_name,
|
defender_name=self.defender_name,
|
||||||
attacker_clients=clients,
|
|
||||||
defender_clients={},
|
|
||||||
from_cp=self.from_cp,
|
from_cp=self.from_cp,
|
||||||
to_cp=self.to_cp)
|
to_cp=self.to_cp)
|
||||||
|
|
||||||
|
armor = unitdict_from(flights[PinpointStrike])
|
||||||
op.setup(target=self.defenders,
|
op.setup(target=self.defenders,
|
||||||
attackers=db.unitdict_restrict_count(armor, sum(self.defenders.values())),
|
attackers=db.unitdict_restrict_count(armor, sum(self.defenders.values())),
|
||||||
strikegroup=strikegroup)
|
strikegroup=flights[CAS])
|
||||||
|
|
||||||
self.operation = op
|
self.operation = op
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,3 @@
|
|||||||
import math
|
|
||||||
import random
|
|
||||||
|
|
||||||
from dcs.task import *
|
|
||||||
from dcs.vehicles import AirDefence
|
|
||||||
|
|
||||||
from game import *
|
|
||||||
from game.event import *
|
from game.event import *
|
||||||
from game.operation.frontlinepatrol import FrontlinePatrolOperation
|
from game.operation.frontlinepatrol import FrontlinePatrolOperation
|
||||||
from userdata.debriefing import Debriefing
|
from userdata.debriefing import Debriefing
|
||||||
@ -12,7 +5,7 @@ from userdata.debriefing import Debriefing
|
|||||||
|
|
||||||
class FrontlinePatrolEvent(Event):
|
class FrontlinePatrolEvent(Event):
|
||||||
ESCORT_FACTOR = 0.5
|
ESCORT_FACTOR = 0.5
|
||||||
STRENGTH_INFLUENCE = 0.2
|
STRENGTH_INFLUENCE = 0.3
|
||||||
SUCCESS_FACTOR = 0.8
|
SUCCESS_FACTOR = 0.8
|
||||||
|
|
||||||
cas = None # type: db.PlaneDict
|
cas = None # type: db.PlaneDict
|
||||||
@ -22,23 +15,19 @@ class FrontlinePatrolEvent(Event):
|
|||||||
def threat_description(self):
|
def threat_description(self):
|
||||||
return "{} aircraft + ? CAS".format(self.to_cp.base.scramble_count(self.game.settings.multiplier * self.ESCORT_FACTOR, CAP))
|
return "{} aircraft + ? CAS".format(self.to_cp.base.scramble_count(self.game.settings.multiplier * self.ESCORT_FACTOR, CAP))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tasks(self):
|
||||||
|
return [CAP, PinpointStrike]
|
||||||
|
|
||||||
|
def flight_name(self, for_task: typing.Type[Task]) -> str:
|
||||||
|
if for_task == CAP:
|
||||||
|
return "CAP flight"
|
||||||
|
elif for_task == PinpointStrike:
|
||||||
|
return "Ground attack"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Frontline CAP"
|
return "Frontline CAP"
|
||||||
|
|
||||||
"""
|
|
||||||
def is_successfull(self, debriefing: Debriefing):
|
|
||||||
total_targets = sum(self.cas.values())
|
|
||||||
destroyed_targets = 0
|
|
||||||
for unit, count in debriefing.destroyed_units[self.defender_name].items():
|
|
||||||
if unit in self.cas:
|
|
||||||
destroyed_targets += count
|
|
||||||
|
|
||||||
if self.from_cp.captured:
|
|
||||||
return float(destroyed_targets) / total_targets >= self.SUCCESS_TARGETS_HIT_PERCENTAGE
|
|
||||||
else:
|
|
||||||
return float(destroyed_targets) / total_targets < self.SUCCESS_TARGETS_HIT_PERCENTAGE
|
|
||||||
"""
|
|
||||||
|
|
||||||
def is_successfull(self, debriefing: Debriefing):
|
def is_successfull(self, debriefing: Debriefing):
|
||||||
alive_attackers = sum([v for k, v in debriefing.alive_units[self.attacker_name].items() if db.unit_task(k) == PinpointStrike])
|
alive_attackers = sum([v for k, v in debriefing.alive_units[self.attacker_name].items() if db.unit_task(k) == PinpointStrike])
|
||||||
alive_defenders = sum([v for k, v in debriefing.alive_units[self.defender_name].items() if db.unit_task(k) == PinpointStrike])
|
alive_defenders = sum([v for k, v in debriefing.alive_units[self.defender_name].items() if db.unit_task(k) == PinpointStrike])
|
||||||
@ -65,23 +54,23 @@ class FrontlinePatrolEvent(Event):
|
|||||||
def skip(self):
|
def skip(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def player_attacking(self, interceptors: db.PlaneDict, clients: db.PlaneDict, armor: db.ArmorDict):
|
def player_attacking(self, flights: db.TaskForceDict):
|
||||||
|
assert CAP in flights and PinpointStrike in flights and len(flights) == 2, "Invalid flights"
|
||||||
|
|
||||||
self.cas = self.to_cp.base.scramble_cas(self.game.settings.multiplier)
|
self.cas = self.to_cp.base.scramble_cas(self.game.settings.multiplier)
|
||||||
self.escort = self.to_cp.base.scramble_sweep(self.game.settings.multiplier * self.ESCORT_FACTOR)
|
self.escort = self.to_cp.base.scramble_sweep(self.game.settings.multiplier * self.ESCORT_FACTOR)
|
||||||
|
|
||||||
op = FrontlinePatrolOperation(game=self.game,
|
op = FrontlinePatrolOperation(game=self.game,
|
||||||
attacker_name=self.attacker_name,
|
attacker_name=self.attacker_name,
|
||||||
defender_name=self.defender_name,
|
defender_name=self.defender_name,
|
||||||
attacker_clients=clients,
|
|
||||||
defender_clients={},
|
|
||||||
from_cp=self.from_cp,
|
from_cp=self.from_cp,
|
||||||
to_cp=self.to_cp)
|
to_cp=self.to_cp)
|
||||||
|
|
||||||
defenders = self.to_cp.base.assemble_attack()
|
defenders = self.to_cp.base.assemble_attack()
|
||||||
op.setup(cas=self.cas,
|
op.setup(cas=assigned_units_from(self.cas),
|
||||||
escort=self.escort,
|
escort=assigned_units_from(self.escort),
|
||||||
interceptors=interceptors,
|
interceptors=flights[CAP],
|
||||||
armor_attackers=db.unitdict_restrict_count(armor, sum(defenders.values())),
|
armor_attackers=db.unitdict_restrict_count(db.unitdict_from(flights[PinpointStrike]), sum(defenders.values())),
|
||||||
armor_defenders=defenders)
|
armor_defenders=defenders)
|
||||||
|
|
||||||
self.operation = op
|
self.operation = op
|
||||||
|
|||||||
@ -9,7 +9,7 @@ from game.operation.infantrytransport import InfantryTransportOperation
|
|||||||
from theater.conflicttheater import *
|
from theater.conflicttheater import *
|
||||||
from userdata.debriefing import Debriefing
|
from userdata.debriefing import Debriefing
|
||||||
|
|
||||||
from .event import Event
|
from .event import *
|
||||||
|
|
||||||
|
|
||||||
class InfantryTransportEvent(Event):
|
class InfantryTransportEvent(Event):
|
||||||
@ -18,6 +18,14 @@ class InfantryTransportEvent(Event):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Frontline transport troops"
|
return "Frontline transport troops"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tasks(self):
|
||||||
|
return [Embarking]
|
||||||
|
|
||||||
|
def flight_name(self, for_task: typing.Type[Task]) -> str:
|
||||||
|
if for_task == Embarking:
|
||||||
|
return "Transport flight"
|
||||||
|
|
||||||
def is_successfull(self, debriefing: Debriefing):
|
def is_successfull(self, debriefing: Debriefing):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -29,19 +37,19 @@ class InfantryTransportEvent(Event):
|
|||||||
else:
|
else:
|
||||||
self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
|
self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
|
||||||
|
|
||||||
def player_attacking(self, transport: db.HeliDict, clients: db.HeliDict):
|
def player_attacking(self, flights: db.TaskForceDict):
|
||||||
|
assert Embarking in flights and len(flights) == 1, "Invalid flights"
|
||||||
|
|
||||||
op = InfantryTransportOperation(
|
op = InfantryTransportOperation(
|
||||||
game=self.game,
|
game=self.game,
|
||||||
attacker_name=self.attacker_name,
|
attacker_name=self.attacker_name,
|
||||||
defender_name=self.defender_name,
|
defender_name=self.defender_name,
|
||||||
attacker_clients=clients,
|
|
||||||
defender_clients={},
|
|
||||||
from_cp=self.from_cp,
|
from_cp=self.from_cp,
|
||||||
to_cp=self.to_cp
|
to_cp=self.to_cp
|
||||||
)
|
)
|
||||||
|
|
||||||
air_defense = db.find_unittype(AirDefence, self.defender_name)[0]
|
air_defense = db.find_unittype(AirDefence, self.defender_name)[0]
|
||||||
op.setup(transport=transport,
|
op.setup(transport=flights[Embarking],
|
||||||
aa={air_defense: 2})
|
aa={air_defense: 2})
|
||||||
|
|
||||||
self.operation = op
|
self.operation = op
|
||||||
|
|||||||
@ -8,19 +8,33 @@ from game.event import *
|
|||||||
from game.event.frontlineattack import FrontlineAttackEvent
|
from game.event.frontlineattack import FrontlineAttackEvent
|
||||||
from game.operation.insurgentattack import InsurgentAttackOperation
|
from game.operation.insurgentattack import InsurgentAttackOperation
|
||||||
|
|
||||||
|
from .event import *
|
||||||
|
|
||||||
|
|
||||||
class InsurgentAttackEvent(Event):
|
class InsurgentAttackEvent(Event):
|
||||||
SUCCESS_FACTOR = 0.7
|
SUCCESS_FACTOR = 0.7
|
||||||
TARGET_VARIETY = 2
|
TARGET_VARIETY = 2
|
||||||
TARGET_AMOUNT_FACTOR = 0.5
|
TARGET_AMOUNT_FACTOR = 0.5
|
||||||
|
STRENGTH_INFLUENCE = 0.1
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def threat_description(self):
|
def threat_description(self):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tasks(self):
|
||||||
|
return [CAS]
|
||||||
|
|
||||||
|
def flight_name(self, for_task: typing.Type[Task]) -> str:
|
||||||
|
if for_task == CAS:
|
||||||
|
return "Ground intercept flight"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Destroy insurgents"
|
return "Destroy insurgents"
|
||||||
|
|
||||||
|
def skip(self):
|
||||||
|
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
|
||||||
|
|
||||||
def is_successfull(self, debriefing: Debriefing):
|
def is_successfull(self, debriefing: Debriefing):
|
||||||
killed_units = sum([v for k, v in debriefing.destroyed_units[self.attacker_name].items() if db.unit_task(k) == PinpointStrike])
|
killed_units = sum([v for k, v in debriefing.destroyed_units[self.attacker_name].items() if db.unit_task(k) == PinpointStrike])
|
||||||
all_units = sum(self.targets.values())
|
all_units = sum(self.targets.values())
|
||||||
@ -30,7 +44,9 @@ class InsurgentAttackEvent(Event):
|
|||||||
else:
|
else:
|
||||||
return not attackers_success
|
return not attackers_success
|
||||||
|
|
||||||
def player_defending(self, strikegroup: db.PlaneDict, clients: db.PlaneDict):
|
def player_defending(self, flights: db.TaskForceDict):
|
||||||
|
assert CAS in flights and len(flights) == 1, "Invalid flights"
|
||||||
|
|
||||||
suitable_unittypes = db.find_unittype(Reconnaissance, self.attacker_name)
|
suitable_unittypes = db.find_unittype(Reconnaissance, self.attacker_name)
|
||||||
random.shuffle(suitable_unittypes)
|
random.shuffle(suitable_unittypes)
|
||||||
unittypes = suitable_unittypes[:self.TARGET_VARIETY]
|
unittypes = suitable_unittypes[:self.TARGET_VARIETY]
|
||||||
@ -40,14 +56,9 @@ class InsurgentAttackEvent(Event):
|
|||||||
op = InsurgentAttackOperation(game=self.game,
|
op = InsurgentAttackOperation(game=self.game,
|
||||||
attacker_name=self.attacker_name,
|
attacker_name=self.attacker_name,
|
||||||
defender_name=self.defender_name,
|
defender_name=self.defender_name,
|
||||||
attacker_clients={},
|
|
||||||
defender_clients=clients,
|
|
||||||
from_cp=self.from_cp,
|
from_cp=self.from_cp,
|
||||||
to_cp=self.to_cp)
|
to_cp=self.to_cp)
|
||||||
op.setup(target=self.targets,
|
op.setup(target=self.targets,
|
||||||
strikegroup=strikegroup)
|
strikegroup=flights[CAS])
|
||||||
|
|
||||||
self.operation = op
|
self.operation = op
|
||||||
|
|
||||||
def player_attacking(self, interceptors: db.PlaneDict, clients: db.PlaneDict):
|
|
||||||
assert False
|
|
||||||
|
|||||||
@ -1,15 +1,6 @@
|
|||||||
import math
|
|
||||||
import random
|
|
||||||
|
|
||||||
from dcs.task import *
|
|
||||||
from dcs.vehicles import *
|
|
||||||
|
|
||||||
from game import db
|
|
||||||
from game.operation.intercept import InterceptOperation
|
from game.operation.intercept import InterceptOperation
|
||||||
from theater.conflicttheater import *
|
|
||||||
from userdata.debriefing import Debriefing
|
|
||||||
|
|
||||||
from .event import Event
|
from .event import *
|
||||||
|
|
||||||
|
|
||||||
class InterceptEvent(Event):
|
class InterceptEvent(Event):
|
||||||
@ -20,7 +11,18 @@ class InterceptEvent(Event):
|
|||||||
transport_unit = None # type: FlyingType
|
transport_unit = None # type: FlyingType
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Intercept"
|
return "Air Intercept"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tasks(self):
|
||||||
|
return [CAP]
|
||||||
|
|
||||||
|
def flight_name(self, for_task: typing.Type[Task]) -> str:
|
||||||
|
if for_task == CAP:
|
||||||
|
if self.is_player_attacking:
|
||||||
|
return "Intercept flight"
|
||||||
|
else:
|
||||||
|
return "Escort flight"
|
||||||
|
|
||||||
def _enemy_scramble_multiplier(self) -> float:
|
def _enemy_scramble_multiplier(self) -> float:
|
||||||
is_global = self.from_cp.is_global or self.to_cp.is_global
|
is_global = self.from_cp.is_global or self.to_cp.is_global
|
||||||
@ -57,7 +59,9 @@ class InterceptEvent(Event):
|
|||||||
if self.to_cp.captured:
|
if self.to_cp.captured:
|
||||||
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
|
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
|
||||||
|
|
||||||
def player_attacking(self, interceptors: db.PlaneDict, clients: db.PlaneDict):
|
def player_attacking(self, flights: db.TaskForceDict):
|
||||||
|
assert CAP in flights and len(flights) == 1, "Invalid flights"
|
||||||
|
|
||||||
escort = self.to_cp.base.scramble_sweep(self._enemy_scramble_multiplier())
|
escort = self.to_cp.base.scramble_sweep(self._enemy_scramble_multiplier())
|
||||||
|
|
||||||
self.transport_unit = random.choice(db.find_unittype(Transport, self.defender_name))
|
self.transport_unit = random.choice(db.find_unittype(Transport, self.defender_name))
|
||||||
@ -67,20 +71,19 @@ class InterceptEvent(Event):
|
|||||||
op = InterceptOperation(game=self.game,
|
op = InterceptOperation(game=self.game,
|
||||||
attacker_name=self.attacker_name,
|
attacker_name=self.attacker_name,
|
||||||
defender_name=self.defender_name,
|
defender_name=self.defender_name,
|
||||||
attacker_clients=clients,
|
|
||||||
defender_clients={},
|
|
||||||
from_cp=self.from_cp,
|
from_cp=self.from_cp,
|
||||||
to_cp=self.to_cp)
|
to_cp=self.to_cp)
|
||||||
|
|
||||||
op.setup(escort=escort,
|
op.setup(escort=assigned_units_from(escort),
|
||||||
transport={self.transport_unit: 1},
|
transport={self.transport_unit: 1},
|
||||||
airdefense={airdefense_unit: self.AIRDEFENSE_COUNT},
|
airdefense={airdefense_unit: self.AIRDEFENSE_COUNT},
|
||||||
interceptors=interceptors)
|
interceptors=flights[CAP])
|
||||||
|
|
||||||
self.operation = op
|
self.operation = op
|
||||||
|
|
||||||
def player_defending(self, escort: db.PlaneDict, clients: db.PlaneDict):
|
def player_defending(self, flights: db.TaskForceDict):
|
||||||
# TODO: even not quick mission is too quick
|
assert CAP in flights and len(flights) == 1, "Invalid flights"
|
||||||
|
|
||||||
interceptors = self.from_cp.base.scramble_interceptors(self.game.settings.multiplier)
|
interceptors = self.from_cp.base.scramble_interceptors(self.game.settings.multiplier)
|
||||||
|
|
||||||
self.transport_unit = random.choice(db.find_unittype(Transport, self.defender_name))
|
self.transport_unit = random.choice(db.find_unittype(Transport, self.defender_name))
|
||||||
@ -89,14 +92,12 @@ class InterceptEvent(Event):
|
|||||||
op = InterceptOperation(game=self.game,
|
op = InterceptOperation(game=self.game,
|
||||||
attacker_name=self.attacker_name,
|
attacker_name=self.attacker_name,
|
||||||
defender_name=self.defender_name,
|
defender_name=self.defender_name,
|
||||||
attacker_clients={},
|
|
||||||
defender_clients=clients,
|
|
||||||
from_cp=self.from_cp,
|
from_cp=self.from_cp,
|
||||||
to_cp=self.to_cp)
|
to_cp=self.to_cp)
|
||||||
|
|
||||||
op.setup(escort=escort,
|
op.setup(escort=flights[CAP],
|
||||||
transport={self.transport_unit: 1},
|
transport={self.transport_unit: 1},
|
||||||
interceptors=interceptors,
|
interceptors=assigned_units_from(interceptors),
|
||||||
airdefense={})
|
airdefense={})
|
||||||
|
|
||||||
self.operation = op
|
self.operation = op
|
||||||
|
|||||||
@ -1,15 +1,6 @@
|
|||||||
import typing
|
|
||||||
import math
|
|
||||||
import random
|
|
||||||
|
|
||||||
from dcs.task import *
|
|
||||||
from dcs.vehicles import *
|
|
||||||
|
|
||||||
from game import db
|
|
||||||
from game.operation.navalintercept import NavalInterceptionOperation
|
from game.operation.navalintercept import NavalInterceptionOperation
|
||||||
from userdata.debriefing import Debriefing
|
|
||||||
|
|
||||||
from .event import Event
|
from .event import *
|
||||||
|
|
||||||
|
|
||||||
class NavalInterceptEvent(Event):
|
class NavalInterceptEvent(Event):
|
||||||
@ -19,13 +10,26 @@ class NavalInterceptEvent(Event):
|
|||||||
targets = None # type: db.ShipDict
|
targets = None # type: db.ShipDict
|
||||||
|
|
||||||
def _targets_count(self) -> int:
|
def _targets_count(self) -> int:
|
||||||
from gen.conflictgen import IMPORTANCE_LOW, IMPORTANCE_HIGH
|
from gen.conflictgen import IMPORTANCE_LOW
|
||||||
factor = (self.to_cp.importance - IMPORTANCE_LOW) * 10
|
factor = (self.to_cp.importance - IMPORTANCE_LOW + 0.1) * 20
|
||||||
return max(int(factor), 1)
|
return max(int(factor), 1)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return "Naval intercept"
|
return "Naval intercept"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tasks(self):
|
||||||
|
if self.is_player_attacking:
|
||||||
|
return [CAS]
|
||||||
|
else:
|
||||||
|
return [CAP]
|
||||||
|
|
||||||
|
def flight_name(self, for_task: typing.Type[Task]) -> str:
|
||||||
|
if for_task == CAS:
|
||||||
|
return "Naval intercept flight"
|
||||||
|
elif for_task == CAP:
|
||||||
|
return "CAP flight"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def threat_description(self):
|
def threat_description(self):
|
||||||
s = "{} ship(s)".format(self._targets_count())
|
s = "{} ship(s)".format(self._targets_count())
|
||||||
@ -64,7 +68,9 @@ class NavalInterceptEvent(Event):
|
|||||||
if self.to_cp.captured:
|
if self.to_cp.captured:
|
||||||
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
|
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
|
||||||
|
|
||||||
def player_attacking(self, strikegroup: db.PlaneDict, clients: db.PlaneDict):
|
def player_attacking(self, flights: db.TaskForceDict):
|
||||||
|
assert CAS in flights and len(flights) == 1, "Invalid flights"
|
||||||
|
|
||||||
self.targets = {
|
self.targets = {
|
||||||
random.choice(db.find_unittype(CargoTransportation, self.defender_name)): self._targets_count(),
|
random.choice(db.find_unittype(CargoTransportation, self.defender_name)): self._targets_count(),
|
||||||
}
|
}
|
||||||
@ -73,19 +79,19 @@ class NavalInterceptEvent(Event):
|
|||||||
self.game,
|
self.game,
|
||||||
attacker_name=self.attacker_name,
|
attacker_name=self.attacker_name,
|
||||||
defender_name=self.defender_name,
|
defender_name=self.defender_name,
|
||||||
attacker_clients=clients,
|
|
||||||
defender_clients={},
|
|
||||||
from_cp=self.from_cp,
|
from_cp=self.from_cp,
|
||||||
to_cp=self.to_cp
|
to_cp=self.to_cp
|
||||||
)
|
)
|
||||||
|
|
||||||
op.setup(strikegroup=strikegroup,
|
op.setup(strikegroup=flights[CAS],
|
||||||
interceptors={},
|
interceptors={},
|
||||||
targets=self.targets)
|
targets=self.targets)
|
||||||
|
|
||||||
self.operation = op
|
self.operation = op
|
||||||
|
|
||||||
def player_defending(self, interceptors: db.PlaneDict, clients: db.PlaneDict):
|
def player_defending(self, flights: db.TaskForceDict):
|
||||||
|
assert CAP in flights and len(flights) == 1, "Invalid flights"
|
||||||
|
|
||||||
self.targets = {
|
self.targets = {
|
||||||
random.choice(db.find_unittype(CargoTransportation, self.defender_name)): self._targets_count(),
|
random.choice(db.find_unittype(CargoTransportation, self.defender_name)): self._targets_count(),
|
||||||
}
|
}
|
||||||
@ -94,15 +100,13 @@ class NavalInterceptEvent(Event):
|
|||||||
self.game,
|
self.game,
|
||||||
attacker_name=self.attacker_name,
|
attacker_name=self.attacker_name,
|
||||||
defender_name=self.defender_name,
|
defender_name=self.defender_name,
|
||||||
attacker_clients={},
|
|
||||||
defender_clients=clients,
|
|
||||||
from_cp=self.from_cp,
|
from_cp=self.from_cp,
|
||||||
to_cp=self.to_cp
|
to_cp=self.to_cp
|
||||||
)
|
)
|
||||||
|
|
||||||
strikegroup = self.from_cp.base.scramble_cas(self.game.settings.multiplier)
|
strikegroup = self.from_cp.base.scramble_cas(self.game.settings.multiplier)
|
||||||
op.setup(strikegroup=strikegroup,
|
op.setup(strikegroup=assigned_units_from(strikegroup),
|
||||||
interceptors=interceptors,
|
interceptors=flights[CAP],
|
||||||
targets=self.targets)
|
targets=self.targets)
|
||||||
|
|
||||||
self.operation = op
|
self.operation = op
|
||||||
|
|||||||
61
game/event/strike.py
Normal file
61
game/event/strike.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
from game.operation.strike import StrikeOperation
|
||||||
|
|
||||||
|
from .event import *
|
||||||
|
|
||||||
|
|
||||||
|
class StrikeEvent(Event):
|
||||||
|
STRENGTH_INFLUENCE = 0.0
|
||||||
|
SINGLE_OBJECT_STRENGTH_INFLUENCE = 0.05
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Strike"
|
||||||
|
|
||||||
|
def is_successfull(self, debriefing: Debriefing):
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def threat_description(self):
|
||||||
|
return "{} aircraft + AA".format(self.to_cp.base.scramble_count(self.game.settings.multiplier, CAP))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tasks(self):
|
||||||
|
if self.is_player_attacking:
|
||||||
|
return [CAP, CAS]
|
||||||
|
else:
|
||||||
|
return [CAP]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ai_banned_tasks(self):
|
||||||
|
return [CAS]
|
||||||
|
|
||||||
|
def flight_name(self, for_task: typing.Type[Task]) -> str:
|
||||||
|
if for_task == CAP:
|
||||||
|
if self.is_player_attacking:
|
||||||
|
return "Escort flight"
|
||||||
|
else:
|
||||||
|
return "CAP flight"
|
||||||
|
elif for_task == CAS:
|
||||||
|
return "Strike flight"
|
||||||
|
|
||||||
|
def commit(self, debriefing: Debriefing):
|
||||||
|
super(StrikeEvent, self).commit(debriefing)
|
||||||
|
|
||||||
|
self.to_cp.base.affect_strength(-self.SINGLE_OBJECT_STRENGTH_INFLUENCE * len(debriefing.destroyed_objects))
|
||||||
|
|
||||||
|
def player_attacking(self, flights: db.TaskForceDict):
|
||||||
|
assert CAP in flights and CAS in flights and len(flights) == 2, "Invalid flights"
|
||||||
|
|
||||||
|
op = StrikeOperation(
|
||||||
|
self.game,
|
||||||
|
attacker_name=self.attacker_name,
|
||||||
|
defender_name=self.defender_name,
|
||||||
|
from_cp=self.from_cp,
|
||||||
|
to_cp=self.to_cp
|
||||||
|
)
|
||||||
|
|
||||||
|
interceptors = self.to_cp.base.scramble_interceptors(self.game.settings.multiplier)
|
||||||
|
op.setup(strikegroup=flights[CAS],
|
||||||
|
escort=flights[CAP],
|
||||||
|
interceptors=assigned_units_from(interceptors))
|
||||||
|
|
||||||
|
self.operation = op
|
||||||
133
game/game.py
133
game/game.py
@ -25,7 +25,7 @@ COMMISION_LIMITS_FACTORS = {
|
|||||||
|
|
||||||
COMMISION_AMOUNTS_SCALE = 1.5
|
COMMISION_AMOUNTS_SCALE = 1.5
|
||||||
COMMISION_AMOUNTS_FACTORS = {
|
COMMISION_AMOUNTS_FACTORS = {
|
||||||
PinpointStrike: 6,
|
PinpointStrike: 3,
|
||||||
CAS: 1,
|
CAS: 1,
|
||||||
CAP: 2,
|
CAP: 2,
|
||||||
AirDefence: 0.3,
|
AirDefence: 0.3,
|
||||||
@ -33,30 +33,39 @@ COMMISION_AMOUNTS_FACTORS = {
|
|||||||
|
|
||||||
PLAYER_INTERCEPT_GLOBAL_PROBABILITY_BASE = 30
|
PLAYER_INTERCEPT_GLOBAL_PROBABILITY_BASE = 30
|
||||||
PLAYER_INTERCEPT_GLOBAL_PROBABILITY_LOG = 2
|
PLAYER_INTERCEPT_GLOBAL_PROBABILITY_LOG = 2
|
||||||
PLAYER_BASEATTACK_THRESHOLD = 0.2
|
PLAYER_BASEATTACK_THRESHOLD = 0.4
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Various events probabilities. First key is player probabilty, second is enemy probability.
|
Various events probabilities. First key is player probabilty, second is enemy probability.
|
||||||
For the enemy events, only 1 event of each type could be generated for a turn.
|
For the enemy events, only 1 event of each type could be generated for a turn.
|
||||||
|
|
||||||
Events:
|
Events:
|
||||||
* CaptureEvent - capture base
|
* BaseAttackEvent - capture base
|
||||||
* InterceptEvent - air intercept
|
* InterceptEvent - air intercept
|
||||||
* FrontlineAttack - frontline attack
|
* FrontlineAttackEvent - frontline attack
|
||||||
* GroundAttackEvent - destroy insurgents
|
* FrontlineCAPEvent - frontline attack
|
||||||
* NavalInterceptEvent - naval intercept
|
* NavalInterceptEvent - naval intercept
|
||||||
* AntiAAStrikeEvent - anti-AA strike
|
* StrikeEvent - strike event
|
||||||
* InfantryTransportEvent - helicopter infantry transport
|
* InfantryTransportEvent - helicopter infantry transport
|
||||||
"""
|
"""
|
||||||
EVENT_PROBABILITIES = {
|
EVENT_PROBABILITIES = {
|
||||||
BaseAttackEvent: [100, 10],
|
# events always present; only for the player
|
||||||
FrontlineAttackEvent: [100, 0],
|
FrontlineAttackEvent: [100, 0],
|
||||||
FrontlinePatrolEvent: [100, 0],
|
FrontlinePatrolEvent: [100, 0],
|
||||||
InterceptEvent: [25, 10],
|
StrikeEvent: [100, 0],
|
||||||
InsurgentAttackEvent: [0, 10],
|
|
||||||
NavalInterceptEvent: [25, 10],
|
# events randomly present; only for the player
|
||||||
AntiAAStrikeEvent: [25, 10],
|
|
||||||
InfantryTransportEvent: [25, 0],
|
InfantryTransportEvent: [25, 0],
|
||||||
|
|
||||||
|
# events conditionally present; for both enemy and player
|
||||||
|
BaseAttackEvent: [100, 9],
|
||||||
|
|
||||||
|
# events randomly present; for both enemy and player
|
||||||
|
InterceptEvent: [25, 9],
|
||||||
|
NavalInterceptEvent: [25, 9],
|
||||||
|
|
||||||
|
# events randomly present; only for the enemy
|
||||||
|
InsurgentAttackEvent: [0, 6],
|
||||||
}
|
}
|
||||||
|
|
||||||
# amount of strength player bases recover for the turn
|
# amount of strength player bases recover for the turn
|
||||||
@ -71,7 +80,7 @@ AWACS_BUDGET_COST = 4
|
|||||||
# Initial budget value
|
# Initial budget value
|
||||||
PLAYER_BUDGET_INITIAL = 170
|
PLAYER_BUDGET_INITIAL = 170
|
||||||
# Base post-turn bonus value
|
# Base post-turn bonus value
|
||||||
PLAYER_BUDGET_BASE = 17
|
PLAYER_BUDGET_BASE = 14
|
||||||
# Bonus multiplier logarithm base
|
# Bonus multiplier logarithm base
|
||||||
PLAYER_BUDGET_IMPORTANCE_LOG = 2
|
PLAYER_BUDGET_IMPORTANCE_LOG = 2
|
||||||
|
|
||||||
@ -107,52 +116,82 @@ class Game:
|
|||||||
game=self))
|
game=self))
|
||||||
break
|
break
|
||||||
|
|
||||||
def _generate_events(self):
|
def _generate_player_event(self, event_class, player_cp, enemy_cp):
|
||||||
enemy_cap_generated = False
|
if event_class == NavalInterceptEvent and enemy_cp.radials == LAND:
|
||||||
enemy_generated_types = []
|
# skip naval events for non-coastal CPs
|
||||||
|
return
|
||||||
|
|
||||||
|
if event_class == BaseAttackEvent and enemy_cp.base.strength > PLAYER_BASEATTACK_THRESHOLD:
|
||||||
|
# skip base attack events for CPs yet too strong
|
||||||
|
return
|
||||||
|
|
||||||
|
if event_class == StrikeEvent and not enemy_cp.ground_objects:
|
||||||
|
# skip strikes in case of no targets
|
||||||
|
return
|
||||||
|
|
||||||
|
self.events.append(event_class(self.player, self.enemy, player_cp, enemy_cp, self))
|
||||||
|
|
||||||
|
def _generate_enemy_event(self, event_class, player_cp, enemy_cp):
|
||||||
|
if event_class in [type(x) for x in self.events if not self.is_player_attack(x)]:
|
||||||
|
# skip already generated enemy event types
|
||||||
|
return
|
||||||
|
|
||||||
|
if player_cp in self.ignored_cps:
|
||||||
|
# skip attacks against ignored CPs (for example just captured ones)
|
||||||
|
return
|
||||||
|
|
||||||
|
if enemy_cp.base.total_planes == 0:
|
||||||
|
# skip event if there's no planes on the base
|
||||||
|
return
|
||||||
|
|
||||||
|
if player_cp.is_global:
|
||||||
|
# skip carriers
|
||||||
|
return
|
||||||
|
|
||||||
|
if event_class == NavalInterceptEvent:
|
||||||
|
if player_cp.radials == LAND:
|
||||||
|
# skip naval events for non-coastal CPs
|
||||||
|
return
|
||||||
|
elif event_class == StrikeEvent:
|
||||||
|
if not player_cp.ground_objects:
|
||||||
|
# skip strikes if there's no ground objects
|
||||||
|
return
|
||||||
|
elif event_class == BaseAttackEvent:
|
||||||
|
if BaseAttackEvent in [type(x) for x in self.events]:
|
||||||
|
# skip base attack event if there's another one going on
|
||||||
|
return
|
||||||
|
|
||||||
|
if enemy_cp.base.total_armor == 0:
|
||||||
|
# skip base attack if there's no armor
|
||||||
|
return
|
||||||
|
|
||||||
|
if player_cp.base.strength > PLAYER_BASEATTACK_THRESHOLD:
|
||||||
|
# skip base attack if strength is too high
|
||||||
|
return
|
||||||
|
|
||||||
|
self.events.append(event_class(self.enemy, self.player, enemy_cp, player_cp, self))
|
||||||
|
|
||||||
|
def _generate_events(self):
|
||||||
for player_cp, enemy_cp in self.theater.conflicts(True):
|
for player_cp, enemy_cp in self.theater.conflicts(True):
|
||||||
if player_cp.is_global or enemy_cp.is_global:
|
if enemy_cp.is_global:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for event_class, (player_probability, enemy_probability) in EVENT_PROBABILITIES.items():
|
for event_class, (player_probability, enemy_probability) in EVENT_PROBABILITIES.items():
|
||||||
if event_class == FrontlineAttackEvent or event_class == InfantryTransportEvent or event_class == FrontlinePatrolEvent:
|
if event_class in [FrontlineAttackEvent, FrontlinePatrolEvent, InfantryTransportEvent]:
|
||||||
|
# skip events requiring frontline
|
||||||
if not Conflict.has_frontline_between(player_cp, enemy_cp):
|
if not Conflict.has_frontline_between(player_cp, enemy_cp):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if self._roll(player_probability, player_cp.base.strength):
|
if player_cp.is_global:
|
||||||
if event_class == NavalInterceptEvent and enemy_cp.radials == LAND:
|
# skip events requiring ground CP
|
||||||
pass
|
if event_class not in [InterceptEvent, StrikeEvent, NavalInterceptEvent]:
|
||||||
else:
|
|
||||||
if event_class == BaseAttackEvent and enemy_cp.base.strength > PLAYER_BASEATTACK_THRESHOLD:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
self.events.append(event_class(self.player, self.enemy, player_cp, enemy_cp, self))
|
|
||||||
elif self._roll(enemy_probability, enemy_cp.base.strength):
|
|
||||||
if event_class in enemy_generated_types:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if player_cp in self.ignored_cps:
|
if player_probability == 100 or self._roll(player_probability, player_cp.base.strength):
|
||||||
continue
|
self._generate_player_event(event_class, player_cp, enemy_cp)
|
||||||
|
|
||||||
if enemy_cp.base.total_planes == 0:
|
if enemy_probability == 100 or self._roll(enemy_probability, enemy_cp.base.strength):
|
||||||
continue
|
self._generate_enemy_event(event_class, player_cp, enemy_cp)
|
||||||
|
|
||||||
if event_class == NavalInterceptEvent:
|
|
||||||
if player_cp.radials == LAND:
|
|
||||||
continue
|
|
||||||
elif event_class == BaseAttackEvent:
|
|
||||||
if enemy_cap_generated:
|
|
||||||
continue
|
|
||||||
if enemy_cp.base.total_armor == 0:
|
|
||||||
continue
|
|
||||||
enemy_cap_generated = True
|
|
||||||
elif event_class == AntiAAStrikeEvent:
|
|
||||||
if player_cp.base.total_aa == 0:
|
|
||||||
continue
|
|
||||||
|
|
||||||
enemy_generated_types.append(event_class)
|
|
||||||
self.events.append(event_class(self.enemy, self.player, enemy_cp, player_cp, self))
|
|
||||||
|
|
||||||
def commision_unit_types(self, cp: ControlPoint, for_task: Task) -> typing.Collection[UnitType]:
|
def commision_unit_types(self, cp: ControlPoint, for_task: Task) -> typing.Collection[UnitType]:
|
||||||
importance_factor = (cp.importance - IMPORTANCE_LOW) / (IMPORTANCE_HIGH - IMPORTANCE_LOW)
|
importance_factor = (cp.importance - IMPORTANCE_LOW) / (IMPORTANCE_HIGH - IMPORTANCE_LOW)
|
||||||
|
|||||||
@ -1,53 +0,0 @@
|
|||||||
from dcs.terrain import Terrain
|
|
||||||
|
|
||||||
from game import db
|
|
||||||
from gen.armor import *
|
|
||||||
from gen.aircraft import *
|
|
||||||
from gen.aaa import *
|
|
||||||
from gen.shipgen import *
|
|
||||||
from gen.triggergen import *
|
|
||||||
from gen.airsupportgen import *
|
|
||||||
from gen.visualgen import *
|
|
||||||
from gen.conflictgen import Conflict
|
|
||||||
|
|
||||||
from .operation import Operation
|
|
||||||
|
|
||||||
|
|
||||||
class AntiAAStrikeOperation(Operation):
|
|
||||||
strikegroup = None # type: db.PlaneDict
|
|
||||||
interceptors = None # type: db.PlaneDict
|
|
||||||
target = None # type: db.ArmorDict
|
|
||||||
|
|
||||||
def setup(self,
|
|
||||||
target: db.ArmorDict,
|
|
||||||
strikegroup: db.PlaneDict,
|
|
||||||
interceptors: db.PlaneDict):
|
|
||||||
self.strikegroup = strikegroup
|
|
||||||
self.interceptors = interceptors
|
|
||||||
self.target = target
|
|
||||||
|
|
||||||
def prepare(self, terrain: Terrain, is_quick: bool):
|
|
||||||
super(AntiAAStrikeOperation, self).prepare(terrain, is_quick)
|
|
||||||
if self.defender_name == self.game.player:
|
|
||||||
self.attackers_starting_position = None
|
|
||||||
self.defenders_starting_position = None
|
|
||||||
|
|
||||||
conflict = Conflict.ground_base_attack(
|
|
||||||
attacker=self.mission.country(self.attacker_name),
|
|
||||||
defender=self.mission.country(self.defender_name),
|
|
||||||
from_cp=self.from_cp,
|
|
||||||
to_cp=self.to_cp,
|
|
||||||
theater=self.game.theater
|
|
||||||
)
|
|
||||||
|
|
||||||
self.initialize(mission=self.mission,
|
|
||||||
conflict=conflict)
|
|
||||||
|
|
||||||
def generate(self):
|
|
||||||
self.airgen.generate_cas_strikegroup(self.strikegroup, clients=self.attacker_clients, at=self.attackers_starting_position)
|
|
||||||
|
|
||||||
if self.interceptors:
|
|
||||||
self.airgen.generate_defense(self.interceptors, clients=self.defender_clients, at=self.defenders_starting_position)
|
|
||||||
|
|
||||||
self.armorgen.generate({}, self.target)
|
|
||||||
super(AntiAAStrikeOperation, self).generate()
|
|
||||||
@ -1,21 +1,14 @@
|
|||||||
from game import db
|
from game.db import assigned_units_split
|
||||||
|
|
||||||
from gen.conflictgen import Conflict
|
|
||||||
from gen.armor import *
|
|
||||||
from gen.aircraft import *
|
|
||||||
from gen.aaa import *
|
|
||||||
from gen.shipgen import *
|
|
||||||
from gen.triggergen import *
|
from gen.triggergen import *
|
||||||
from gen.airsupportgen import *
|
|
||||||
from gen.visualgen import *
|
|
||||||
|
|
||||||
from .operation import Operation
|
from .operation import *
|
||||||
|
|
||||||
|
|
||||||
class BaseAttackOperation(Operation):
|
class BaseAttackOperation(Operation):
|
||||||
cas = None # type: db.PlaneDict
|
cas = None # type: db.AssignedUnitsDict
|
||||||
escort = None # type: db.PlaneDict
|
escort = None # type: db.AssignedUnitsDict
|
||||||
intercept = None # type: db.PlaneDict
|
intercept = None # type: db.AssignedUnitsDict
|
||||||
attack = None # type: db.ArmorDict
|
attack = None # type: db.ArmorDict
|
||||||
defense = None # type: db.ArmorDict
|
defense = None # type: db.ArmorDict
|
||||||
aa = None # type: db.AirDefenseDict
|
aa = None # type: db.AirDefenseDict
|
||||||
@ -23,10 +16,10 @@ class BaseAttackOperation(Operation):
|
|||||||
trigger_radius = TRIGGER_RADIUS_SMALL
|
trigger_radius = TRIGGER_RADIUS_SMALL
|
||||||
|
|
||||||
def setup(self,
|
def setup(self,
|
||||||
cas: db.PlaneDict,
|
cas: db.AssignedUnitsDict,
|
||||||
escort: db.PlaneDict,
|
escort: db.AssignedUnitsDict,
|
||||||
attack: db.ArmorDict,
|
attack: db.AssignedUnitsDict,
|
||||||
intercept: db.PlaneDict,
|
intercept: db.AssignedUnitsDict,
|
||||||
defense: db.ArmorDict,
|
defense: db.ArmorDict,
|
||||||
aa: db.AirDefenseDict):
|
aa: db.AirDefenseDict):
|
||||||
self.cas = cas
|
self.cas = cas
|
||||||
@ -44,24 +37,33 @@ class BaseAttackOperation(Operation):
|
|||||||
self.attackers_starting_position = None
|
self.attackers_starting_position = None
|
||||||
|
|
||||||
conflict = Conflict.capture_conflict(
|
conflict = Conflict.capture_conflict(
|
||||||
attacker=self.mission.country(self.attacker_name),
|
attacker=self.current_mission.country(self.attacker_name),
|
||||||
defender=self.mission.country(self.defender_name),
|
defender=self.current_mission.country(self.defender_name),
|
||||||
from_cp=self.from_cp,
|
from_cp=self.from_cp,
|
||||||
to_cp=self.to_cp,
|
to_cp=self.to_cp,
|
||||||
theater=self.game.theater
|
theater=self.game.theater
|
||||||
)
|
)
|
||||||
self.initialize(mission=self.mission,
|
self.initialize(mission=self.current_mission,
|
||||||
conflict=conflict)
|
conflict=conflict)
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
self.armorgen.generate(self.attack, self.defense)
|
self.armorgen.generate(self.attack, self.defense)
|
||||||
self.aagen.generate(self.aa)
|
self.aagen.generate(self.aa)
|
||||||
|
|
||||||
self.airgen.generate_defense(self.intercept, clients=self.defender_clients, at=self.defenders_starting_position)
|
self.airgen.generate_defense(*assigned_units_split(self.intercept), at=self.defenders_starting_position)
|
||||||
|
|
||||||
self.airgen.generate_cas_strikegroup(self.cas, clients=self.attacker_clients, at=self.attackers_starting_position)
|
self.airgen.generate_cas_strikegroup(*assigned_units_split(self.cas), at=self.attackers_starting_position)
|
||||||
self.airgen.generate_attackers_escort(self.escort, clients=self.attacker_clients, at=self.attackers_starting_position)
|
self.airgen.generate_attackers_escort(*assigned_units_split(self.escort), at=self.attackers_starting_position)
|
||||||
|
|
||||||
self.visualgen.generate_target_smokes(self.to_cp)
|
self.visualgen.generate_target_smokes(self.to_cp)
|
||||||
|
|
||||||
|
self.briefinggen.title = "Base attack"
|
||||||
|
self.briefinggen.description = "The goal of an attacker is to lower defender presence by destroying their armor and aircraft. Base will be considered captured if attackers on the ground overrun the defenders. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu."
|
||||||
|
|
||||||
|
if self.game.player == self.attacker_name:
|
||||||
|
self.briefinggen.append_waypoint("TARGET")
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
super(BaseAttackOperation, self).generate()
|
super(BaseAttackOperation, self).generate()
|
||||||
|
|
||||||
|
|||||||
@ -1,32 +1,20 @@
|
|||||||
from itertools import zip_longest
|
from game.db import assigned_units_split
|
||||||
|
|
||||||
from dcs.terrain import Terrain
|
from .operation import *
|
||||||
|
|
||||||
from game import db
|
|
||||||
from gen.armor import *
|
|
||||||
from gen.aircraft import *
|
|
||||||
from gen.aaa import *
|
|
||||||
from gen.shipgen import *
|
|
||||||
from gen.triggergen import *
|
|
||||||
from gen.airsupportgen import *
|
|
||||||
from gen.visualgen import *
|
|
||||||
from gen.conflictgen import Conflict
|
|
||||||
|
|
||||||
from .operation import Operation
|
|
||||||
|
|
||||||
|
|
||||||
MAX_DISTANCE_BETWEEN_GROUPS = 12000
|
MAX_DISTANCE_BETWEEN_GROUPS = 12000
|
||||||
|
|
||||||
|
|
||||||
class FrontlineAttackOperation(Operation):
|
class FrontlineAttackOperation(Operation):
|
||||||
|
strikegroup = None # type: db.AssignedUnitsDict
|
||||||
attackers = None # type: db.ArmorDict
|
attackers = None # type: db.ArmorDict
|
||||||
strikegroup = None # type: db.PlaneDict
|
|
||||||
target = None # type: db.ArmorDict
|
target = None # type: db.ArmorDict
|
||||||
|
|
||||||
def setup(self,
|
def setup(self,
|
||||||
target: db.ArmorDict,
|
target: db.ArmorDict,
|
||||||
attackers: db.ArmorDict,
|
attackers: db.ArmorDict,
|
||||||
strikegroup: db.PlaneDict):
|
strikegroup: db.AssignedUnitsDict):
|
||||||
self.strikegroup = strikegroup
|
self.strikegroup = strikegroup
|
||||||
self.target = target
|
self.target = target
|
||||||
self.attackers = attackers
|
self.attackers = attackers
|
||||||
@ -38,17 +26,33 @@ class FrontlineAttackOperation(Operation):
|
|||||||
self.defenders_starting_position = None
|
self.defenders_starting_position = None
|
||||||
|
|
||||||
conflict = Conflict.frontline_cas_conflict(
|
conflict = Conflict.frontline_cas_conflict(
|
||||||
attacker=self.mission.country(self.attacker_name),
|
attacker=self.current_mission.country(self.attacker_name),
|
||||||
defender=self.mission.country(self.defender_name),
|
defender=self.current_mission.country(self.defender_name),
|
||||||
from_cp=self.from_cp,
|
from_cp=self.from_cp,
|
||||||
to_cp=self.to_cp,
|
to_cp=self.to_cp,
|
||||||
theater=self.game.theater
|
theater=self.game.theater
|
||||||
)
|
)
|
||||||
|
|
||||||
self.initialize(mission=self.mission,
|
self.initialize(mission=self.current_mission,
|
||||||
conflict=conflict)
|
conflict=conflict)
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
self.armorgen.generate_vec(self.attackers, self.target)
|
self.armorgen.generate_vec(self.attackers, self.target)
|
||||||
self.airgen.generate_cas_strikegroup(self.strikegroup, clients=self.attacker_clients, at=self.attackers_starting_position)
|
|
||||||
|
planes_flights = {k: v for k, v in self.strikegroup.items() if k in plane_map.values()}
|
||||||
|
self.airgen.generate_cas_strikegroup(*assigned_units_split(planes_flights), at=self.attackers_starting_position)
|
||||||
|
|
||||||
|
heli_flights = {k: v for k, v in self.strikegroup.items() if k in helicopters.helicopter_map.values()}
|
||||||
|
if heli_flights:
|
||||||
|
self.briefinggen.append_frequency("FARP + Heli flights", "127.5 MHz AM")
|
||||||
|
for farp, dict in zip(self.groundobjectgen.generate_farps(sum([x[0] for x in heli_flights.values()])),
|
||||||
|
db.assignedunits_split_to_count(heli_flights, self.groundobjectgen.FARP_CAPACITY)):
|
||||||
|
self.airgen.generate_cas_strikegroup(*assigned_units_split(dict),
|
||||||
|
at=farp,
|
||||||
|
escort=len(planes_flights) == 0)
|
||||||
|
|
||||||
|
self.briefinggen.title = "Frontline CAS"
|
||||||
|
self.briefinggen.description = "Provide CAS for the ground forces attacking enemy lines. Operation will be considered successful if total number of enemy units will be lower than your own by a factor of 1.5 (i.e. with 12 units from both sides, enemy forces need to be reduced to at least 8), meaning that you (and, probably, your wingmans) should concentrate on destroying the enemy units. Target base strength will be lowered as a result. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu."
|
||||||
|
self.briefinggen.append_waypoint("CAS AREA IP")
|
||||||
|
self.briefinggen.append_waypoint("CAS AREA EGRESS")
|
||||||
super(FrontlineAttackOperation, self).generate()
|
super(FrontlineAttackOperation, self).generate()
|
||||||
|
|||||||
@ -1,32 +1,25 @@
|
|||||||
from itertools import zip_longest
|
from game.db import assigned_units_split
|
||||||
|
|
||||||
from dcs.terrain import Terrain
|
from .operation import *
|
||||||
|
|
||||||
from game import db
|
|
||||||
from gen.armor import *
|
|
||||||
from gen.aircraft import *
|
|
||||||
from gen.aaa import *
|
|
||||||
from gen.shipgen import *
|
|
||||||
from gen.triggergen import *
|
|
||||||
from gen.airsupportgen import *
|
|
||||||
from gen.visualgen import *
|
|
||||||
from gen.conflictgen import Conflict
|
|
||||||
|
|
||||||
from .operation import Operation
|
|
||||||
|
|
||||||
|
|
||||||
MAX_DISTANCE_BETWEEN_GROUPS = 12000
|
MAX_DISTANCE_BETWEEN_GROUPS = 12000
|
||||||
|
|
||||||
|
|
||||||
class FrontlinePatrolOperation(Operation):
|
class FrontlinePatrolOperation(Operation):
|
||||||
cas = None # type: db.PlaneDict
|
cas = None # type: db.AssignedUnitsDict
|
||||||
escort = None # type: db.PlaneDict
|
escort = None # type: db.AssignedUnitsDict
|
||||||
interceptors = None # type: db.PlaneDict
|
interceptors = None # type: db.AssignedUnitsDict
|
||||||
|
|
||||||
armor_attackers = None # type: db.ArmorDict
|
armor_attackers = None # type: db.ArmorDict
|
||||||
armor_defenders = None # type: db.ArmorDict
|
armor_defenders = None # type: db.ArmorDict
|
||||||
|
|
||||||
def setup(self, cas: db.PlaneDict, escort: db.PlaneDict, interceptors: db.PlaneDict, armor_attackers: db.ArmorDict, armor_defenders: db.ArmorDict):
|
def setup(self,
|
||||||
|
cas: db.AssignedUnitsDict,
|
||||||
|
escort: db.AssignedUnitsDict,
|
||||||
|
interceptors: db.AssignedUnitsDict,
|
||||||
|
armor_attackers: db.ArmorDict,
|
||||||
|
armor_defenders: db.ArmorDict):
|
||||||
self.cas = cas
|
self.cas = cas
|
||||||
self.escort = escort
|
self.escort = escort
|
||||||
self.interceptors = interceptors
|
self.interceptors = interceptors
|
||||||
@ -39,20 +32,25 @@ class FrontlinePatrolOperation(Operation):
|
|||||||
self.defenders_starting_position = None
|
self.defenders_starting_position = None
|
||||||
|
|
||||||
conflict = Conflict.frontline_cap_conflict(
|
conflict = Conflict.frontline_cap_conflict(
|
||||||
attacker=self.mission.country(self.attacker_name),
|
attacker=self.current_mission.country(self.attacker_name),
|
||||||
defender=self.mission.country(self.defender_name),
|
defender=self.current_mission.country(self.defender_name),
|
||||||
from_cp=self.from_cp,
|
from_cp=self.from_cp,
|
||||||
to_cp=self.to_cp,
|
to_cp=self.to_cp,
|
||||||
theater=self.game.theater
|
theater=self.game.theater
|
||||||
)
|
)
|
||||||
|
|
||||||
self.initialize(mission=self.mission,
|
self.initialize(mission=self.current_mission,
|
||||||
conflict=conflict)
|
conflict=conflict)
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
self.airgen.generate_defenders_cas(self.cas, {}, self.defenders_starting_position)
|
self.airgen.generate_defenders_cas(*assigned_units_split(self.cas), at=self.defenders_starting_position)
|
||||||
self.airgen.generate_defenders_escort(self.escort, {}, self.defenders_starting_position)
|
self.airgen.generate_defenders_escort(*assigned_units_split(self.escort), at=self.defenders_starting_position)
|
||||||
self.airgen.generate_patrol(self.interceptors, self.attacker_clients, self.attackers_starting_position)
|
self.airgen.generate_migcap(*assigned_units_split(self.interceptors), at=self.attackers_starting_position)
|
||||||
|
|
||||||
self.armorgen.generate_vec(self.armor_attackers, self.armor_defenders)
|
self.armorgen.generate_vec(self.armor_attackers, self.armor_defenders)
|
||||||
|
|
||||||
|
self.briefinggen.title = "Frontline CAP"
|
||||||
|
self.briefinggen.description = "Providing CAP support for ground units attacking enemy lines. Enemy will scramble its CAS and your task is to intercept it. Operation will be considered successful if total number of friendly units will be lower than enemy by at least a factor of 0.8 (i.e. with 12 units from both sides, there should be at least 8 friendly units alive), lowering targets strength as a result."
|
||||||
|
self.briefinggen.append_waypoint("CAP AREA IP")
|
||||||
|
self.briefinggen.append_waypoint("CAP AREA EGRESS")
|
||||||
super(FrontlinePatrolOperation, self).generate()
|
super(FrontlinePatrolOperation, self).generate()
|
||||||
|
|||||||
@ -1,23 +1,13 @@
|
|||||||
from dcs.terrain import Terrain
|
from game.db import assigned_units_split
|
||||||
|
|
||||||
from game import db
|
from .operation import *
|
||||||
from gen.armor import *
|
|
||||||
from gen.aircraft import *
|
|
||||||
from gen.aaa import *
|
|
||||||
from gen.shipgen import *
|
|
||||||
from gen.triggergen import *
|
|
||||||
from gen.airsupportgen import *
|
|
||||||
from gen.visualgen import *
|
|
||||||
from gen.conflictgen import Conflict
|
|
||||||
|
|
||||||
from .operation import Operation
|
|
||||||
|
|
||||||
|
|
||||||
class InfantryTransportOperation(Operation):
|
class InfantryTransportOperation(Operation):
|
||||||
transport = None # type: db.HeliDict
|
transport = None # type: db.AssignedUnitsDict
|
||||||
aa = None # type: db.AirDefenseDict
|
aa = None # type: db.AirDefenseDict
|
||||||
|
|
||||||
def setup(self, transport: db.HeliDict, aa: db.AirDefenseDict):
|
def setup(self, transport: db.AssignedUnitsDict, aa: db.AirDefenseDict):
|
||||||
self.transport = transport
|
self.transport = transport
|
||||||
self.aa = aa
|
self.aa = aa
|
||||||
|
|
||||||
@ -25,22 +15,18 @@ class InfantryTransportOperation(Operation):
|
|||||||
super(InfantryTransportOperation, self).prepare(terrain, is_quick)
|
super(InfantryTransportOperation, self).prepare(terrain, is_quick)
|
||||||
|
|
||||||
conflict = Conflict.transport_conflict(
|
conflict = Conflict.transport_conflict(
|
||||||
attacker=self.mission.country(self.attacker_name),
|
attacker=self.current_mission.country(self.attacker_name),
|
||||||
defender=self.mission.country(self.defender_name),
|
defender=self.current_mission.country(self.defender_name),
|
||||||
from_cp=self.from_cp,
|
from_cp=self.from_cp,
|
||||||
to_cp=self.to_cp,
|
to_cp=self.to_cp,
|
||||||
theater=self.game.theater
|
theater=self.game.theater
|
||||||
)
|
)
|
||||||
|
|
||||||
self.initialize(mission=self.mission,
|
self.initialize(mission=self.current_mission,
|
||||||
conflict=conflict)
|
conflict=conflict)
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
self.airgen.generate_passenger_transport(
|
self.airgen.generate_passenger_transport(*assigned_units_split(self.transport), at=self.attackers_starting_position)
|
||||||
helis=self.transport,
|
|
||||||
clients=self.attacker_clients,
|
|
||||||
at=self.attackers_starting_position
|
|
||||||
)
|
|
||||||
|
|
||||||
self.armorgen.generate_passengers(count=6)
|
self.armorgen.generate_passengers(count=6)
|
||||||
self.aagen.generate_at_defenders_location(self.aa)
|
self.aagen.generate_at_defenders_location(self.aa)
|
||||||
@ -48,6 +34,10 @@ class InfantryTransportOperation(Operation):
|
|||||||
self.visualgen.generate_transportation_marker(self.conflict.ground_attackers_location)
|
self.visualgen.generate_transportation_marker(self.conflict.ground_attackers_location)
|
||||||
self.visualgen.generate_transportation_destination(self.conflict.position)
|
self.visualgen.generate_transportation_destination(self.conflict.position)
|
||||||
|
|
||||||
|
self.briefinggen.title = "Infantry transport"
|
||||||
|
self.briefinggen.description = "Helicopter operation to transport infantry troops from the base to the front line. Lowers target strength"
|
||||||
|
self.briefinggen.append_waypoint("DROP POINT")
|
||||||
|
|
||||||
# TODO: horrible, horrible hack
|
# TODO: horrible, horrible hack
|
||||||
# this will disable vehicle activation triggers,
|
# this will disable vehicle activation triggers,
|
||||||
# which aren't needed on this type of missions
|
# which aren't needed on this type of missions
|
||||||
|
|||||||
@ -1,25 +1,15 @@
|
|||||||
from dcs.terrain import Terrain
|
from game.db import assigned_units_split
|
||||||
|
|
||||||
from game import db
|
from .operation import *
|
||||||
from gen.armor import *
|
|
||||||
from gen.aircraft import *
|
|
||||||
from gen.aaa import *
|
|
||||||
from gen.shipgen import *
|
|
||||||
from gen.triggergen import *
|
|
||||||
from gen.airsupportgen import *
|
|
||||||
from gen.visualgen import *
|
|
||||||
from gen.conflictgen import Conflict
|
|
||||||
|
|
||||||
from .operation import Operation
|
|
||||||
|
|
||||||
|
|
||||||
class InsurgentAttackOperation(Operation):
|
class InsurgentAttackOperation(Operation):
|
||||||
strikegroup = None # type: db.PlaneDict
|
strikegroup = None # type: db.AssignedUnitsDict
|
||||||
target = None # type: db.ArmorDict
|
target = None # type: db.ArmorDict
|
||||||
|
|
||||||
def setup(self,
|
def setup(self,
|
||||||
target: db.ArmorDict,
|
target: db.ArmorDict,
|
||||||
strikegroup: db.PlaneDict):
|
strikegroup: db.AssignedUnitsDict):
|
||||||
self.strikegroup = strikegroup
|
self.strikegroup = strikegroup
|
||||||
self.target = target
|
self.target = target
|
||||||
|
|
||||||
@ -27,18 +17,22 @@ class InsurgentAttackOperation(Operation):
|
|||||||
super(InsurgentAttackOperation, self).prepare(terrain, is_quick)
|
super(InsurgentAttackOperation, self).prepare(terrain, is_quick)
|
||||||
|
|
||||||
conflict = Conflict.ground_attack_conflict(
|
conflict = Conflict.ground_attack_conflict(
|
||||||
attacker=self.mission.country(self.attacker_name),
|
attacker=self.current_mission.country(self.attacker_name),
|
||||||
defender=self.mission.country(self.defender_name),
|
defender=self.current_mission.country(self.defender_name),
|
||||||
from_cp=self.from_cp,
|
from_cp=self.from_cp,
|
||||||
to_cp=self.to_cp,
|
to_cp=self.to_cp,
|
||||||
theater=self.game.theater
|
theater=self.game.theater
|
||||||
)
|
)
|
||||||
|
|
||||||
self.initialize(mission=self.mission,
|
self.initialize(mission=self.current_mission,
|
||||||
conflict=conflict)
|
conflict=conflict)
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
self.airgen.generate_defense(self.strikegroup, self.defender_clients, self.defenders_starting_position)
|
self.airgen.generate_defenders_cas(*assigned_units_split(self.strikegroup), at=self.defenders_starting_position)
|
||||||
self.armorgen.generate(self.target, {})
|
self.armorgen.generate(self.target, {})
|
||||||
|
|
||||||
|
self.briefinggen.title = "Destroy insurgents"
|
||||||
|
self.briefinggen.description = "Destroy vehicles of insurgents in close proximity of the friendly base. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu."
|
||||||
|
self.briefinggen.append_waypoint("TARGET")
|
||||||
|
|
||||||
super(InsurgentAttackOperation, self).generate()
|
super(InsurgentAttackOperation, self).generate()
|
||||||
|
|||||||
@ -1,22 +1,21 @@
|
|||||||
from dcs.terrain import Terrain
|
from game.db import assigned_units_split
|
||||||
|
|
||||||
from gen import *
|
from .operation import *
|
||||||
from .operation import Operation
|
|
||||||
|
|
||||||
|
|
||||||
class InterceptOperation(Operation):
|
class InterceptOperation(Operation):
|
||||||
escort = None # type: db.PlaneDict
|
escort = None # type: db.AssignedUnitsDict
|
||||||
transport = None # type: db.PlaneDict
|
transport = None # type: db.PlaneDict
|
||||||
interceptors = None # type: db.PlaneDict
|
interceptors = None # type: db.AssignedUnitsDict
|
||||||
airdefense = None # type: db.AirDefenseDict
|
airdefense = None # type: db.AirDefenseDict
|
||||||
|
|
||||||
trigger_radius = TRIGGER_RADIUS_LARGE
|
trigger_radius = TRIGGER_RADIUS_LARGE
|
||||||
|
|
||||||
def setup(self,
|
def setup(self,
|
||||||
escort: db.PlaneDict,
|
escort: db.AssignedUnitsDict,
|
||||||
transport: db.PlaneDict,
|
transport: db.PlaneDict,
|
||||||
airdefense: db.AirDefenseDict,
|
airdefense: db.AirDefenseDict,
|
||||||
interceptors: db.PlaneDict):
|
interceptors: db.AssignedUnitsDict):
|
||||||
self.escort = escort
|
self.escort = escort
|
||||||
self.transport = transport
|
self.transport = transport
|
||||||
self.airdefense = airdefense
|
self.airdefense = airdefense
|
||||||
@ -29,31 +28,33 @@ class InterceptOperation(Operation):
|
|||||||
self.attackers_starting_position = None
|
self.attackers_starting_position = None
|
||||||
|
|
||||||
conflict = Conflict.intercept_conflict(
|
conflict = Conflict.intercept_conflict(
|
||||||
attacker=self.mission.country(self.attacker_name),
|
attacker=self.current_mission.country(self.attacker_name),
|
||||||
defender=self.mission.country(self.defender_name),
|
defender=self.current_mission.country(self.defender_name),
|
||||||
from_cp=self.from_cp,
|
from_cp=self.from_cp,
|
||||||
to_cp=self.to_cp,
|
to_cp=self.to_cp,
|
||||||
theater=self.game.theater
|
theater=self.game.theater
|
||||||
)
|
)
|
||||||
|
|
||||||
self.initialize(mission=self.mission,
|
self.initialize(mission=self.current_mission,
|
||||||
conflict=conflict)
|
conflict=conflict)
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
for global_cp in self.game.theater.controlpoints:
|
self.prepare_carriers(db.unitdict_from(self.interceptors))
|
||||||
if not global_cp.is_global:
|
|
||||||
continue
|
|
||||||
|
|
||||||
ship = self.shipgen.generate_carrier(type=db.find_unittype(Carriage, self.game.player)[0],
|
|
||||||
country=self.game.player,
|
|
||||||
at=global_cp.at)
|
|
||||||
|
|
||||||
if global_cp == self.from_cp and not self.is_quick:
|
|
||||||
self.attackers_starting_position = ship
|
|
||||||
|
|
||||||
self.airgen.generate_transport(self.transport, self.to_cp.at)
|
self.airgen.generate_transport(self.transport, self.to_cp.at)
|
||||||
self.airgen.generate_defenders_escort(self.escort, clients=self.defender_clients)
|
self.airgen.generate_defenders_escort(*assigned_units_split(self.escort), at=self.defenders_starting_position)
|
||||||
|
|
||||||
|
self.airgen.generate_interception(*assigned_units_split(self.interceptors), at=self.attackers_starting_position)
|
||||||
|
|
||||||
|
self.briefinggen.title = "Air Intercept"
|
||||||
|
|
||||||
|
if self.game.player == self.attacker_name:
|
||||||
|
self.briefinggen.description = "Intercept enemy supply transport aircraft. Escort will also be present if there are available planes on the base. Operation will be considered successful if most of the targets are destroyed, lowering targets strength as a result"
|
||||||
|
self.briefinggen.append_waypoint("TARGET")
|
||||||
|
for unit_type, count in self.transport.items():
|
||||||
|
self.briefinggen.append_target("{} ({})".format(db.unit_type_name(unit_type), count))
|
||||||
|
else:
|
||||||
|
self.briefinggen.description = "Escort friendly supply transport aircraft. Operation will be considered failed if most of the targets are destroyed, lowering CP strength as a result"
|
||||||
|
|
||||||
self.airgen.generate_interception(self.interceptors, clients=self.attacker_clients, at=self.attackers_starting_position)
|
|
||||||
super(InterceptOperation, self).generate()
|
super(InterceptOperation, self).generate()
|
||||||
|
|
||||||
|
|||||||
@ -1,18 +1,17 @@
|
|||||||
from dcs.terrain import Terrain
|
from game.db import assigned_units_split
|
||||||
|
|
||||||
from gen import *
|
from .operation import *
|
||||||
from .operation import Operation
|
|
||||||
|
|
||||||
|
|
||||||
class NavalInterceptionOperation(Operation):
|
class NavalInterceptionOperation(Operation):
|
||||||
strikegroup = None # type: db.PlaneDict
|
strikegroup = None # type: db.AssignedUnitsDict
|
||||||
interceptors = None # type: db.PlaneDict
|
interceptors = None # type: db.AssignedUnitsDict
|
||||||
targets = None # type: db.ShipDict
|
targets = None # type: db.ShipDict
|
||||||
trigger_radius = TRIGGER_RADIUS_LARGE
|
trigger_radius = TRIGGER_RADIUS_LARGE
|
||||||
|
|
||||||
def setup(self,
|
def setup(self,
|
||||||
strikegroup: db.PlaneDict,
|
strikegroup: db.AssignedUnitsDict,
|
||||||
interceptors: db.PlaneDict,
|
interceptors: db.AssignedUnitsDict,
|
||||||
targets: db.ShipDict):
|
targets: db.ShipDict):
|
||||||
self.strikegroup = strikegroup
|
self.strikegroup = strikegroup
|
||||||
self.interceptors = interceptors
|
self.interceptors = interceptors
|
||||||
@ -24,30 +23,40 @@ class NavalInterceptionOperation(Operation):
|
|||||||
self.attackers_starting_position = None
|
self.attackers_starting_position = None
|
||||||
|
|
||||||
conflict = Conflict.naval_intercept_conflict(
|
conflict = Conflict.naval_intercept_conflict(
|
||||||
attacker=self.mission.country(self.attacker_name),
|
attacker=self.current_mission.country(self.attacker_name),
|
||||||
defender=self.mission.country(self.defender_name),
|
defender=self.current_mission.country(self.defender_name),
|
||||||
from_cp=self.from_cp,
|
from_cp=self.from_cp,
|
||||||
to_cp=self.to_cp,
|
to_cp=self.to_cp,
|
||||||
theater=self.game.theater
|
theater=self.game.theater
|
||||||
)
|
)
|
||||||
|
|
||||||
self.initialize(self.mission, conflict)
|
self.initialize(self.current_mission, conflict)
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
super(NavalInterceptionOperation, self).generate()
|
self.prepare_carriers(db.unitdict_from(self.strikegroup))
|
||||||
|
|
||||||
target_groups = self.shipgen.generate_cargo(units=self.targets)
|
target_groups = self.shipgen.generate_cargo(units=self.targets)
|
||||||
|
|
||||||
self.airgen.generate_ship_strikegroup(
|
self.airgen.generate_ship_strikegroup(
|
||||||
attackers=self.strikegroup,
|
*assigned_units_split(self.strikegroup),
|
||||||
clients=self.attacker_clients,
|
|
||||||
target_groups=target_groups,
|
target_groups=target_groups,
|
||||||
at=self.attackers_starting_position
|
at=self.attackers_starting_position
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.interceptors:
|
if self.interceptors:
|
||||||
self.airgen.generate_defense(
|
self.airgen.generate_defense(
|
||||||
defenders=self.interceptors,
|
*assigned_units_split(self.interceptors),
|
||||||
clients=self.defender_clients,
|
|
||||||
at=self.defenders_starting_position
|
at=self.defenders_starting_position
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.briefinggen.title = "Naval Intercept"
|
||||||
|
if self.game.player == self.attacker_name:
|
||||||
|
self.briefinggen.description = "Destroy supply transport ships. Lowers target strength. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu."
|
||||||
|
for unit_type, count in self.targets.items():
|
||||||
|
self.briefinggen.append_target("{} ({})".format(db.unit_type_name(unit_type), count))
|
||||||
|
else:
|
||||||
|
self.briefinggen.description = "Protect supply transport ships."
|
||||||
|
self.briefinggen.append_waypoint("TARGET")
|
||||||
|
|
||||||
|
super(NavalInterceptionOperation, self).generate()
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,19 @@
|
|||||||
from dcs.terrain import Terrain
|
|
||||||
from dcs.lua.parse import loads
|
from dcs.lua.parse import loads
|
||||||
|
|
||||||
from userdata.debriefing import *
|
from userdata.debriefing import *
|
||||||
|
|
||||||
from theater import *
|
|
||||||
from gen import *
|
from gen import *
|
||||||
|
|
||||||
|
TANKER_CALLSIGNS = ["Texaco", "Arco", "Shell"]
|
||||||
|
|
||||||
|
|
||||||
class Operation:
|
class Operation:
|
||||||
attackers_starting_position = None # type: db.StartingPosition
|
attackers_starting_position = None # type: db.StartingPosition
|
||||||
defenders_starting_position = None # type: db.StartingPosition
|
defenders_starting_position = None # type: db.StartingPosition
|
||||||
mission = None # type: dcs.Mission
|
|
||||||
|
current_mission = None # type: dcs.Mission
|
||||||
|
regular_mission = None # type: dcs.Mission
|
||||||
|
quick_mission = None # type: dcs.Mission
|
||||||
conflict = None # type: Conflict
|
conflict = None # type: Conflict
|
||||||
armorgen = None # type: ArmorConflictGenerator
|
armorgen = None # type: ArmorConflictGenerator
|
||||||
airgen = None # type: AircraftConflictGenerator
|
airgen = None # type: AircraftConflictGenerator
|
||||||
@ -18,44 +21,50 @@ class Operation:
|
|||||||
extra_aagen = None # type: ExtraAAConflictGenerator
|
extra_aagen = None # type: ExtraAAConflictGenerator
|
||||||
shipgen = None # type: ShipGenerator
|
shipgen = None # type: ShipGenerator
|
||||||
triggersgen = None # type: TriggersGenerator
|
triggersgen = None # type: TriggersGenerator
|
||||||
awacsgen = None # type: AirSupportConflictGenerator
|
airsupportgen = None # type: AirSupportConflictGenerator
|
||||||
visualgen = None # type: VisualGenerator
|
visualgen = None # type: VisualGenerator
|
||||||
envgen = None # type: EnvironmentGenerator
|
envgen = None # type: EnvironmentGenerator
|
||||||
|
groundobjectgen = None # type: GroundObjectsGenerator
|
||||||
|
briefinggen = None # type: BriefingGenerator
|
||||||
|
|
||||||
environment_settings = None
|
environment_settings = None
|
||||||
trigger_radius = TRIGGER_RADIUS_MEDIUM
|
trigger_radius = TRIGGER_RADIUS_MEDIUM
|
||||||
is_quick = None
|
is_quick = None
|
||||||
is_awacs_enabled = False
|
is_awacs_enabled = False
|
||||||
|
ca_slots = 0
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
game,
|
game,
|
||||||
attacker_name: str,
|
attacker_name: str,
|
||||||
defender_name: str,
|
defender_name: str,
|
||||||
attacker_clients: db.PlaneDict,
|
|
||||||
defender_clients: db.PlaneDict,
|
|
||||||
from_cp: ControlPoint,
|
from_cp: ControlPoint,
|
||||||
to_cp: ControlPoint = None):
|
to_cp: ControlPoint = None):
|
||||||
self.game = game
|
self.game = game
|
||||||
self.attacker_name = attacker_name
|
self.attacker_name = attacker_name
|
||||||
self.defender_name = defender_name
|
self.defender_name = defender_name
|
||||||
self.attacker_clients = attacker_clients
|
|
||||||
self.defender_clients = defender_clients
|
|
||||||
self.from_cp = from_cp
|
self.from_cp = from_cp
|
||||||
self.to_cp = to_cp
|
self.to_cp = to_cp
|
||||||
self.is_quick = False
|
self.is_quick = False
|
||||||
|
|
||||||
def initialize(self, mission: Mission, conflict: Conflict):
|
def units_of(self, country_name: str) -> typing.Collection[UnitType]:
|
||||||
self.mission = mission
|
return []
|
||||||
self.conflict = conflict
|
|
||||||
|
|
||||||
|
def is_successfull(self, debriefing: Debriefing) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def initialize(self, mission: Mission, conflict: Conflict):
|
||||||
|
self.current_mission = mission
|
||||||
|
self.conflict = conflict
|
||||||
self.armorgen = ArmorConflictGenerator(mission, conflict)
|
self.armorgen = ArmorConflictGenerator(mission, conflict)
|
||||||
self.airgen = AircraftConflictGenerator(mission, conflict, self.game.settings)
|
self.airgen = AircraftConflictGenerator(mission, conflict, self.game.settings)
|
||||||
self.aagen = AAConflictGenerator(mission, conflict)
|
self.aagen = AAConflictGenerator(mission, conflict)
|
||||||
self.shipgen = ShipGenerator(mission, conflict)
|
self.shipgen = ShipGenerator(mission, conflict)
|
||||||
self.awacsgen = AirSupportConflictGenerator(mission, conflict, self.game)
|
self.airsupportgen = AirSupportConflictGenerator(mission, conflict, self.game)
|
||||||
self.triggersgen = TriggersGenerator(mission, conflict, self.game)
|
self.triggersgen = TriggersGenerator(mission, conflict, self.game)
|
||||||
self.visualgen = VisualGenerator(mission, conflict, self.game)
|
self.visualgen = VisualGenerator(mission, conflict, self.game)
|
||||||
self.envgen = EnviromentGenerator(mission, conflict, self.game)
|
self.envgen = EnviromentGenerator(mission, conflict, self.game)
|
||||||
|
self.groundobjectgen = GroundObjectsGenerator(mission, conflict, self.game)
|
||||||
|
self.briefinggen = BriefingGenerator(mission, conflict, self.game)
|
||||||
|
|
||||||
player_name = self.from_cp.captured and self.attacker_name or self.defender_name
|
player_name = self.from_cp.captured and self.attacker_name or self.defender_name
|
||||||
enemy_name = self.from_cp.captured and self.defender_name or self.attacker_name
|
enemy_name = self.from_cp.captured and self.defender_name or self.attacker_name
|
||||||
@ -65,8 +74,13 @@ class Operation:
|
|||||||
with open("resources/default_options.lua", "r") as f:
|
with open("resources/default_options.lua", "r") as f:
|
||||||
options_dict = loads(f.read())["options"]
|
options_dict = loads(f.read())["options"]
|
||||||
|
|
||||||
self.mission = dcs.Mission(terrain)
|
self.current_mission = dcs.Mission(terrain)
|
||||||
self.mission.options.load_from_dict(options_dict)
|
if is_quick:
|
||||||
|
self.quick_mission = self.current_mission
|
||||||
|
else:
|
||||||
|
self.regular_mission = self.current_mission
|
||||||
|
|
||||||
|
self.current_mission.options.load_from_dict(options_dict)
|
||||||
self.is_quick = is_quick
|
self.is_quick = is_quick
|
||||||
|
|
||||||
if is_quick:
|
if is_quick:
|
||||||
@ -76,12 +90,41 @@ class Operation:
|
|||||||
self.attackers_starting_position = self.from_cp.at
|
self.attackers_starting_position = self.from_cp.at
|
||||||
self.defenders_starting_position = self.to_cp.at
|
self.defenders_starting_position = self.to_cp.at
|
||||||
|
|
||||||
|
def prepare_carriers(self, for_units: db.UnitsDict):
|
||||||
|
for global_cp in self.game.theater.controlpoints:
|
||||||
|
if not global_cp.is_global:
|
||||||
|
continue
|
||||||
|
|
||||||
|
ship = self.shipgen.generate_carrier(for_units=[t for t, c in for_units.items() if c > 0],
|
||||||
|
country=self.game.player,
|
||||||
|
at=global_cp.at)
|
||||||
|
|
||||||
|
if global_cp == self.from_cp and not self.is_quick:
|
||||||
|
self.attackers_starting_position = ship
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
self.visualgen.generate()
|
self.visualgen.generate()
|
||||||
self.awacsgen.generate(self.is_awacs_enabled)
|
|
||||||
|
|
||||||
|
# air support
|
||||||
|
self.airsupportgen.generate(self.is_awacs_enabled)
|
||||||
|
for i, tanker_type in enumerate(self.airsupportgen.generated_tankers):
|
||||||
|
self.briefinggen.append_frequency("Tanker {} ({})".format(TANKER_CALLSIGNS[i], tanker_type), "{}X/{} MHz AM".format(97+i, 130+i))
|
||||||
|
|
||||||
|
if self.is_awacs_enabled:
|
||||||
|
self.briefinggen.append_frequency("AWACS", "133 MHz AM")
|
||||||
|
|
||||||
|
# combined arms
|
||||||
|
self.current_mission.groundControl.pilot_can_control_vehicles = self.ca_slots > 0
|
||||||
|
if self.game.player in [country.name for country in self.current_mission.coalition["blue"].countries.values()]:
|
||||||
|
self.current_mission.groundControl.blue_tactical_commander = self.ca_slots
|
||||||
|
else:
|
||||||
|
self.current_mission.groundControl.red_tactical_commander = self.ca_slots
|
||||||
|
|
||||||
|
# ground infrastructure
|
||||||
|
self.groundobjectgen.generate()
|
||||||
self.extra_aagen.generate()
|
self.extra_aagen.generate()
|
||||||
|
|
||||||
|
# triggers
|
||||||
if self.game.is_player_attack(self.conflict.attackers_side):
|
if self.game.is_player_attack(self.conflict.attackers_side):
|
||||||
cp = self.conflict.from_cp
|
cp = self.conflict.from_cp
|
||||||
else:
|
else:
|
||||||
@ -92,13 +135,16 @@ class Operation:
|
|||||||
activation_trigger_radius=self.trigger_radius,
|
activation_trigger_radius=self.trigger_radius,
|
||||||
awacs_enabled=self.is_awacs_enabled)
|
awacs_enabled=self.is_awacs_enabled)
|
||||||
|
|
||||||
|
# env settings
|
||||||
if self.environment_settings is None:
|
if self.environment_settings is None:
|
||||||
self.environment_settings = self.envgen.generate()
|
self.environment_settings = self.envgen.generate()
|
||||||
else:
|
else:
|
||||||
self.envgen.load(self.environment_settings)
|
self.envgen.load(self.environment_settings)
|
||||||
|
|
||||||
def units_of(self, country_name: str) -> typing.Collection[UnitType]:
|
# main frequencies
|
||||||
return []
|
self.briefinggen.append_frequency("Flight", "251 MHz AM")
|
||||||
|
if self.conflict.from_cp.is_global or self.conflict.to_cp.is_global:
|
||||||
|
self.briefinggen.append_frequency("Carrier", "20X/ICLS CHAN1")
|
||||||
|
|
||||||
def is_successfull(self, debriefing: Debriefing) -> bool:
|
# briefing
|
||||||
return True
|
self.briefinggen.generate()
|
||||||
|
|||||||
79
game/operation/strike.py
Normal file
79
game/operation/strike.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
from game.db import assigned_units_split
|
||||||
|
|
||||||
|
from .operation import *
|
||||||
|
|
||||||
|
|
||||||
|
class StrikeOperation(Operation):
|
||||||
|
strikegroup = None # type: db.AssignedUnitsDict
|
||||||
|
escort = None # type: db.AssignedUnitsDict
|
||||||
|
interceptors = None # type: db.AssignedUnitsDict
|
||||||
|
|
||||||
|
trigger_radius = TRIGGER_RADIUS_ALL_MAP
|
||||||
|
|
||||||
|
def setup(self,
|
||||||
|
strikegroup: db.AssignedUnitsDict,
|
||||||
|
escort: db.AssignedUnitsDict,
|
||||||
|
interceptors: db.AssignedUnitsDict):
|
||||||
|
self.strikegroup = strikegroup
|
||||||
|
self.escort = escort
|
||||||
|
self.interceptors = interceptors
|
||||||
|
|
||||||
|
def prepare(self, terrain: Terrain, is_quick: bool):
|
||||||
|
super(StrikeOperation, self).prepare(terrain, is_quick)
|
||||||
|
|
||||||
|
self.defenders_starting_position = None
|
||||||
|
if self.game.player == self.defender_name:
|
||||||
|
self.attackers_starting_position = None
|
||||||
|
|
||||||
|
conflict = Conflict.strike_conflict(
|
||||||
|
attacker=self.current_mission.country(self.attacker_name),
|
||||||
|
defender=self.current_mission.country(self.defender_name),
|
||||||
|
from_cp=self.from_cp,
|
||||||
|
to_cp=self.to_cp,
|
||||||
|
theater=self.game.theater
|
||||||
|
)
|
||||||
|
|
||||||
|
self.initialize(mission=self.current_mission,
|
||||||
|
conflict=conflict)
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
self.prepare_carriers(db.unitdict_merge(db.unitdict_from(self.strikegroup), db.unitdict_from(self.escort)))
|
||||||
|
|
||||||
|
targets = [] # type: typing.List[typing.Tuple[str, str, Point]]
|
||||||
|
category_counters = {} # type: typing.Dict[str, int]
|
||||||
|
processed_groups = []
|
||||||
|
for object in self.to_cp.ground_objects:
|
||||||
|
if object.group_identifier in processed_groups:
|
||||||
|
continue
|
||||||
|
|
||||||
|
processed_groups.append(object.group_identifier)
|
||||||
|
category_counters[object.category] = category_counters.get(object.category, 0) + 1
|
||||||
|
markpoint_name = "{}{}".format(object.name_abbrev, category_counters[object.category])
|
||||||
|
targets.append((str(object), markpoint_name, object.position))
|
||||||
|
|
||||||
|
targets.sort(key=lambda x: self.from_cp.position.distance_to_point(x[2]))
|
||||||
|
|
||||||
|
for (name, markpoint_name, _) in targets:
|
||||||
|
self.briefinggen.append_waypoint("TARGET {} (TP {})".format(str(name), markpoint_name))
|
||||||
|
|
||||||
|
planes_flights = {k: v for k, v in self.strikegroup.items() if k in plane_map.values()}
|
||||||
|
self.airgen.generate_ground_attack_strikegroup(*assigned_units_split(planes_flights),
|
||||||
|
targets=[(mp, pos) for (n, mp, pos) in targets],
|
||||||
|
at=self.attackers_starting_position)
|
||||||
|
|
||||||
|
heli_flights = {k: v for k, v in self.strikegroup.items() if k in helicopters.helicopter_map.values()}
|
||||||
|
if heli_flights:
|
||||||
|
self.briefinggen.append_frequency("FARP", "127.5 MHz AM")
|
||||||
|
for farp, dict in zip(self.groundobjectgen.generate_farps(sum([x[0] for x in heli_flights.values()])),
|
||||||
|
db.assignedunits_split_to_count(heli_flights, self.groundobjectgen.FARP_CAPACITY)):
|
||||||
|
self.airgen.generate_ground_attack_strikegroup(*assigned_units_split(dict),
|
||||||
|
targets=[(mp, pos) for (n, mp, pos) in targets],
|
||||||
|
at=farp,
|
||||||
|
escort=len(planes_flights) == 0)
|
||||||
|
|
||||||
|
self.airgen.generate_attackers_escort(*assigned_units_split(self.escort), at=self.attackers_starting_position)
|
||||||
|
self.airgen.generate_barcap(*assigned_units_split(self.interceptors), at=self.defenders_starting_position)
|
||||||
|
|
||||||
|
self.briefinggen.title = "Strike"
|
||||||
|
self.briefinggen.description = "Destroy infrastructure assets and military supplies in the region. Each building destroyed will lower targets strength."
|
||||||
|
super(StrikeOperation, self).generate()
|
||||||
@ -2,8 +2,11 @@
|
|||||||
class Settings:
|
class Settings:
|
||||||
player_skill = "Good"
|
player_skill = "Good"
|
||||||
enemy_skill = "Average"
|
enemy_skill = "Average"
|
||||||
only_player_takeoff = False
|
enemy_vehicle_skill = "Average"
|
||||||
|
only_player_takeoff = True
|
||||||
night_disabled = False
|
night_disabled = False
|
||||||
|
|
||||||
multiplier = 1
|
multiplier = 1
|
||||||
sams = True
|
sams = True
|
||||||
cold_start = False
|
cold_start = False
|
||||||
|
version = None
|
||||||
|
|||||||
@ -7,6 +7,8 @@ from .shipgen import *
|
|||||||
from .visualgen import *
|
from .visualgen import *
|
||||||
from .triggergen import *
|
from .triggergen import *
|
||||||
from .environmentgen import *
|
from .environmentgen import *
|
||||||
|
from .groundobjectsgen import *
|
||||||
|
from .briefinggen import *
|
||||||
|
|
||||||
from . import naming
|
from . import naming
|
||||||
|
|
||||||
|
|||||||
14
gen/aaa.py
14
gen/aaa.py
@ -1,13 +1,11 @@
|
|||||||
from game import *
|
|
||||||
|
|
||||||
from theater.conflicttheater import ConflictTheater
|
|
||||||
from .conflictgen import *
|
from .conflictgen import *
|
||||||
from .naming import *
|
from .naming import *
|
||||||
|
|
||||||
from dcs.mission import *
|
from dcs.mission import *
|
||||||
|
|
||||||
DISTANCE_FACTOR = 0.5, 1
|
DISTANCE_FACTOR = 0.5, 1
|
||||||
EXTRA_AA_MIN_DISTANCE = 35000
|
EXTRA_AA_MIN_DISTANCE = 50000
|
||||||
|
EXTRA_AA_MAX_DISTANCE = 150000
|
||||||
EXTRA_AA_POSITION_FROM_CP = 550
|
EXTRA_AA_POSITION_FROM_CP = 550
|
||||||
|
|
||||||
|
|
||||||
@ -61,6 +59,12 @@ class ExtraAAConflictGenerator:
|
|||||||
if cp.position.distance_to_point(self.conflict.from_cp.position) < EXTRA_AA_MIN_DISTANCE:
|
if cp.position.distance_to_point(self.conflict.from_cp.position) < EXTRA_AA_MIN_DISTANCE:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if cp.position.distance_to_point(self.conflict.to_cp.position) < EXTRA_AA_MIN_DISTANCE:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if cp.position.distance_to_point(self.conflict.position) > EXTRA_AA_MAX_DISTANCE:
|
||||||
|
continue
|
||||||
|
|
||||||
country_name = cp.captured and self.player_name or self.enemy_name
|
country_name = cp.captured and self.player_name or self.enemy_name
|
||||||
position = cp.position.point_from_heading(0, EXTRA_AA_POSITION_FROM_CP)
|
position = cp.position.point_from_heading(0, EXTRA_AA_POSITION_FROM_CP)
|
||||||
|
|
||||||
@ -69,6 +73,6 @@ class ExtraAAConflictGenerator:
|
|||||||
name=namegen.next_basedefense_name(),
|
name=namegen.next_basedefense_name(),
|
||||||
_type=db.EXTRA_AA[country_name],
|
_type=db.EXTRA_AA[country_name],
|
||||||
position=position,
|
position=position,
|
||||||
group_size=2
|
group_size=1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
134
gen/aircraft.py
134
gen/aircraft.py
@ -24,6 +24,7 @@ WARM_START_AIRSPEED = 550
|
|||||||
|
|
||||||
INTERCEPTION_ALT = 3000
|
INTERCEPTION_ALT = 3000
|
||||||
INTERCEPTION_AIRSPEED = 1000
|
INTERCEPTION_AIRSPEED = 1000
|
||||||
|
BARCAP_RACETRACK_DISTANCE = 20000
|
||||||
|
|
||||||
ATTACK_CIRCLE_ALT = 5000
|
ATTACK_CIRCLE_ALT = 5000
|
||||||
ATTACK_CIRCLE_DURATION = 15
|
ATTACK_CIRCLE_DURATION = 15
|
||||||
@ -41,7 +42,7 @@ GROUP_VERTICAL_OFFSET = 300
|
|||||||
|
|
||||||
|
|
||||||
class AircraftConflictGenerator:
|
class AircraftConflictGenerator:
|
||||||
escort_targets = [] # type: typing.List[typing.Tuple[PlaneGroup, int]]
|
escort_targets = [] # type: typing.List[typing.Tuple[FlyingGroup, int]]
|
||||||
vertical_offset = None # type: int
|
vertical_offset = None # type: int
|
||||||
|
|
||||||
def __init__(self, mission: Mission, conflict: Conflict, settings: Settings):
|
def __init__(self, mission: Mission, conflict: Conflict, settings: Settings):
|
||||||
@ -76,18 +77,22 @@ class AircraftConflictGenerator:
|
|||||||
count -= group_size
|
count -= group_size
|
||||||
client_count -= client_size
|
client_count -= client_size
|
||||||
|
|
||||||
def _setup_group(self, group: FlyingGroup, for_task: Task, client_count: int):
|
def _setup_group(self, group: FlyingGroup, for_task: typing.Type[Task], client_count: int):
|
||||||
did_load_loadout = False
|
did_load_loadout = False
|
||||||
unit_type = group.units[0].unit_type
|
unit_type = group.units[0].unit_type
|
||||||
if unit_type in db.PLANE_PAYLOAD_OVERRIDES:
|
if unit_type in db.PLANE_PAYLOAD_OVERRIDES:
|
||||||
override_loadout = db.PLANE_PAYLOAD_OVERRIDES[unit_type]
|
override_loadout = db.PLANE_PAYLOAD_OVERRIDES[unit_type]
|
||||||
if type(override_loadout) == dict:
|
if type(override_loadout) == dict:
|
||||||
if for_task in db.PLANE_PAYLOAD_OVERRIDES[unit_type]:
|
if for_task in db.PLANE_PAYLOAD_OVERRIDES[unit_type]:
|
||||||
group.load_loadout(db.PLANE_PAYLOAD_OVERRIDES[unit_type][for_task])
|
payload_name = db.PLANE_PAYLOAD_OVERRIDES[unit_type][for_task]
|
||||||
|
group.load_loadout(payload_name)
|
||||||
did_load_loadout = True
|
did_load_loadout = True
|
||||||
|
logging.info("Loaded overridden payload for {} - {} for task {}".format(unit_type, payload_name, for_task))
|
||||||
elif "*" in db.PLANE_PAYLOAD_OVERRIDES[unit_type]:
|
elif "*" in db.PLANE_PAYLOAD_OVERRIDES[unit_type]:
|
||||||
group.load_loadout(db.PLANE_PAYLOAD_OVERRIDES[unit_type]["*"])
|
payload_name = db.PLANE_PAYLOAD_OVERRIDES[unit_type]["*"]
|
||||||
|
group.load_loadout(payload_name)
|
||||||
did_load_loadout = True
|
did_load_loadout = True
|
||||||
|
logging.info("Loaded overridden payload for {} - {} for task {}".format(unit_type, payload_name, for_task))
|
||||||
elif issubclass(override_loadout, MainTask):
|
elif issubclass(override_loadout, MainTask):
|
||||||
group.load_task_default_loadout(override_loadout)
|
group.load_task_default_loadout(override_loadout)
|
||||||
did_load_loadout = True
|
did_load_loadout = True
|
||||||
@ -108,6 +113,12 @@ class AircraftConflictGenerator:
|
|||||||
|
|
||||||
group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
|
group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
|
||||||
|
|
||||||
|
if unit_type in helicopters.helicopter_map.values():
|
||||||
|
print(unit_type)
|
||||||
|
group.set_frequency(127.5)
|
||||||
|
else:
|
||||||
|
group.set_frequency(251.0)
|
||||||
|
|
||||||
def _generate_at_airport(self, name: str, side: Country, unit_type: FlyingType, count: int, client_count: int, airport: Airport = None) -> FlyingGroup:
|
def _generate_at_airport(self, name: str, side: Country, unit_type: FlyingType, count: int, client_count: int, airport: Airport = None) -> FlyingGroup:
|
||||||
assert count > 0
|
assert count > 0
|
||||||
assert unit is not None
|
assert unit is not None
|
||||||
@ -150,11 +161,11 @@ class AircraftConflictGenerator:
|
|||||||
start_type=self._start_type(),
|
start_type=self._start_type(),
|
||||||
group_size=count)
|
group_size=count)
|
||||||
|
|
||||||
def _generate_at_carrier(self, name: str, side: Country, unit_type: FlyingType, count: int, client_count: int, at: ShipGroup) -> FlyingGroup:
|
def _generate_at_group(self, name: str, side: Country, unit_type: FlyingType, count: int, client_count: int, at: typing.Union[ShipGroup, StaticGroup]) -> FlyingGroup:
|
||||||
assert count > 0
|
assert count > 0
|
||||||
assert unit is not None
|
assert unit is not None
|
||||||
|
|
||||||
logging.info("airgen: {} for {} at carrier {}".format(unit_type, side.id, at))
|
logging.info("airgen: {} for {} at unit {}".format(unit_type, side.id, at))
|
||||||
return self.m.flight_group_from_unit(
|
return self.m.flight_group_from_unit(
|
||||||
country=side,
|
country=side,
|
||||||
name=name,
|
name=name,
|
||||||
@ -167,10 +178,10 @@ class AircraftConflictGenerator:
|
|||||||
def _generate_group(self, name: str, side: Country, unit_type: FlyingType, count: int, client_count: int, at: db.StartingPosition):
|
def _generate_group(self, name: str, side: Country, unit_type: FlyingType, count: int, client_count: int, at: db.StartingPosition):
|
||||||
if isinstance(at, Point):
|
if isinstance(at, Point):
|
||||||
return self._generate_inflight(name, side, unit_type, count, client_count, at)
|
return self._generate_inflight(name, side, unit_type, count, client_count, at)
|
||||||
elif isinstance(at, ShipGroup):
|
elif isinstance(at, Group):
|
||||||
takeoff_ban = unit_type in db.CARRIER_TAKEOFF_BAN
|
takeoff_ban = unit_type in db.CARRIER_TAKEOFF_BAN
|
||||||
if not takeoff_ban:
|
if not takeoff_ban:
|
||||||
return self._generate_at_carrier(name, side, unit_type, count, client_count, at)
|
return self._generate_at_group(name, side, unit_type, count, client_count, at)
|
||||||
else:
|
else:
|
||||||
return self._generate_inflight(name, side, unit_type, count, client_count, at.position)
|
return self._generate_inflight(name, side, unit_type, count, client_count, at.position)
|
||||||
elif issubclass(at, Airport):
|
elif issubclass(at, Airport):
|
||||||
@ -187,14 +198,16 @@ class AircraftConflictGenerator:
|
|||||||
assert False
|
assert False
|
||||||
|
|
||||||
def _rtb_for(self, group: FlyingGroup, cp: ControlPoint, at: db.StartingPosition = None):
|
def _rtb_for(self, group: FlyingGroup, cp: ControlPoint, at: db.StartingPosition = None):
|
||||||
group.add_waypoint(cp.position, RTB_ALTITUDE)
|
if not at:
|
||||||
|
at = cp.at
|
||||||
|
|
||||||
if isinstance(cp.at, Point):
|
if isinstance(at, Point):
|
||||||
pass
|
group.add_waypoint(at, RTB_ALTITUDE)
|
||||||
elif isinstance(cp.at, ShipGroup):
|
elif isinstance(at, Group):
|
||||||
pass
|
group.add_waypoint(at.position, RTB_ALTITUDE)
|
||||||
elif issubclass(cp.at, Airport):
|
elif issubclass(at, Airport):
|
||||||
group.land_at(cp.at)
|
group.add_waypoint(at.position, RTB_ALTITUDE)
|
||||||
|
group.land_at(at)
|
||||||
|
|
||||||
def _at_position(self, at) -> Point:
|
def _at_position(self, at) -> Point:
|
||||||
if isinstance(at, Point):
|
if isinstance(at, Point):
|
||||||
@ -218,12 +231,6 @@ class AircraftConflictGenerator:
|
|||||||
at=at)
|
at=at)
|
||||||
|
|
||||||
group.task = Escort.name
|
group.task = Escort.name
|
||||||
|
|
||||||
"""
|
|
||||||
heading = group.position.heading_between_point(self.conflict.position)
|
|
||||||
position = group.position # type: Point
|
|
||||||
wayp = group.add_waypoint(position.point_from_heading(heading, WORKAROUND_WAYP_DIST), CAS_ALTITUDE, WARM_START_AIRSPEED)
|
|
||||||
"""
|
|
||||||
self._setup_group(group, CAP, client_count)
|
self._setup_group(group, CAP, client_count)
|
||||||
|
|
||||||
for escorted_group, waypoint_index in self.escort_targets:
|
for escorted_group, waypoint_index in self.escort_targets:
|
||||||
@ -244,8 +251,8 @@ class AircraftConflictGenerator:
|
|||||||
groups.append(group)
|
groups.append(group)
|
||||||
return groups
|
return groups
|
||||||
|
|
||||||
def generate_cas_strikegroup(self, attackers: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
|
def generate_cas_strikegroup(self, attackers: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None, escort=True):
|
||||||
assert len(self.escort_targets) == 0
|
assert not escort or len(self.escort_targets) == 0
|
||||||
|
|
||||||
for flying_type, count, client_count in self._split_to_groups(attackers, clients):
|
for flying_type, count, client_count in self._split_to_groups(attackers, clients):
|
||||||
group = self._generate_group(
|
group = self._generate_group(
|
||||||
@ -262,11 +269,37 @@ class AircraftConflictGenerator:
|
|||||||
|
|
||||||
group.task = CAS.name
|
group.task = CAS.name
|
||||||
self._setup_group(group, CAS, client_count)
|
self._setup_group(group, CAS, client_count)
|
||||||
self.escort_targets.append((group, group.points.index(waypoint)))
|
if escort:
|
||||||
|
self.escort_targets.append((group, group.points.index(waypoint)))
|
||||||
self._rtb_for(group, self.conflict.from_cp, at)
|
self._rtb_for(group, self.conflict.from_cp, at)
|
||||||
|
|
||||||
def generate_defenders_cas(self, defenders: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
|
def generate_ground_attack_strikegroup(self, strikegroup: db.PlaneDict, clients: db.PlaneDict, targets: typing.List[typing.Tuple[str, Point]], at: db.StartingPosition = None, escort=True):
|
||||||
assert len(self.escort_targets) == 0
|
assert not escort or len(self.escort_targets) == 0
|
||||||
|
|
||||||
|
for flying_type, count, client_count in self._split_to_groups(strikegroup, clients):
|
||||||
|
group = self._generate_group(
|
||||||
|
name=namegen.next_unit_name(self.conflict.attackers_side, flying_type),
|
||||||
|
side=self.conflict.attackers_side,
|
||||||
|
unit_type=flying_type,
|
||||||
|
count=count,
|
||||||
|
client_count=client_count,
|
||||||
|
at=at and at or self._group_point(self.conflict.air_attackers_location))
|
||||||
|
|
||||||
|
escort_until_waypoint = None
|
||||||
|
|
||||||
|
for name, pos in targets:
|
||||||
|
waypoint = group.add_waypoint(pos, 0, WARM_START_AIRSPEED, self.m.translation.create_string(name))
|
||||||
|
if escort_until_waypoint is None:
|
||||||
|
escort_until_waypoint = waypoint
|
||||||
|
|
||||||
|
group.task = GroundAttack.name
|
||||||
|
self._setup_group(group, GroundAttack, client_count)
|
||||||
|
if escort:
|
||||||
|
self.escort_targets.append((group, group.points.index(escort_until_waypoint)))
|
||||||
|
self._rtb_for(group, self.conflict.from_cp, at)
|
||||||
|
|
||||||
|
def generate_defenders_cas(self, defenders: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None, escort=True):
|
||||||
|
assert not escort or len(self.escort_targets) == 0
|
||||||
|
|
||||||
for flying_type, count, client_count in self._split_to_groups(defenders, clients):
|
for flying_type, count, client_count in self._split_to_groups(defenders, clients):
|
||||||
group = self._generate_group(
|
group = self._generate_group(
|
||||||
@ -287,11 +320,12 @@ class AircraftConflictGenerator:
|
|||||||
|
|
||||||
group.task = CAS.name
|
group.task = CAS.name
|
||||||
self._setup_group(group, CAS, client_count)
|
self._setup_group(group, CAS, client_count)
|
||||||
self.escort_targets.append((group, group.points.index(waypoint)))
|
if escort:
|
||||||
|
self.escort_targets.append((group, group.points.index(waypoint)))
|
||||||
self._rtb_for(group, self.conflict.to_cp, at)
|
self._rtb_for(group, self.conflict.to_cp, at)
|
||||||
|
|
||||||
def generate_ship_strikegroup(self, attackers: db.PlaneDict, clients: db.PlaneDict, target_groups: typing.Collection[ShipGroup], at: db.StartingPosition = None):
|
def generate_ship_strikegroup(self, attackers: db.PlaneDict, clients: db.PlaneDict, target_groups: typing.Collection[ShipGroup], at: db.StartingPosition = None, escort=True):
|
||||||
assert len(self.escort_targets) == 0
|
assert not escort or len(self.escort_targets) == 0
|
||||||
|
|
||||||
for flying_type, count, client_count in self._split_to_groups(attackers, clients):
|
for flying_type, count, client_count in self._split_to_groups(attackers, clients):
|
||||||
group = self._generate_group(
|
group = self._generate_group(
|
||||||
@ -308,7 +342,8 @@ class AircraftConflictGenerator:
|
|||||||
|
|
||||||
group.task = AntishipStrike.name
|
group.task = AntishipStrike.name
|
||||||
self._setup_group(group, AntishipStrike, client_count)
|
self._setup_group(group, AntishipStrike, client_count)
|
||||||
self.escort_targets.append((group, group.points.index(wayp)))
|
if escort:
|
||||||
|
self.escort_targets.append((group, group.points.index(wayp)))
|
||||||
self._rtb_for(group, self.conflict.from_cp, at)
|
self._rtb_for(group, self.conflict.from_cp, at)
|
||||||
|
|
||||||
def generate_attackers_escort(self, attackers: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
|
def generate_attackers_escort(self, attackers: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
|
||||||
@ -348,7 +383,7 @@ class AircraftConflictGenerator:
|
|||||||
self._setup_group(group, CAP, client_count)
|
self._setup_group(group, CAP, client_count)
|
||||||
self._rtb_for(group, self.conflict.to_cp, at)
|
self._rtb_for(group, self.conflict.to_cp, at)
|
||||||
|
|
||||||
def generate_patrol(self, patrol: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
|
def generate_migcap(self, patrol: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
|
||||||
for flying_type, count, client_count in self._split_to_groups(patrol, clients):
|
for flying_type, count, client_count in self._split_to_groups(patrol, clients):
|
||||||
group = self._generate_group(
|
group = self._generate_group(
|
||||||
name=namegen.next_unit_name(self.conflict.attackers_side, flying_type),
|
name=namegen.next_unit_name(self.conflict.attackers_side, flying_type),
|
||||||
@ -366,8 +401,32 @@ class AircraftConflictGenerator:
|
|||||||
self._setup_group(group, CAP, client_count)
|
self._setup_group(group, CAP, client_count)
|
||||||
self._rtb_for(group, self.conflict.from_cp, at)
|
self._rtb_for(group, self.conflict.from_cp, at)
|
||||||
|
|
||||||
def generate_transport(self, transport: db.PlaneDict, destination: Airport):
|
def generate_barcap(self, patrol: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
|
||||||
assert len(self.escort_targets) == 0
|
for flying_type, count, client_count in self._split_to_groups(patrol, clients):
|
||||||
|
group = self._generate_group(
|
||||||
|
name=namegen.next_unit_name(self.conflict.defenders_side, flying_type),
|
||||||
|
side=self.conflict.defenders_side,
|
||||||
|
unit_type=flying_type,
|
||||||
|
count=count,
|
||||||
|
client_count=client_count,
|
||||||
|
at=at and at or self._group_point(self.conflict.air_defenders_location))
|
||||||
|
|
||||||
|
waypoint = group.add_waypoint(self.conflict.position, WARM_START_ALTITUDE, WARM_START_AIRSPEED)
|
||||||
|
if self.conflict.is_vector:
|
||||||
|
group.add_waypoint(self.conflict.tail, WARM_START_ALTITUDE, WARM_START_AIRSPEED)
|
||||||
|
else:
|
||||||
|
heading = group.position.heading_between_point(self.conflict.position)
|
||||||
|
waypoint = group.add_waypoint(self.conflict.position.point_from_heading(heading, BARCAP_RACETRACK_DISTANCE),
|
||||||
|
WARM_START_ALTITUDE,
|
||||||
|
WARM_START_AIRSPEED)
|
||||||
|
waypoint.tasks.append(OrbitAction(WARM_START_ALTITUDE, WARM_START_AIRSPEED))
|
||||||
|
|
||||||
|
group.task = CAP.name
|
||||||
|
self._setup_group(group, CAP, client_count)
|
||||||
|
self._rtb_for(group, self.conflict.to_cp, at)
|
||||||
|
|
||||||
|
def generate_transport(self, transport: db.PlaneDict, destination: Airport, escort=True):
|
||||||
|
assert not escort or len(self.escort_targets) == 0
|
||||||
|
|
||||||
for flying_type, count, client_count in self._split_to_groups(transport):
|
for flying_type, count, client_count in self._split_to_groups(transport):
|
||||||
group = self._generate_group(
|
group = self._generate_group(
|
||||||
@ -379,8 +438,8 @@ class AircraftConflictGenerator:
|
|||||||
at=self._group_point(self.conflict.air_defenders_location))
|
at=self._group_point(self.conflict.air_defenders_location))
|
||||||
|
|
||||||
waypoint = group.add_waypoint(destination.position.random_point_within(0, 0), TRANSPORT_LANDING_ALT)
|
waypoint = group.add_waypoint(destination.position.random_point_within(0, 0), TRANSPORT_LANDING_ALT)
|
||||||
self.escort_targets.append((group, group.points.index(waypoint)))
|
if escort:
|
||||||
|
self.escort_targets.append((group, group.points.index(waypoint)))
|
||||||
group.task = Transport.name
|
group.task = Transport.name
|
||||||
group.land_at(destination)
|
group.land_at(destination)
|
||||||
|
|
||||||
@ -395,10 +454,7 @@ class AircraftConflictGenerator:
|
|||||||
at=at and at or self._group_point(self.conflict.air_attackers_location))
|
at=at and at or self._group_point(self.conflict.air_attackers_location))
|
||||||
|
|
||||||
group.task = CAP.name
|
group.task = CAP.name
|
||||||
|
group.points[0].tasks.append(EngageTargets(max_distance=INTERCEPT_MAX_DISTANCE))
|
||||||
heading = group.position.heading_between_point(self.conflict.position)
|
|
||||||
initial_wayp = group.add_waypoint(group.position.point_from_heading(heading, WORKAROUND_WAYP_DIST), INTERCEPTION_ALT, INTERCEPTION_AIRSPEED)
|
|
||||||
initial_wayp.tasks.append(EngageTargets(max_distance=INTERCEPT_MAX_DISTANCE))
|
|
||||||
|
|
||||||
wayp = group.add_waypoint(self.conflict.position, WARM_START_ALTITUDE, INTERCEPTION_AIRSPEED)
|
wayp = group.add_waypoint(self.conflict.position, WARM_START_ALTITUDE, INTERCEPTION_AIRSPEED)
|
||||||
wayp.tasks.append(EngageTargets(max_distance=INTERCEPT_MAX_DISTANCE))
|
wayp.tasks.append(EngageTargets(max_distance=INTERCEPT_MAX_DISTANCE))
|
||||||
|
|||||||
@ -9,36 +9,46 @@ from dcs.task import *
|
|||||||
from dcs.terrain.terrain import NoParkingSlotError
|
from dcs.terrain.terrain import NoParkingSlotError
|
||||||
|
|
||||||
TANKER_DISTANCE = 15000
|
TANKER_DISTANCE = 15000
|
||||||
TANKER_ALT = 10000
|
TANKER_ALT = 4572
|
||||||
|
TANKER_HEADING_OFFSET = 45
|
||||||
|
|
||||||
AWACS_DISTANCE = 150000
|
AWACS_DISTANCE = 150000
|
||||||
AWACS_ALT = 10000
|
AWACS_ALT = 13000
|
||||||
|
|
||||||
|
|
||||||
class AirSupportConflictGenerator:
|
class AirSupportConflictGenerator:
|
||||||
|
generated_tankers = None # type: typing.List[str]
|
||||||
|
|
||||||
def __init__(self, mission: Mission, conflict: Conflict, game):
|
def __init__(self, mission: Mission, conflict: Conflict, game):
|
||||||
self.mission = mission
|
self.mission = mission
|
||||||
self.conflict = conflict
|
self.conflict = conflict
|
||||||
self.game = game
|
self.game = game
|
||||||
|
self.generated_tankers = []
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def support_tasks(cls) -> typing.Collection[typing.Type[MainTask]]:
|
||||||
|
return [Refueling, AWACS]
|
||||||
|
|
||||||
def generate(self, is_awacs_enabled):
|
def generate(self, is_awacs_enabled):
|
||||||
player_cp = self.conflict.from_cp if self.conflict.from_cp.captured else self.conflict.to_cp
|
player_cp = self.conflict.from_cp if self.conflict.from_cp.captured else self.conflict.to_cp
|
||||||
tanker_unit = db.find_unittype(Refueling, self.conflict.attackers_side.name)[0]
|
|
||||||
tanker_heading = self.conflict.to_cp.position.heading_between_point(self.conflict.from_cp.position)
|
|
||||||
tanker_position = player_cp.position.point_from_heading(tanker_heading, TANKER_DISTANCE)
|
|
||||||
tanker_group = self.mission.refuel_flight(
|
|
||||||
country=self.mission.country(self.game.player),
|
|
||||||
name=namegen.next_tanker_name(self.mission.country(self.game.player)),
|
|
||||||
airport=None,
|
|
||||||
plane_type=tanker_unit,
|
|
||||||
position=tanker_position,
|
|
||||||
altitude=TANKER_ALT,
|
|
||||||
frequency=240,
|
|
||||||
start_type=StartType.Warm,
|
|
||||||
tacanchannel="99X",
|
|
||||||
)
|
|
||||||
|
|
||||||
tanker_group.points[0].tasks.append(ActivateBeaconCommand(channel=10, unit_id=tanker_group.id, aa=False))
|
for i, tanker_unit_type in enumerate(db.find_unittype(Refueling, self.conflict.attackers_side.name)):
|
||||||
|
self.generated_tankers.append(db.unit_type_name(tanker_unit_type))
|
||||||
|
tanker_heading = self.conflict.to_cp.position.heading_between_point(self.conflict.from_cp.position) + TANKER_HEADING_OFFSET * i
|
||||||
|
tanker_position = player_cp.position.point_from_heading(tanker_heading, TANKER_DISTANCE)
|
||||||
|
tanker_group = self.mission.refuel_flight(
|
||||||
|
country=self.mission.country(self.game.player),
|
||||||
|
name=namegen.next_tanker_name(self.mission.country(self.game.player)),
|
||||||
|
airport=None,
|
||||||
|
plane_type=tanker_unit_type,
|
||||||
|
position=tanker_position,
|
||||||
|
altitude=TANKER_ALT,
|
||||||
|
frequency=130 + i,
|
||||||
|
start_type=StartType.Warm,
|
||||||
|
tacanchannel="{}X".format(97 + i),
|
||||||
|
)
|
||||||
|
|
||||||
|
tanker_group.points[0].tasks.append(ActivateBeaconCommand(channel=97 + i, unit_id=tanker_group.id, aa=False))
|
||||||
|
|
||||||
if is_awacs_enabled:
|
if is_awacs_enabled:
|
||||||
awacs_unit = db.find_unittype(AWACS, self.conflict.attackers_side.name)[0]
|
awacs_unit = db.find_unittype(AWACS, self.conflict.attackers_side.name)[0]
|
||||||
@ -49,6 +59,6 @@ class AirSupportConflictGenerator:
|
|||||||
altitude=AWACS_ALT,
|
altitude=AWACS_ALT,
|
||||||
airport=None,
|
airport=None,
|
||||||
position=self.conflict.position.random_point_within(AWACS_DISTANCE, AWACS_DISTANCE),
|
position=self.conflict.position.random_point_within(AWACS_DISTANCE, AWACS_DISTANCE),
|
||||||
frequency=244,
|
frequency=133,
|
||||||
start_type=StartType.Warm,
|
start_type=StartType.Warm,
|
||||||
)
|
)
|
||||||
|
|||||||
15
gen/armor.py
15
gen/armor.py
@ -20,6 +20,8 @@ FRONTLINE_CAS_FIGHTS_COUNT = 4, 8
|
|||||||
FRONTLINE_CAS_GROUP_MIN = 1, 2
|
FRONTLINE_CAS_GROUP_MIN = 1, 2
|
||||||
FRONTLINE_CAS_PADDING = 12000
|
FRONTLINE_CAS_PADDING = 12000
|
||||||
|
|
||||||
|
FIGHT_DISTANCE = 1500
|
||||||
|
|
||||||
|
|
||||||
class ArmorConflictGenerator:
|
class ArmorConflictGenerator:
|
||||||
def __init__(self, mission: Mission, conflict: Conflict):
|
def __init__(self, mission: Mission, conflict: Conflict):
|
||||||
@ -45,6 +47,9 @@ class ArmorConflictGenerator:
|
|||||||
group_size=1,
|
group_size=1,
|
||||||
move_formation=PointAction.OffRoad)
|
move_formation=PointAction.OffRoad)
|
||||||
|
|
||||||
|
vehicle: Vehicle = group.units[0]
|
||||||
|
vehicle.player_can_drive = True
|
||||||
|
|
||||||
if not to:
|
if not to:
|
||||||
to = self.conflict.position.point_from_heading(0, 500)
|
to = self.conflict.position.point_from_heading(0, 500)
|
||||||
|
|
||||||
@ -53,8 +58,8 @@ class ArmorConflictGenerator:
|
|||||||
|
|
||||||
def _generate_fight_at(self, attackers: db.ArmorDict, defenders: db.ArmorDict, position: Point):
|
def _generate_fight_at(self, attackers: db.ArmorDict, defenders: db.ArmorDict, position: Point):
|
||||||
if attackers:
|
if attackers:
|
||||||
attack_pos = position.point_from_heading(self.conflict.heading - 90, 8000)
|
attack_pos = position.point_from_heading(self.conflict.heading - 90, FIGHT_DISTANCE)
|
||||||
attack_dest = position.point_from_heading(self.conflict.heading + 90, 25000)
|
attack_dest = position.point_from_heading(self.conflict.heading + 90, FIGHT_DISTANCE * 2)
|
||||||
for type, count in attackers.items():
|
for type, count in attackers.items():
|
||||||
self._generate_group(
|
self._generate_group(
|
||||||
side=self.conflict.attackers_side,
|
side=self.conflict.attackers_side,
|
||||||
@ -65,8 +70,8 @@ class ArmorConflictGenerator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if defenders:
|
if defenders:
|
||||||
def_pos = position.point_from_heading(self.conflict.heading + 90, 4000)
|
def_pos = position.point_from_heading(self.conflict.heading + 90, FIGHT_DISTANCE)
|
||||||
def_dest = position.point_from_heading(self.conflict.heading - 90, 25000)
|
def_dest = position.point_from_heading(self.conflict.heading - 90, FIGHT_DISTANCE * 2)
|
||||||
for type, count in defenders.items():
|
for type, count in defenders.items():
|
||||||
self._generate_group(
|
self._generate_group(
|
||||||
side=self.conflict.defenders_side,
|
side=self.conflict.defenders_side,
|
||||||
@ -101,7 +106,7 @@ class ArmorConflictGenerator:
|
|||||||
|
|
||||||
for attacker_group_dict, target_group_dict in zip_longest(attacker_groups, defender_groups):
|
for attacker_group_dict, target_group_dict in zip_longest(attacker_groups, defender_groups):
|
||||||
position = self.conflict.position.point_from_heading(self.conflict.heading,
|
position = self.conflict.position.point_from_heading(self.conflict.heading,
|
||||||
random.randint(FRONTLINE_CAS_PADDING, int(self.conflict.distance - FRONTLINE_CAS_PADDING)))
|
random.randint(0, self.conflict.distance))
|
||||||
self._generate_fight_at(attacker_group_dict, target_group_dict, position)
|
self._generate_fight_at(attacker_group_dict, target_group_dict, position)
|
||||||
|
|
||||||
def generate_passengers(self, count: int):
|
def generate_passengers(self, count: int):
|
||||||
|
|||||||
63
gen/briefinggen.py
Normal file
63
gen/briefinggen.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from game import db
|
||||||
|
from .conflictgen import *
|
||||||
|
from .naming import *
|
||||||
|
|
||||||
|
from dcs.mission import *
|
||||||
|
|
||||||
|
|
||||||
|
class BriefingGenerator:
|
||||||
|
freqs = None # type: typing.List[typing.Tuple[str, str]]
|
||||||
|
title = "" # type: str
|
||||||
|
description = "" # type: str
|
||||||
|
targets = None # type: typing.List[typing.Tuple[str, str]]
|
||||||
|
waypoints = None # type: typing.List[str]
|
||||||
|
|
||||||
|
def __init__(self, mission: Mission, conflict: Conflict, game):
|
||||||
|
self.m = mission
|
||||||
|
self.conflict = conflict
|
||||||
|
self.game = game
|
||||||
|
|
||||||
|
self.freqs = []
|
||||||
|
self.targets = []
|
||||||
|
self.waypoints = []
|
||||||
|
|
||||||
|
def append_frequency(self, name: str, frequency: str):
|
||||||
|
self.freqs.append((name, frequency))
|
||||||
|
|
||||||
|
def append_target(self, description: str, markpoint: str = None):
|
||||||
|
self.targets.append((description, markpoint))
|
||||||
|
|
||||||
|
def append_waypoint(self, description: str):
|
||||||
|
self.waypoints.append(description)
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
self.waypoints.insert(0, "INITIAL")
|
||||||
|
self.waypoints.append("RTB")
|
||||||
|
self.waypoints.append("RTB Landing")
|
||||||
|
|
||||||
|
description = ""
|
||||||
|
|
||||||
|
if self.title:
|
||||||
|
description += self.title
|
||||||
|
|
||||||
|
if self.description:
|
||||||
|
description += "\n\n" + self.description
|
||||||
|
|
||||||
|
if self.freqs:
|
||||||
|
description += "\n\nCOMMS:"
|
||||||
|
for name, freq in self.freqs:
|
||||||
|
description += "\n{}: {}".format(name, freq)
|
||||||
|
|
||||||
|
if self.targets:
|
||||||
|
description += "\n\nTARGETS:"
|
||||||
|
for i, (name, tp) in enumerate(self.targets):
|
||||||
|
description += "\n#{} {} {}".format(i+1, name, "(TP {})".format(tp) if tp else "")
|
||||||
|
|
||||||
|
if self.waypoints:
|
||||||
|
description += "\n\nWAYPOINTS:"
|
||||||
|
for i, descr in enumerate(self.waypoints):
|
||||||
|
description += "\n#{}: {}".format(i, descr)
|
||||||
|
|
||||||
|
self.m.set_description_text(description)
|
||||||
@ -21,10 +21,15 @@ AIR_DISTANCE = 40000
|
|||||||
|
|
||||||
CAPTURE_AIR_ATTACKERS_DISTANCE = 25000
|
CAPTURE_AIR_ATTACKERS_DISTANCE = 25000
|
||||||
CAPTURE_AIR_DEFENDERS_DISTANCE = 60000
|
CAPTURE_AIR_DEFENDERS_DISTANCE = 60000
|
||||||
|
STRIKE_AIR_ATTACKERS_DISTANCE = 45000
|
||||||
|
STRIKE_AIR_DEFENDERS_DISTANCE = 25000
|
||||||
|
|
||||||
CAP_CAS_DISTANCE = 10000, 120000
|
CAP_CAS_DISTANCE = 10000, 120000
|
||||||
|
|
||||||
GROUND_INTERCEPT_SPREAD = 5000
|
GROUND_INTERCEPT_SPREAD = 5000
|
||||||
GROUND_DISTANCE_FACTOR = 1
|
GROUND_DISTANCE_FACTOR = 1
|
||||||
|
GROUND_DISTANCE = 4000
|
||||||
|
|
||||||
GROUND_ATTACK_DISTANCE = 25000, 13000
|
GROUND_ATTACK_DISTANCE = 25000, 13000
|
||||||
|
|
||||||
TRANSPORT_FRONTLINE_DIST = 1800
|
TRANSPORT_FRONTLINE_DIST = 1800
|
||||||
@ -125,63 +130,94 @@ class Conflict:
|
|||||||
return self.to_cp.size * GROUND_DISTANCE_FACTOR
|
return self.to_cp.size * GROUND_DISTANCE_FACTOR
|
||||||
|
|
||||||
def find_insertion_point(self, other_point: Point) -> Point:
|
def find_insertion_point(self, other_point: Point) -> Point:
|
||||||
dx = self.position.x - self.tail.x
|
if self.is_vector:
|
||||||
dy = self.position.y - self.tail.y
|
dx = self.position.x - self.tail.x
|
||||||
dr2 = float(dx ** 2 + dy ** 2)
|
dy = self.position.y - self.tail.y
|
||||||
|
dr2 = float(dx ** 2 + dy ** 2)
|
||||||
|
|
||||||
lerp = ((other_point.x - self.tail.x) * dx + (other_point.y - self.tail.y) * dy) / dr2
|
lerp = ((other_point.x - self.tail.x) * dx + (other_point.y - self.tail.y) * dy) / dr2
|
||||||
if lerp < 0:
|
if lerp < 0:
|
||||||
lerp = 0
|
lerp = 0
|
||||||
elif lerp > 1:
|
elif lerp > 1:
|
||||||
lerp = 1
|
lerp = 1
|
||||||
|
|
||||||
x = lerp * dx + self.tail.x
|
x = lerp * dx + self.tail.x
|
||||||
y = lerp * dy + self.tail.y
|
y = lerp * dy + self.tail.y
|
||||||
return Point(x, y)
|
return Point(x, y)
|
||||||
|
else:
|
||||||
|
return self.position
|
||||||
|
|
||||||
|
def find_ground_position(self, at: Point, heading: int, max_distance: int = 40000) -> typing.Optional[Point]:
|
||||||
|
return Conflict._find_ground_position(at, max_distance, heading, self.theater)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def has_frontline_between(cls, from_cp: ControlPoint, to_cp: ControlPoint) -> bool:
|
def has_frontline_between(cls, from_cp: ControlPoint, to_cp: ControlPoint) -> bool:
|
||||||
return from_cp.has_frontline and to_cp.has_frontline
|
return from_cp.has_frontline and to_cp.has_frontline
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def frontline_position(cls, from_cp: ControlPoint, to_cp: ControlPoint) -> typing.Tuple[Point, int]:
|
def frontline_position(cls, theater: ConflictTheater, from_cp: ControlPoint, to_cp: ControlPoint) -> typing.Optional[typing.Tuple[Point, int]]:
|
||||||
distance = max(from_cp.position.distance_to_point(to_cp.position) * FRONTLINE_DISTANCE_STRENGTH_FACTOR * to_cp.base.strength, FRONTLINE_MIN_CP_DISTANCE)
|
attack_heading = from_cp.position.heading_between_point(to_cp.position)
|
||||||
heading = to_cp.position.heading_between_point(from_cp.position)
|
attack_distance = from_cp.position.distance_to_point(to_cp.position)
|
||||||
return to_cp.position.point_from_heading(heading, distance), heading
|
middle_point = from_cp.position.point_from_heading(attack_heading, attack_distance / 2)
|
||||||
|
|
||||||
|
strength_delta = (from_cp.base.strength - to_cp.base.strength) / 1.0
|
||||||
|
position = middle_point.point_from_heading(attack_heading, strength_delta * attack_distance / 2 - FRONTLINE_MIN_CP_DISTANCE)
|
||||||
|
ground_position = cls._find_ground_position(position, attack_distance / 2 - FRONTLINE_MIN_CP_DISTANCE, attack_heading, theater)
|
||||||
|
if ground_position:
|
||||||
|
return ground_position, _opposite_heading(attack_heading)
|
||||||
|
else:
|
||||||
|
logging.warning("Coudn't find frontline position between {} and {}!".format(from_cp, to_cp))
|
||||||
|
return position, _opposite_heading(attack_heading)
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def frontline_vector(cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater) -> typing.Tuple[Point, int, int]:
|
def frontline_vector(cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater) -> typing.Optional[typing.Tuple[Point, int, int]]:
|
||||||
center_position, heading = cls.frontline_position(from_cp, to_cp)
|
frontline = cls.frontline_position(theater, from_cp, to_cp)
|
||||||
|
if not frontline:
|
||||||
|
return None
|
||||||
|
|
||||||
left_position = center_position
|
center_position, heading = frontline
|
||||||
|
left_position, right_position = None, None
|
||||||
|
|
||||||
for offset in range(0, int(FRONTLINE_LENGTH / 2), 1000):
|
if not theater.is_on_land(center_position):
|
||||||
pos = center_position.point_from_heading(_heading_sum(heading, -90), offset)
|
pos = cls._find_ground_position(center_position, FRONTLINE_LENGTH, _heading_sum(heading, -90), theater)
|
||||||
if not theater.is_on_land(pos):
|
if pos:
|
||||||
break
|
|
||||||
else:
|
|
||||||
left_position = pos
|
|
||||||
|
|
||||||
right_position = center_position
|
|
||||||
for offset in range(0, int(FRONTLINE_LENGTH / 2), 1000):
|
|
||||||
pos = center_position.point_from_heading(_heading_sum(heading, 90), offset)
|
|
||||||
if not theater.is_on_land(pos):
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
right_position = pos
|
right_position = pos
|
||||||
|
center_position = pos
|
||||||
|
else:
|
||||||
|
pos = cls._find_ground_position(center_position, FRONTLINE_LENGTH, _heading_sum(heading, +90), theater)
|
||||||
|
if pos:
|
||||||
|
left_position = pos
|
||||||
|
center_position = pos
|
||||||
|
|
||||||
return left_position, _heading_sum(heading, 90), right_position.distance_to_point(left_position)
|
if left_position is None:
|
||||||
|
left_position = cls._extend_ground_position(center_position, int(FRONTLINE_LENGTH/2), _heading_sum(heading, -90), theater)
|
||||||
|
|
||||||
|
if right_position is None:
|
||||||
|
right_position = cls._extend_ground_position(center_position, int(FRONTLINE_LENGTH/2), _heading_sum(heading, 90), theater)
|
||||||
|
|
||||||
|
return left_position, _heading_sum(heading, 90), int(right_position.distance_to_point(left_position))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _find_ground_location(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point:
|
def _extend_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point:
|
||||||
for _ in range(0, int(max_distance), 800):
|
pos = initial
|
||||||
for _ in range(3):
|
for offset in range(0, int(max_distance), 500):
|
||||||
if theater.is_on_land(initial):
|
new_pos = initial.point_from_heading(heading, offset)
|
||||||
return initial
|
if theater.is_on_land(new_pos):
|
||||||
|
pos = new_pos
|
||||||
|
else:
|
||||||
|
return pos
|
||||||
|
|
||||||
initial = initial.random_point_within(1000, 1000)
|
return pos
|
||||||
|
|
||||||
initial = initial.point_from_heading(heading, 800)
|
@classmethod
|
||||||
|
def _find_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> typing.Optional[Point]:
|
||||||
|
pos = initial
|
||||||
|
for _ in range(0, int(max_distance), 500):
|
||||||
|
if theater.is_on_land(pos):
|
||||||
|
return pos
|
||||||
|
|
||||||
|
pos = pos.point_from_heading(heading, 500)
|
||||||
|
|
||||||
logging.info("Didn't find ground position!")
|
logging.info("Didn't find ground position!")
|
||||||
return None
|
return None
|
||||||
@ -193,12 +229,12 @@ class Conflict:
|
|||||||
attack_heading = to_cp.find_radial(attack_raw_heading)
|
attack_heading = to_cp.find_radial(attack_raw_heading)
|
||||||
defense_heading = to_cp.find_radial(from_cp.position.heading_between_point(to_cp.position), ignored_radial=attack_heading)
|
defense_heading = to_cp.find_radial(from_cp.position.heading_between_point(to_cp.position), ignored_radial=attack_heading)
|
||||||
|
|
||||||
distance = to_cp.size * GROUND_DISTANCE_FACTOR
|
distance = GROUND_DISTANCE
|
||||||
attackers_location = position.point_from_heading(attack_heading, distance)
|
attackers_location = position.point_from_heading(attack_heading, distance)
|
||||||
attackers_location = Conflict._find_ground_location(attackers_location, distance * 2, _heading_sum(attack_heading, 180), theater)
|
attackers_location = Conflict._find_ground_position(attackers_location, distance * 2, attack_heading, theater)
|
||||||
|
|
||||||
defenders_location = position.point_from_heading(defense_heading, distance)
|
defenders_location = position.point_from_heading(defense_heading, 0)
|
||||||
defenders_location = Conflict._find_ground_location(defenders_location, distance * 2, _heading_sum(defense_heading, 180), theater)
|
defenders_location = Conflict._find_ground_position(defenders_location, distance * 2, defense_heading, theater)
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
position=position,
|
position=position,
|
||||||
@ -213,6 +249,33 @@ class Conflict:
|
|||||||
air_defenders_location=position.point_from_heading(_opposite_heading(attack_raw_heading), CAPTURE_AIR_DEFENDERS_DISTANCE)
|
air_defenders_location=position.point_from_heading(_opposite_heading(attack_raw_heading), CAPTURE_AIR_DEFENDERS_DISTANCE)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def strike_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
|
||||||
|
position = to_cp.position
|
||||||
|
attack_raw_heading = to_cp.position.heading_between_point(from_cp.position)
|
||||||
|
attack_heading = to_cp.find_radial(attack_raw_heading)
|
||||||
|
defense_heading = to_cp.find_radial(from_cp.position.heading_between_point(to_cp.position), ignored_radial=attack_heading)
|
||||||
|
|
||||||
|
distance = to_cp.size * GROUND_DISTANCE_FACTOR
|
||||||
|
attackers_location = position.point_from_heading(attack_heading, distance)
|
||||||
|
attackers_location = Conflict._find_ground_position(attackers_location, distance * 2, _heading_sum(attack_heading, 180), theater)
|
||||||
|
|
||||||
|
defenders_location = position.point_from_heading(defense_heading, distance)
|
||||||
|
defenders_location = Conflict._find_ground_position(defenders_location, distance * 2, _heading_sum(defense_heading, 180), theater)
|
||||||
|
|
||||||
|
return cls(
|
||||||
|
position=position,
|
||||||
|
theater=theater,
|
||||||
|
from_cp=from_cp,
|
||||||
|
to_cp=to_cp,
|
||||||
|
attackers_side=attacker,
|
||||||
|
defenders_side=defender,
|
||||||
|
ground_attackers_location=attackers_location,
|
||||||
|
ground_defenders_location=defenders_location,
|
||||||
|
air_attackers_location=position.point_from_heading(attack_raw_heading, STRIKE_AIR_ATTACKERS_DISTANCE),
|
||||||
|
air_defenders_location=position.point_from_heading(_opposite_heading(attack_raw_heading), STRIKE_AIR_DEFENDERS_DISTANCE)
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def intercept_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
|
def intercept_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
|
||||||
raw_distance = from_cp.position.distance_to_point(to_cp.position) * 1.5
|
raw_distance = from_cp.position.distance_to_point(to_cp.position) * 1.5
|
||||||
@ -238,7 +301,7 @@ class Conflict:
|
|||||||
def ground_attack_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
|
def ground_attack_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
|
||||||
heading = random.choice(to_cp.radials)
|
heading = random.choice(to_cp.radials)
|
||||||
initial_location = to_cp.position.random_point_within(*GROUND_ATTACK_DISTANCE)
|
initial_location = to_cp.position.random_point_within(*GROUND_ATTACK_DISTANCE)
|
||||||
position = Conflict._find_ground_location(initial_location, GROUND_INTERCEPT_SPREAD, _heading_sum(heading, 180), theater)
|
position = Conflict._find_ground_position(initial_location, GROUND_INTERCEPT_SPREAD, _heading_sum(heading, 180), theater)
|
||||||
if not position:
|
if not position:
|
||||||
heading = to_cp.find_radial(to_cp.position.heading_between_point(from_cp.position))
|
heading = to_cp.find_radial(to_cp.position.heading_between_point(from_cp.position))
|
||||||
position = to_cp.position.point_from_heading(heading, to_cp.size * GROUND_DISTANCE_FACTOR)
|
position = to_cp.position.point_from_heading(heading, to_cp.size * GROUND_DISTANCE_FACTOR)
|
||||||
@ -306,7 +369,7 @@ class Conflict:
|
|||||||
|
|
||||||
distance = to_cp.size * GROUND_DISTANCE_FACTOR
|
distance = to_cp.size * GROUND_DISTANCE_FACTOR
|
||||||
defenders_location = position.point_from_heading(defense_heading, distance)
|
defenders_location = position.point_from_heading(defense_heading, distance)
|
||||||
defenders_location = Conflict._find_ground_location(defenders_location, distance * 2, _heading_sum(defense_heading, 180), theater)
|
defenders_location = Conflict._find_ground_position(defenders_location, distance * 2, _heading_sum(defense_heading, 180), theater)
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
position=position,
|
position=position,
|
||||||
@ -349,9 +412,9 @@ class Conflict:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def transport_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
|
def transport_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater):
|
||||||
frontline_position, heading = cls.frontline_position(from_cp, to_cp)
|
frontline_position, heading = cls.frontline_position(theater, from_cp, to_cp)
|
||||||
initial_dest = frontline_position.point_from_heading(heading, TRANSPORT_FRONTLINE_DIST)
|
initial_dest = frontline_position.point_from_heading(heading, TRANSPORT_FRONTLINE_DIST)
|
||||||
dest = cls._find_ground_location(initial_dest, from_cp.position.distance_to_point(to_cp.position) / 3, heading, theater)
|
dest = cls._find_ground_position(initial_dest, from_cp.position.distance_to_point(to_cp.position) / 3, heading, theater)
|
||||||
if not dest:
|
if not dest:
|
||||||
radial = to_cp.find_radial(to_cp.position.heading_between_point(from_cp.position))
|
radial = to_cp.find_radial(to_cp.position.heading_between_point(from_cp.position))
|
||||||
dest = to_cp.position.point_from_heading(radial, to_cp.size * GROUND_DISTANCE_FACTOR)
|
dest = to_cp.position.point_from_heading(radial, to_cp.size * GROUND_DISTANCE_FACTOR)
|
||||||
|
|||||||
@ -21,6 +21,10 @@ WEATHER_CLOUD_DENSITY = 1, 8
|
|||||||
WEATHER_CLOUD_THICKNESS = 100, 400
|
WEATHER_CLOUD_THICKNESS = 100, 400
|
||||||
WEATHER_CLOUD_BASE_MIN = 1600
|
WEATHER_CLOUD_BASE_MIN = 1600
|
||||||
|
|
||||||
|
WEATHER_FOG_CHANCE = 20
|
||||||
|
WEATHER_FOG_VISIBILITY = 2500, 5000
|
||||||
|
WEATHER_FOG_THICKNESS = 100, 500
|
||||||
|
|
||||||
RANDOM_TIME = {
|
RANDOM_TIME = {
|
||||||
"night": 5,
|
"night": 5,
|
||||||
"dusk": 30,
|
"dusk": 30,
|
||||||
@ -29,11 +33,10 @@ RANDOM_TIME = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
RANDOM_WEATHER = {
|
RANDOM_WEATHER = {
|
||||||
1: 0, # heavy rain
|
1: 0, # thunderstorm
|
||||||
2: 10, # rain
|
2: 20, # rain
|
||||||
3: 20, # dynamic
|
3: 80, # clouds
|
||||||
4: 30, # clear
|
4: 100, # clear
|
||||||
5: 100, # random
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -49,7 +52,8 @@ class EnviromentGenerator:
|
|||||||
self.game = game
|
self.game = game
|
||||||
|
|
||||||
def _gen_random_time(self):
|
def _gen_random_time(self):
|
||||||
start_time = datetime.combine(datetime.today(), time())
|
start_time = datetime.strptime('May 25 2018 12:00AM', '%b %d %Y %I:%M%p')
|
||||||
|
|
||||||
time_range = None
|
time_range = None
|
||||||
for k, v in RANDOM_TIME.items():
|
for k, v in RANDOM_TIME.items():
|
||||||
if self.game.settings.night_disabled and k == "night":
|
if self.game.settings.night_disabled and k == "night":
|
||||||
@ -60,8 +64,36 @@ class EnviromentGenerator:
|
|||||||
break
|
break
|
||||||
|
|
||||||
start_time += timedelta(hours=random.randint(*time_range))
|
start_time += timedelta(hours=random.randint(*time_range))
|
||||||
|
logging.info("time - {}, slot - {}, night skipped - {}".format(
|
||||||
|
str(start_time),
|
||||||
|
str(time_range),
|
||||||
|
self.game.settings.night_disabled))
|
||||||
|
|
||||||
self.mission.start_time = start_time
|
self.mission.start_time = start_time
|
||||||
|
|
||||||
|
def _generate_wind(self, wind_speed, wind_direction=None):
|
||||||
|
# wind
|
||||||
|
if not wind_direction:
|
||||||
|
wind_direction = random.randint(0, 360)
|
||||||
|
|
||||||
|
self.mission.weather.wind_at_ground = Wind(wind_direction, wind_speed)
|
||||||
|
self.mission.weather.wind_at_2000 = Wind(wind_direction, wind_speed * 2)
|
||||||
|
self.mission.weather.wind_at_8000 = Wind(wind_direction, wind_speed * 3)
|
||||||
|
|
||||||
|
def _generate_base_weather(self):
|
||||||
|
# clouds
|
||||||
|
self.mission.weather.clouds_base = random.randint(*WEATHER_CLOUD_BASE)
|
||||||
|
self.mission.weather.clouds_density = random.randint(*WEATHER_CLOUD_DENSITY)
|
||||||
|
self.mission.weather.clouds_thickness = random.randint(*WEATHER_CLOUD_THICKNESS)
|
||||||
|
|
||||||
|
# wind
|
||||||
|
self._generate_wind(random.randint(0, 4))
|
||||||
|
|
||||||
|
# fog
|
||||||
|
if random.randint(0, 100) < WEATHER_FOG_CHANCE:
|
||||||
|
self.mission.weather.fog_visibility = random.randint(*WEATHER_FOG_VISIBILITY)
|
||||||
|
self.mission.weather.fog_thickness = random.randint(*WEATHER_FOG_THICKNESS)
|
||||||
|
|
||||||
def _gen_random_weather(self):
|
def _gen_random_weather(self):
|
||||||
weather_type = None
|
weather_type = None
|
||||||
for k, v in RANDOM_WEATHER.items():
|
for k, v in RANDOM_WEATHER.items():
|
||||||
@ -71,32 +103,33 @@ class EnviromentGenerator:
|
|||||||
|
|
||||||
logging.info("generated weather {}".format(weather_type))
|
logging.info("generated weather {}".format(weather_type))
|
||||||
if weather_type == 1:
|
if weather_type == 1:
|
||||||
self.mission.weather.heavy_rain()
|
# thunderstorm
|
||||||
elif weather_type == 2:
|
self._generate_base_weather()
|
||||||
self.mission.weather.heavy_rain()
|
self._generate_wind(random.randint(8, 12))
|
||||||
self.mission.weather.enable_fog = False
|
|
||||||
elif weather_type == 3:
|
|
||||||
self.mission.weather.random(self.mission.start_time, self.conflict.theater.terrain)
|
|
||||||
elif weather_type == 4:
|
|
||||||
pass
|
|
||||||
elif weather_type == 5:
|
|
||||||
self.mission.weather.clouds_base = random.randint(*WEATHER_CLOUD_BASE)
|
|
||||||
self.mission.weather.clouds_density = random.randint(*WEATHER_CLOUD_DENSITY)
|
|
||||||
self.mission.weather.clouds_thickness = random.randint(*WEATHER_CLOUD_THICKNESS)
|
|
||||||
|
|
||||||
wind_direction = random.randint(0, 360)
|
self.mission.weather.clouds_density = random.randint(9, 10)
|
||||||
wind_speed = random.randint(0, 13)
|
self.mission.weather.clouds_iprecptns = Weather.Preceptions.Thunderstorm
|
||||||
self.mission.weather.wind_at_ground = Wind(wind_direction, wind_speed)
|
elif weather_type == 2:
|
||||||
self.mission.weather.wind_at_2000 = Wind(wind_direction, wind_speed * 2)
|
# rain
|
||||||
self.mission.weather.wind_at_8000 = Wind(wind_direction, wind_speed * 3)
|
self._generate_base_weather()
|
||||||
|
self.mission.weather.clouds_density = random.randint(5, 8)
|
||||||
|
self.mission.weather.clouds_iprecptns = Weather.Preceptions.Rain
|
||||||
|
|
||||||
|
self._generate_wind(random.randint(4, 8))
|
||||||
|
elif weather_type == 3:
|
||||||
|
# clouds
|
||||||
|
self._generate_base_weather()
|
||||||
|
elif weather_type == 4:
|
||||||
|
# clear
|
||||||
|
pass
|
||||||
|
|
||||||
if self.mission.weather.clouds_density > 0:
|
if self.mission.weather.clouds_density > 0:
|
||||||
# sometimes clouds are randomized way too low and need to be fixed
|
# sometimes clouds are randomized way too low and need to be fixed
|
||||||
self.mission.weather.clouds_base = max(self.mission.weather.clouds_base, WEATHER_CLOUD_BASE_MIN)
|
self.mission.weather.clouds_base = max(self.mission.weather.clouds_base, WEATHER_CLOUD_BASE_MIN)
|
||||||
|
|
||||||
if self.mission.weather.wind_at_ground == 0:
|
if self.mission.weather.wind_at_ground.speed == 0:
|
||||||
# frontline smokes look silly w/o any wind
|
# frontline smokes look silly w/o any wind
|
||||||
self.mission.weather.wind_at_ground = random.randint(1, 2)
|
self._generate_wind(1)
|
||||||
|
|
||||||
def generate(self) -> EnvironmentSettings:
|
def generate(self) -> EnvironmentSettings:
|
||||||
self._gen_random_time()
|
self._gen_random_time()
|
||||||
|
|||||||
91
gen/groundobjectsgen.py
Normal file
91
gen/groundobjectsgen.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from game import db
|
||||||
|
from .conflictgen import *
|
||||||
|
from .naming import *
|
||||||
|
|
||||||
|
from dcs.mission import *
|
||||||
|
from dcs.statics import *
|
||||||
|
|
||||||
|
FARP_FRONTLINE_DISTANCE = 10000
|
||||||
|
AA_CP_MIN_DISTANCE = 40000
|
||||||
|
|
||||||
|
|
||||||
|
class GroundObjectsGenerator:
|
||||||
|
FARP_CAPACITY = 4
|
||||||
|
|
||||||
|
def __init__(self, mission: Mission, conflict: Conflict, game):
|
||||||
|
self.m = mission
|
||||||
|
self.conflict = conflict
|
||||||
|
self.game = game
|
||||||
|
|
||||||
|
def generate_farps(self, number_of_units=1) -> typing.Collection[StaticGroup]:
|
||||||
|
if self.conflict.is_vector:
|
||||||
|
center = self.conflict.center
|
||||||
|
heading = self.conflict.heading - 90
|
||||||
|
else:
|
||||||
|
center, heading = self.conflict.frontline_position(self.conflict.theater, self.conflict.from_cp, self.conflict.to_cp)
|
||||||
|
heading -= 90
|
||||||
|
|
||||||
|
position = self.conflict.find_ground_position(center.point_from_heading(heading, FARP_FRONTLINE_DISTANCE), heading)
|
||||||
|
if not position:
|
||||||
|
return
|
||||||
|
|
||||||
|
for i, _ in enumerate(range(0, number_of_units, self.FARP_CAPACITY)):
|
||||||
|
position = position.point_from_heading(0, i * 275)
|
||||||
|
|
||||||
|
yield self.m.farp(
|
||||||
|
country=self.m.country(self.game.player),
|
||||||
|
name="FARP",
|
||||||
|
position=position,
|
||||||
|
)
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
side = self.m.country(self.game.enemy)
|
||||||
|
|
||||||
|
cp = None # type: ControlPoint
|
||||||
|
if self.conflict.attackers_side.name == self.game.player:
|
||||||
|
cp = self.conflict.to_cp
|
||||||
|
else:
|
||||||
|
cp = self.conflict.from_cp
|
||||||
|
|
||||||
|
for ground_object in cp.ground_objects:
|
||||||
|
if ground_object.dcs_identifier == "AA":
|
||||||
|
if ground_object.position.distance_to_point(self.conflict.from_cp.position) < AA_CP_MIN_DISTANCE:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if ground_object.is_dead:
|
||||||
|
continue
|
||||||
|
|
||||||
|
unit_type = random.choice(self.game.commision_unit_types(cp, AirDefence))
|
||||||
|
assert unit_type is not None, "Cannot find unit type for GroundObject defense ({})!".format(cp)
|
||||||
|
|
||||||
|
group = self.m.vehicle_group(
|
||||||
|
country=side,
|
||||||
|
name=ground_object.string_identifier,
|
||||||
|
_type=unit_type,
|
||||||
|
position=ground_object.position,
|
||||||
|
heading=ground_object.heading,
|
||||||
|
)
|
||||||
|
|
||||||
|
logging.info("generated defense object identifier {} with mission id {}".format(group.name, group.id))
|
||||||
|
else:
|
||||||
|
if ground_object.dcs_identifier in warehouse_map:
|
||||||
|
static_type = warehouse_map[ground_object.dcs_identifier]
|
||||||
|
else:
|
||||||
|
static_type = fortification_map[ground_object.dcs_identifier]
|
||||||
|
|
||||||
|
if not static_type:
|
||||||
|
print("Didn't find {} in static _map(s)!".format(ground_object.dcs_identifier))
|
||||||
|
continue
|
||||||
|
|
||||||
|
group = self.m.static_group(
|
||||||
|
country=side,
|
||||||
|
name=ground_object.string_identifier,
|
||||||
|
_type=static_type,
|
||||||
|
position=ground_object.position,
|
||||||
|
heading=ground_object.heading,
|
||||||
|
dead=ground_object.is_dead,
|
||||||
|
)
|
||||||
|
|
||||||
|
logging.info("generated {}object identifier {} with mission id {}".format("dead " if ground_object.is_dead else "", group.name, group.id))
|
||||||
@ -16,7 +16,13 @@ class ShipGenerator:
|
|||||||
self.m = mission
|
self.m = mission
|
||||||
self.conflict = conflict
|
self.conflict = conflict
|
||||||
|
|
||||||
def generate_carrier(self, type: ShipType, country: str, at: Point) -> ShipGroup:
|
def generate_carrier(self, for_units: typing.Collection[UnitType], country: str, at: Point) -> ShipGroup:
|
||||||
|
type = db.find_unittype(Carriage, country)[0]
|
||||||
|
for unit_type in for_units:
|
||||||
|
if unit_type in db.CARRIER_TYPE_BY_PLANE:
|
||||||
|
type = db.CARRIER_TYPE_BY_PLANE[unit_type]
|
||||||
|
break
|
||||||
|
|
||||||
group = self.m.ship_group(
|
group = self.m.ship_group(
|
||||||
country=self.m.country(country),
|
country=self.m.country(country),
|
||||||
name=namegen.next_carrier_name(self.m.country(country)),
|
name=namegen.next_carrier_name(self.m.country(country)),
|
||||||
|
|||||||
@ -12,20 +12,23 @@ from dcs.action import *
|
|||||||
|
|
||||||
from game import db
|
from game import db
|
||||||
from theater import *
|
from theater import *
|
||||||
|
from gen.airsupportgen import AirSupportConflictGenerator
|
||||||
from gen import *
|
from gen import *
|
||||||
|
|
||||||
PUSH_TRIGGER_SIZE = 3000
|
PUSH_TRIGGER_SIZE = 3000
|
||||||
|
PUSH_TRIGGER_ACTIVATION_AGL = 25
|
||||||
|
|
||||||
REGROUP_ZONE_DISTANCE = 12000
|
REGROUP_ZONE_DISTANCE = 12000
|
||||||
REGROUP_ALT = 5000
|
REGROUP_ALT = 5000
|
||||||
|
|
||||||
TRIGGER_WAYPOINT_OFFSET = 2
|
TRIGGER_WAYPOINT_OFFSET = 2
|
||||||
TRIGGER_MIN_DISTANCE_FROM_START = 10000
|
TRIGGER_MIN_DISTANCE_FROM_START = 10000
|
||||||
TRIGGER_RADIUS_MINIMUM = 25000
|
TRIGGER_RADIUS_MINIMUM = 20000
|
||||||
|
|
||||||
TRIGGER_RADIUS_SMALL = 30000
|
TRIGGER_RADIUS_SMALL = 50000
|
||||||
TRIGGER_RADIUS_MEDIUM = 100000
|
TRIGGER_RADIUS_MEDIUM = 100000
|
||||||
TRIGGER_RADIUS_LARGE = 150000
|
TRIGGER_RADIUS_LARGE = 150000
|
||||||
|
TRIGGER_RADIUS_ALL_MAP = 3000000
|
||||||
|
|
||||||
|
|
||||||
class Silence(Option):
|
class Silence(Option):
|
||||||
@ -51,14 +54,16 @@ class TriggersGenerator:
|
|||||||
vehicle_group.late_activation = True
|
vehicle_group.late_activation = True
|
||||||
activate_by_trigger.append(vehicle_group)
|
activate_by_trigger.append(vehicle_group)
|
||||||
|
|
||||||
|
"""
|
||||||
conflict_distance = player_cp.position.distance_to_point(self.conflict.position)
|
conflict_distance = player_cp.position.distance_to_point(self.conflict.position)
|
||||||
minimum_radius = max(conflict_distance - TRIGGER_MIN_DISTANCE_FROM_START, TRIGGER_RADIUS_MINIMUM)
|
minimum_radius = max(conflict_distance - TRIGGER_MIN_DISTANCE_FROM_START, TRIGGER_RADIUS_MINIMUM)
|
||||||
if minimum_radius < 0:
|
if minimum_radius < 0:
|
||||||
minimum_radius = 0
|
minimum_radius = 0
|
||||||
|
|
||||||
result_radius = min(minimum_radius, radius)
|
result_radius = min(minimum_radius, radius)
|
||||||
|
"""
|
||||||
|
|
||||||
activation_trigger_zone = self.mission.triggers.add_triggerzone(self.conflict.position, result_radius, name="Activation zone")
|
activation_trigger_zone = self.mission.triggers.add_triggerzone(self.conflict.position, radius, name="Activation zone")
|
||||||
activation_trigger = TriggerOnce(Event.NoEvent, "Activation trigger")
|
activation_trigger = TriggerOnce(Event.NoEvent, "Activation trigger")
|
||||||
activation_trigger.add_condition(PartOfCoalitionInZone(player_coalition, activation_trigger_zone.id))
|
activation_trigger.add_condition(PartOfCoalitionInZone(player_coalition, activation_trigger_zone.id))
|
||||||
activation_trigger.add_condition(FlagIsTrue())
|
activation_trigger.add_condition(FlagIsTrue())
|
||||||
@ -72,41 +77,48 @@ class TriggersGenerator:
|
|||||||
for coalition_name, coalition in self.mission.coalition.items():
|
for coalition_name, coalition in self.mission.coalition.items():
|
||||||
for country in coalition.countries.values():
|
for country in coalition.countries.values():
|
||||||
if coalition_name == player_coalition:
|
if coalition_name == player_coalition:
|
||||||
for plane_group in country.plane_group + country.helicopter_group:
|
for group in country.plane_group + country.helicopter_group:
|
||||||
if plane_group.task == AWACS.name or plane_group.task == Refueling.name:
|
if group.task == AWACS.name or group.task == Refueling.name:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
regroup_heading = self.conflict.to_cp.position.heading_between_point(player_cp.position)
|
if player_cp.position.distance_to_point(group.position) > PUSH_TRIGGER_SIZE * 3:
|
||||||
|
continue
|
||||||
|
|
||||||
pos1 = plane_group.position.point_from_heading(regroup_heading, REGROUP_ZONE_DISTANCE)
|
push_by_trigger.append(group)
|
||||||
pos2 = plane_group.position.point_from_heading(regroup_heading, REGROUP_ZONE_DISTANCE+5000)
|
|
||||||
w1 = plane_group.add_waypoint(pos1, REGROUP_ALT)
|
|
||||||
w2 = plane_group.add_waypoint(pos2, REGROUP_ALT)
|
|
||||||
|
|
||||||
plane_group.points.remove(w1)
|
if not group.units[0].is_human():
|
||||||
plane_group.points.remove(w2)
|
regroup_heading = self.conflict.to_cp.position.heading_between_point(player_cp.position)
|
||||||
|
|
||||||
plane_group.points.insert(1, w2)
|
pos1 = group.position.point_from_heading(regroup_heading, REGROUP_ZONE_DISTANCE)
|
||||||
plane_group.points.insert(1, w1)
|
pos2 = group.position.point_from_heading(regroup_heading, REGROUP_ZONE_DISTANCE+5000)
|
||||||
|
w1 = group.add_waypoint(pos1, REGROUP_ALT)
|
||||||
|
w2 = group.add_waypoint(pos2, REGROUP_ALT)
|
||||||
|
|
||||||
w1.tasks.append(Silence(True))
|
group.points.remove(w1)
|
||||||
|
group.points.remove(w2)
|
||||||
|
|
||||||
switch_waypoint_task = ControlledTask(SwitchWaypoint(from_waypoint=3, to_waypoint=2))
|
group.points.insert(1, w2)
|
||||||
switch_waypoint_task.start_if_user_flag(1, False)
|
group.points.insert(1, w1)
|
||||||
w2.tasks.append(switch_waypoint_task)
|
|
||||||
plane_group.points[3].tasks.append(Silence(False))
|
|
||||||
|
|
||||||
plane_group.add_trigger_action(SwitchWaypoint(to_waypoint=4))
|
w1.tasks.append(Silence(True))
|
||||||
push_by_trigger.append(plane_group)
|
|
||||||
|
switch_waypoint_task = ControlledTask(SwitchWaypoint(from_waypoint=3, to_waypoint=2))
|
||||||
|
switch_waypoint_task.start_if_user_flag(1, False)
|
||||||
|
w2.tasks.append(switch_waypoint_task)
|
||||||
|
group.points[3].tasks.append(Silence(False))
|
||||||
|
|
||||||
|
group.add_trigger_action(SwitchWaypoint(to_waypoint=4))
|
||||||
|
|
||||||
push_trigger_zone = self.mission.triggers.add_triggerzone(player_cp.position, PUSH_TRIGGER_SIZE, name="Push zone")
|
|
||||||
push_trigger = TriggerOnce(Event.NoEvent, "Push trigger")
|
push_trigger = TriggerOnce(Event.NoEvent, "Push trigger")
|
||||||
|
|
||||||
for group in push_by_trigger:
|
for group in push_by_trigger:
|
||||||
push_trigger.add_condition(AllOfGroupOutsideZone(group.id, push_trigger_zone.id))
|
for unit in group.units:
|
||||||
push_trigger.add_action(AITaskPush(group.id, 1))
|
push_trigger.add_condition(UnitAltitudeHigherAGL(unit.id, PUSH_TRIGGER_ACTIVATION_AGL))
|
||||||
|
|
||||||
message_string = self.mission.string("Task force is in the air, proceed with the objective (activate waypoint 3).")
|
if not group.units[0].is_human():
|
||||||
|
push_trigger.add_action(AITaskPush(group.id, 1))
|
||||||
|
|
||||||
|
message_string = self.mission.string("Task force is in the air, proceed with the objective.")
|
||||||
push_trigger.add_action(MessageToAll(message_string, clearview=True))
|
push_trigger.add_action(MessageToAll(message_string, clearview=True))
|
||||||
push_trigger.add_action(SetFlagValue())
|
push_trigger.add_action(SetFlagValue())
|
||||||
|
|
||||||
@ -121,9 +133,9 @@ class TriggersGenerator:
|
|||||||
def _set_skill(self, player_coalition: str, enemy_coalition: str):
|
def _set_skill(self, player_coalition: str, enemy_coalition: str):
|
||||||
for coalition_name, coalition in self.mission.coalition.items():
|
for coalition_name, coalition in self.mission.coalition.items():
|
||||||
if coalition_name == player_coalition:
|
if coalition_name == player_coalition:
|
||||||
skill_level = self.game.settings.player_skill
|
skill_level = self.game.settings.player_skill, self.game.settings.player_skill
|
||||||
elif coalition_name == enemy_coalition:
|
elif coalition_name == enemy_coalition:
|
||||||
skill_level = self.game.settings.enemy_skill
|
skill_level = self.game.settings.enemy_skill, self.game.settings.enemy_vehicle_skill
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -131,10 +143,10 @@ class TriggersGenerator:
|
|||||||
for plane_group in country.plane_group:
|
for plane_group in country.plane_group:
|
||||||
for plane_unit in plane_group.units:
|
for plane_unit in plane_group.units:
|
||||||
if plane_unit.skill != Skill.Client and plane_unit.skill != Skill.Player:
|
if plane_unit.skill != Skill.Client and plane_unit.skill != Skill.Player:
|
||||||
plane_unit.skill = Skill(skill_level)
|
plane_unit.skill = Skill(skill_level[0])
|
||||||
|
|
||||||
for vehicle_group in country.vehicle_group:
|
for vehicle_group in country.vehicle_group:
|
||||||
vehicle_group.set_skill(Skill(skill_level))
|
vehicle_group.set_skill(Skill(skill_level[1]))
|
||||||
|
|
||||||
def generate(self, player_cp: ControlPoint, is_quick: bool, activation_trigger_radius: int, awacs_enabled: bool):
|
def generate(self, player_cp: ControlPoint, is_quick: bool, activation_trigger_radius: int, awacs_enabled: bool):
|
||||||
player_coalition = self.game.player == "USA" and "blue" or "red"
|
player_coalition = self.game.player == "USA" and "blue" or "red"
|
||||||
@ -146,19 +158,6 @@ class TriggersGenerator:
|
|||||||
self._set_skill(player_coalition, enemy_coalition)
|
self._set_skill(player_coalition, enemy_coalition)
|
||||||
self._set_allegiances(player_coalition, enemy_coalition)
|
self._set_allegiances(player_coalition, enemy_coalition)
|
||||||
|
|
||||||
description = ""
|
|
||||||
description += "FREQUENCIES:"
|
|
||||||
description += "\nFlight: 251 MHz AM"
|
|
||||||
description += "\nTanker: 10X/240 MHz"
|
|
||||||
|
|
||||||
if awacs_enabled:
|
|
||||||
description += "\nAWACS: 244 MHz"
|
|
||||||
|
|
||||||
if self.conflict.from_cp.is_global or self.conflict.to_cp.is_global:
|
|
||||||
description += "\nCarrier: 20X/ICLS CHAN1"
|
|
||||||
|
|
||||||
self.mission.set_description_text(description)
|
|
||||||
|
|
||||||
if not is_quick:
|
if not is_quick:
|
||||||
# TODO: waypoint parts of this should not be post-hacked but added in airgen
|
# TODO: waypoint parts of this should not be post-hacked but added in airgen
|
||||||
self._gen_activation_trigger(activation_trigger_radius, player_cp, player_coalition, enemy_coalition)
|
self._gen_activation_trigger(activation_trigger_radius, player_cp, player_coalition, enemy_coalition)
|
||||||
|
|||||||
@ -98,7 +98,14 @@ class VisualGenerator:
|
|||||||
|
|
||||||
def _generate_frontline_smokes(self):
|
def _generate_frontline_smokes(self):
|
||||||
for from_cp, to_cp in self.game.theater.conflicts():
|
for from_cp, to_cp in self.game.theater.conflicts():
|
||||||
point, heading = Conflict.frontline_position(from_cp, to_cp)
|
if from_cp.is_global or to_cp.is_global:
|
||||||
|
continue
|
||||||
|
|
||||||
|
frontline = Conflict.frontline_position(self.game.theater, from_cp, to_cp)
|
||||||
|
if not frontline:
|
||||||
|
continue
|
||||||
|
|
||||||
|
point, heading = frontline
|
||||||
plane_start = point.point_from_heading(turn_heading(heading, 90), FRONTLINE_LENGTH / 2)
|
plane_start = point.point_from_heading(turn_heading(heading, 90), FRONTLINE_LENGTH / 2)
|
||||||
|
|
||||||
for offset in range(0, FRONTLINE_LENGTH, FRONT_SMOKE_SPACING):
|
for offset in range(0, FRONTLINE_LENGTH, FRONT_SMOKE_SPACING):
|
||||||
|
|||||||
BIN
resources/cau_groundobjects.p
Normal file
BIN
resources/cau_groundobjects.p
Normal file
Binary file not shown.
Binary file not shown.
BIN
resources/groundobject_templates.p
Normal file
BIN
resources/groundobject_templates.p
Normal file
Binary file not shown.
Binary file not shown.
BIN
resources/nevlandmap.p
Normal file
BIN
resources/nevlandmap.p
Normal file
Binary file not shown.
BIN
resources/tools/cau_groundobjects.miz
Normal file
BIN
resources/tools/cau_groundobjects.miz
Normal file
Binary file not shown.
Binary file not shown.
38
resources/tools/generate_example_groundobjects.py
Normal file
38
resources/tools/generate_example_groundobjects.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import typing
|
||||||
|
|
||||||
|
from dcs.mission import *
|
||||||
|
from dcs.terrain import *
|
||||||
|
|
||||||
|
from theater.nevada import *
|
||||||
|
from theater.persiangulf import *
|
||||||
|
from theater.caucasus import *
|
||||||
|
from theater.controlpoint import *
|
||||||
|
|
||||||
|
def find_ground_location(near, theater, max, min) -> typing.Optional[Point]:
|
||||||
|
for _ in range(500):
|
||||||
|
p = near.random_point_within(max, min)
|
||||||
|
if theater.is_on_land(p):
|
||||||
|
return p
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
mission = Mission(Nevada())
|
||||||
|
theater = NevadaTheater()
|
||||||
|
|
||||||
|
for cp in theater.enemy_points():
|
||||||
|
for _ in range(0, random.randrange(3, 6)):
|
||||||
|
p = find_ground_location(cp.position, theater, 120000, 5000)
|
||||||
|
if not p:
|
||||||
|
print("Didn't find ground location for {}".format(cp))
|
||||||
|
continue
|
||||||
|
|
||||||
|
mission.flight_group_inflight(
|
||||||
|
mission.country("USA"),
|
||||||
|
"",
|
||||||
|
A_10C,
|
||||||
|
p,
|
||||||
|
10000
|
||||||
|
)
|
||||||
|
|
||||||
|
mission.save("resources/tools/ground_objects_example.miz")
|
||||||
50
resources/tools/generate_groundobject_templates.py
Normal file
50
resources/tools/generate_groundobject_templates.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import pickle
|
||||||
|
import typing
|
||||||
|
|
||||||
|
from dcs.mission import Mission
|
||||||
|
from dcs.mapping import Point
|
||||||
|
from dcs.unit import *
|
||||||
|
from dcs.statics import warehouse_map, fortification_map
|
||||||
|
|
||||||
|
|
||||||
|
def load_templates():
|
||||||
|
temp_mis = Mission()
|
||||||
|
temp_mis.load_file("resources/tools/groundobject_templates.miz")
|
||||||
|
|
||||||
|
groups = {} # type: typing.Dict[str, typing.Dict[int, typing.List[Static]]]
|
||||||
|
|
||||||
|
for static_group in temp_mis.country("USA").static_group:
|
||||||
|
for static in static_group.units:
|
||||||
|
static_name = str(static.name).split()[0]
|
||||||
|
tpl_name, tpl_idx = static_name[:-1], int(static_name[-1])
|
||||||
|
|
||||||
|
groups[tpl_name] = groups.get(tpl_name, {})
|
||||||
|
groups[tpl_name][tpl_idx] = groups[tpl_name].get(tpl_idx, [])
|
||||||
|
groups[tpl_name][tpl_idx].append(static)
|
||||||
|
|
||||||
|
tpls = {name: {idx: [] for idx in groups[name].keys()} for name in groups.keys()}
|
||||||
|
for category_name, category_groups in groups.items():
|
||||||
|
for idx, static_groups in category_groups.items():
|
||||||
|
dist = -1
|
||||||
|
a, b = None, None
|
||||||
|
for aa in static_groups:
|
||||||
|
for bb in static_groups:
|
||||||
|
if aa.position.distance_to_point(bb.position) > dist:
|
||||||
|
dist = aa.position.distance_to_point(bb.position)
|
||||||
|
a = aa
|
||||||
|
b = bb
|
||||||
|
|
||||||
|
center = a.position.point_from_heading(a.position.heading_between_point(b.position), dist / 2)
|
||||||
|
for static in static_groups:
|
||||||
|
tpls[category_name][idx].append({
|
||||||
|
"type": static.type,
|
||||||
|
"offset": Point(center.x - static.position.x, center.y - static.position.y),
|
||||||
|
"heading": static.heading,
|
||||||
|
})
|
||||||
|
|
||||||
|
tpls["aa"] = {0: [{"type": "AA", "offset": Point(0, 0), "heading": 0}]}
|
||||||
|
return tpls
|
||||||
|
|
||||||
|
|
||||||
|
with open("resources/groundobject_templates.p", "wb") as f:
|
||||||
|
pickle.dump(load_templates(), f)
|
||||||
108
resources/tools/generate_groundobjectsmap.py
Normal file
108
resources/tools/generate_groundobjectsmap.py
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import pickle
|
||||||
|
import typing
|
||||||
|
|
||||||
|
from dcs.mission import Mission
|
||||||
|
from dcs.mapping import Point
|
||||||
|
from dcs.terrain import *
|
||||||
|
from dcs.unitgroup import VehicleGroup, StaticGroup
|
||||||
|
from dcs import vehicles
|
||||||
|
from dcs.unit import *
|
||||||
|
from dcs.statics import warehouse_map, fortification_map
|
||||||
|
|
||||||
|
from game import db
|
||||||
|
from gen.groundobjectsgen import TheaterGroundObject
|
||||||
|
from theater.caucasus import CaucasusTheater
|
||||||
|
from theater.persiangulf import PersianGulfTheater
|
||||||
|
from theater.nevada import NevadaTheater
|
||||||
|
|
||||||
|
m = Mission()
|
||||||
|
m.load_file("resources/tools/cau_groundobjects.miz")
|
||||||
|
|
||||||
|
if isinstance(m.terrain, Caucasus):
|
||||||
|
theater = CaucasusTheater(load_ground_objects=False)
|
||||||
|
elif isinstance(m.terrain, PersianGulf):
|
||||||
|
theater = PersianGulfTheater(load_ground_objects=False)
|
||||||
|
elif isinstance(m.terrain, Nevada):
|
||||||
|
theater = NevadaTheater(load_ground_objects=False)
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
|
||||||
|
def closest_cp(location: Point) -> (int, float):
|
||||||
|
global theater
|
||||||
|
min_distance, min_cp = None, None
|
||||||
|
|
||||||
|
for cp in theater.controlpoints:
|
||||||
|
if not min_distance or location.distance_to_point(cp.position) < min_distance:
|
||||||
|
min_distance = location.distance_to_point(cp.position)
|
||||||
|
min_cp = cp.id
|
||||||
|
|
||||||
|
assert min_cp is not None
|
||||||
|
return min_cp
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
theater_objects = []
|
||||||
|
|
||||||
|
for group in m.country("Russia").static_group + m.country("Russia").vehicle_group:
|
||||||
|
for unit in group.units:
|
||||||
|
theater_object = TheaterGroundObject()
|
||||||
|
theater_object.object_id = len(theater_objects) + 1
|
||||||
|
|
||||||
|
theater_object.position = unit.position
|
||||||
|
theater_object.heading = unit.heading
|
||||||
|
|
||||||
|
if isinstance(unit, Vehicle) and unit.type in vehicles.AirDefence.__dict__.values():
|
||||||
|
theater_object.dcs_identifier = "AA"
|
||||||
|
else:
|
||||||
|
theater_object.dcs_identifier = unit.type
|
||||||
|
|
||||||
|
assert theater_object.dcs_identifier
|
||||||
|
assert theater_object.object_id
|
||||||
|
|
||||||
|
theater_objects.append(theater_object)
|
||||||
|
|
||||||
|
group_ids = 1
|
||||||
|
for object_a in theater_objects:
|
||||||
|
for object_b in theater_objects:
|
||||||
|
if object_a.position.distance_to_point(object_b.position) < 2000:
|
||||||
|
if object_a.group_id and object_b.group_id:
|
||||||
|
continue
|
||||||
|
elif object_a.group_id:
|
||||||
|
object_b.group_id = object_a.group_id
|
||||||
|
object_b.cp_id = object_a.cp_id
|
||||||
|
elif object_b.group_id:
|
||||||
|
object_a.group_id = object_b.group_id
|
||||||
|
object_a.cp_id = object_b.cp_id
|
||||||
|
else:
|
||||||
|
object_a.group_id = group_ids
|
||||||
|
object_b.group_id = group_ids
|
||||||
|
object_a.cp_id = closest_cp(object_a.position)
|
||||||
|
object_b.cp_id = object_a.cp_id
|
||||||
|
group_ids += 1
|
||||||
|
|
||||||
|
assert object_a.cp_id == object_b.cp_id, "Object {} and {} are placed in group with different airports!".format(object_a.string_identifier, object_b.string_identifier)
|
||||||
|
|
||||||
|
for a in theater_objects:
|
||||||
|
if not a.group_id:
|
||||||
|
a.group_id = group_ids
|
||||||
|
a.cp_id = closest_cp(a.position)
|
||||||
|
group_ids += 1
|
||||||
|
|
||||||
|
with open("resources/cau_groundobjects.p", "wb") as f:
|
||||||
|
result = {}
|
||||||
|
for theater_object in theater_objects:
|
||||||
|
assert theater_object.cp_id
|
||||||
|
assert theater_object.group_id
|
||||||
|
assert theater_object.object_id
|
||||||
|
|
||||||
|
if theater_object.cp_id not in result:
|
||||||
|
result[theater_object.cp_id] = []
|
||||||
|
result[theater_object.cp_id].append(theater_object)
|
||||||
|
|
||||||
|
print("Total {} objects".format(len(theater_objects)))
|
||||||
|
for cp_id, objects in result.items():
|
||||||
|
print("{}: total {} objects".format(m.terrain.airport_by_id(cp_id), len(objects)))
|
||||||
|
|
||||||
|
pickle.dump(result, f)
|
||||||
|
|
||||||
@ -1,14 +1,26 @@
|
|||||||
import pickle
|
import pickle
|
||||||
|
|
||||||
from dcs.mission import Mission
|
from dcs.mission import Mission
|
||||||
from dcs.terrain import PersianGulf
|
from dcs.planes import A_10C
|
||||||
|
|
||||||
m = Mission()
|
for terrain in ["cau", "gulf", "nev"]:
|
||||||
m.load_file("./gulf_terrain.miz")
|
m = Mission()
|
||||||
|
m.load_file("./{}_terrain.miz".format(terrain))
|
||||||
|
|
||||||
landmap = []
|
inclusion_zones = []
|
||||||
for plane_group in m.country("USA").plane_group:
|
exclusion_zones = []
|
||||||
landmap.append([(x.position.x, x.position.y) for x in plane_group.points])
|
for plane_group in m.country("USA").plane_group:
|
||||||
|
zone = [(x.position.x, x.position.y) for x in plane_group.points]
|
||||||
|
|
||||||
with open("../gulflandmap.p", "wb") as f:
|
if terrain == "cau" and inclusion_zones:
|
||||||
pickle.dump(landmap, f)
|
# legacy
|
||||||
|
exclusion_zones.append(zone)
|
||||||
|
else:
|
||||||
|
if plane_group.units[0].type == "F-15C":
|
||||||
|
exclusion_zones.append(zone)
|
||||||
|
else:
|
||||||
|
inclusion_zones.append(zone)
|
||||||
|
|
||||||
|
with open("../{}landmap.p".format(terrain), "wb") as f:
|
||||||
|
print(len(inclusion_zones), len(exclusion_zones))
|
||||||
|
pickle.dump((inclusion_zones, exclusion_zones), f)
|
||||||
|
|||||||
@ -1,10 +1,14 @@
|
|||||||
|
import os
|
||||||
import dcs
|
import dcs
|
||||||
|
|
||||||
from gen.aircraft import AircraftConflictGenerator
|
|
||||||
from game import db
|
from game import db
|
||||||
|
from gen.aircraft import AircraftConflictGenerator
|
||||||
|
|
||||||
|
dcs.planes.FlyingType.payload_dirs = [os.path.join(os.path.dirname(os.path.realpath(__file__)), "..\\payloads")]
|
||||||
|
|
||||||
mis = dcs.Mission(dcs.terrain.PersianGulf())
|
mis = dcs.Mission(dcs.terrain.PersianGulf())
|
||||||
pos = dcs.terrain.PersianGulf().khasab().position
|
pos = dcs.terrain.PersianGulf().khasab().position
|
||||||
airgen = AircraftConflictGenerator(mis, None)
|
airgen = AircraftConflictGenerator(mis, None, None)
|
||||||
|
|
||||||
for t, uts in db.UNIT_BY_TASK.items():
|
for t, uts in db.UNIT_BY_TASK.items():
|
||||||
if t != dcs.task.CAP and t != dcs.task.CAS:
|
if t != dcs.task.CAP and t != dcs.task.CAS:
|
||||||
@ -25,6 +29,6 @@ for t, uts in db.UNIT_BY_TASK.items():
|
|||||||
altitude=10000
|
altitude=10000
|
||||||
)
|
)
|
||||||
g.task = t.name
|
g.task = t.name
|
||||||
airgen._setup_group(g, t)
|
airgen._setup_group(g, t, 0)
|
||||||
|
|
||||||
mis.save("loadout_test.miz")
|
mis.save("loadout_test.miz")
|
||||||
|
|||||||
BIN
resources/tools/groundobject_templates.miz
Normal file
BIN
resources/tools/groundobject_templates.miz
Normal file
Binary file not shown.
Binary file not shown.
@ -16,7 +16,7 @@ IGNORED_PATHS = [
|
|||||||
"venv",
|
"venv",
|
||||||
]
|
]
|
||||||
|
|
||||||
VERSION = "1.3.3"
|
VERSION = input("version str:")
|
||||||
|
|
||||||
|
|
||||||
def _zip_dir(archieve, path):
|
def _zip_dir(archieve, path):
|
||||||
@ -43,7 +43,7 @@ def _mk_archieve():
|
|||||||
return
|
return
|
||||||
|
|
||||||
archieve = ZipFile(path, "w")
|
archieve = ZipFile(path, "w")
|
||||||
archieve.writestr("start.bat", "py.exe __init__.py \"%UserProfile%\" \"{}\"".format(VERSION))
|
archieve.writestr("start.bat", "py.exe __init__.py \"%UserProfile%\\Saved Games\" \"{}\"".format(VERSION))
|
||||||
_zip_dir(archieve, ".")
|
_zip_dir(archieve, ".")
|
||||||
os.chdir("submodules\\dcs")
|
os.chdir("submodules\\dcs")
|
||||||
_zip_dir(archieve, "dcs")
|
_zip_dir(archieve, "dcs")
|
||||||
|
|||||||
BIN
resources/tools/nev_terrain.miz
Normal file
BIN
resources/tools/nev_terrain.miz
Normal file
Binary file not shown.
BIN
resources/ui/terrain_caucasus.gif
Normal file
BIN
resources/ui/terrain_caucasus.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 852 B |
BIN
resources/ui/terrain_nevada.gif
Normal file
BIN
resources/ui/terrain_nevada.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
BIN
resources/ui/terrain_pg.gif
Normal file
BIN
resources/ui/terrain_pg.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
@ -1 +1 @@
|
|||||||
Subproject commit fae126689132d643d317252adfb03184042a0ded
|
Subproject commit 54eab60f228847f2d92e344e6885eca855354f41
|
||||||
@ -1,3 +1,3 @@
|
|||||||
from .controlpoint import *
|
from .controlpoint import *
|
||||||
from .conflicttheater import *
|
from .conflicttheater import *
|
||||||
from .base import *
|
from .base import *
|
||||||
|
|||||||
@ -3,17 +3,19 @@ import typing
|
|||||||
import math
|
import math
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
from game import db
|
|
||||||
from theater.controlpoint import ControlPoint
|
|
||||||
|
|
||||||
from dcs.planes import *
|
from dcs.planes import *
|
||||||
from dcs.vehicles import *
|
from dcs.vehicles import *
|
||||||
from dcs.task import *
|
from dcs.task import *
|
||||||
|
|
||||||
|
from game import db
|
||||||
|
|
||||||
STRENGTH_AA_ASSEMBLE_MIN = 0.2
|
STRENGTH_AA_ASSEMBLE_MIN = 0.2
|
||||||
PLANES_SCRAMBLE_MIN_BASE = 4
|
PLANES_SCRAMBLE_MIN_BASE = 2
|
||||||
PLANES_SCRAMBLE_MAX_BASE = 8
|
PLANES_SCRAMBLE_MAX_BASE = 8
|
||||||
PLANES_SCRAMBLE_FACTOR = 0.6
|
PLANES_SCRAMBLE_FACTOR = 0.3
|
||||||
|
|
||||||
|
BASE_MAX_STRENGTH = 1
|
||||||
|
BASE_MIN_STRENGTH = 0
|
||||||
|
|
||||||
|
|
||||||
class Base:
|
class Base:
|
||||||
@ -125,9 +127,11 @@ class Base:
|
|||||||
elif unit_type in self.aa:
|
elif unit_type in self.aa:
|
||||||
target_array = self.aa
|
target_array = self.aa
|
||||||
else:
|
else:
|
||||||
|
print("Base didn't find event type {}".format(unit_type))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if unit_type not in target_array:
|
if unit_type not in target_array:
|
||||||
|
print("Base didn't find event type {}".format(unit_type))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
target_array[unit_type] = max(target_array[unit_type] - count, 0)
|
target_array[unit_type] = max(target_array[unit_type] - count, 0)
|
||||||
@ -136,10 +140,10 @@ class Base:
|
|||||||
|
|
||||||
def affect_strength(self, amount):
|
def affect_strength(self, amount):
|
||||||
self.strength += amount
|
self.strength += amount
|
||||||
if self.strength > 1:
|
if self.strength > BASE_MAX_STRENGTH:
|
||||||
self.strength = 1
|
self.strength = BASE_MAX_STRENGTH
|
||||||
elif self.strength < 0:
|
elif self.strength <= 0:
|
||||||
self.strength = 0.001
|
self.strength = BASE_MIN_STRENGTH
|
||||||
|
|
||||||
def scramble_count(self, multiplier: float, task: Task = None) -> int:
|
def scramble_count(self, multiplier: float, task: Task = None) -> int:
|
||||||
if task:
|
if task:
|
||||||
|
|||||||
@ -11,13 +11,13 @@ from .base import *
|
|||||||
class CaucasusTheater(ConflictTheater):
|
class CaucasusTheater(ConflictTheater):
|
||||||
terrain = caucasus.Caucasus()
|
terrain = caucasus.Caucasus()
|
||||||
overview_image = "caumap.gif"
|
overview_image = "caumap.gif"
|
||||||
reference_points = {(-317948.32727306, 635639.37385346): (282.5, 319),
|
reference_points = {(-317948.32727306, 635639.37385346): (278.5, 319),
|
||||||
(-355692.3067714, 617269.96285781): (269, 352), }
|
(-355692.3067714, 617269.96285781): (263, 352), }
|
||||||
landmap_poly = load_poly("resources\\caulandmap.p")
|
landmap = load_landmap("resources\\caulandmap.p")
|
||||||
daytime_map = {
|
daytime_map = {
|
||||||
"dawn": (6, 9),
|
"dawn": (6, 9),
|
||||||
"day": (9, 18),
|
"day": (9, 18),
|
||||||
"dusk": (18, 21),
|
"dusk": (18, 20),
|
||||||
"night": (0, 5),
|
"night": (0, 5),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,7 +33,6 @@ class CaucasusTheater(ConflictTheater):
|
|||||||
gelendzhik = ControlPoint.from_airport(caucasus.Gelendzhik, COAST_DR_E, SIZE_BIG, 1.1)
|
gelendzhik = ControlPoint.from_airport(caucasus.Gelendzhik, COAST_DR_E, SIZE_BIG, 1.1)
|
||||||
maykop = ControlPoint.from_airport(caucasus.Maykop_Khanskaya, LAND, SIZE_LARGE, IMPORTANCE_HIGH)
|
maykop = ControlPoint.from_airport(caucasus.Maykop_Khanskaya, LAND, SIZE_LARGE, IMPORTANCE_HIGH)
|
||||||
krasnodar = ControlPoint.from_airport(caucasus.Krasnodar_Center, LAND, SIZE_LARGE, IMPORTANCE_HIGH)
|
krasnodar = ControlPoint.from_airport(caucasus.Krasnodar_Center, LAND, SIZE_LARGE, IMPORTANCE_HIGH)
|
||||||
novorossiysk = ControlPoint.from_airport(caucasus.Novorossiysk, COAST_DR_E, SIZE_BIG, 1.2)
|
|
||||||
krymsk = ControlPoint.from_airport(caucasus.Krymsk, LAND, SIZE_LARGE, 1.2)
|
krymsk = ControlPoint.from_airport(caucasus.Krymsk, LAND, SIZE_LARGE, 1.2)
|
||||||
anapa = ControlPoint.from_airport(caucasus.Anapa_Vityazevo, LAND, SIZE_LARGE, IMPORTANCE_HIGH)
|
anapa = ControlPoint.from_airport(caucasus.Anapa_Vityazevo, LAND, SIZE_LARGE, IMPORTANCE_HIGH)
|
||||||
|
|
||||||
@ -44,9 +43,12 @@ class CaucasusTheater(ConflictTheater):
|
|||||||
|
|
||||||
carrier_1 = ControlPoint.carrier("Carrier", mapping.Point(-305810.6875, 406399.1875))
|
carrier_1 = ControlPoint.carrier("Carrier", mapping.Point(-305810.6875, 406399.1875))
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, load_ground_objects=True):
|
||||||
super(CaucasusTheater, self).__init__()
|
super(CaucasusTheater, self).__init__()
|
||||||
|
|
||||||
|
self.soganlug.frontline_offset = 0.5
|
||||||
|
self.soganlug.base.strength = 1
|
||||||
|
|
||||||
self.add_controlpoint(self.soganlug, connected_to=[self.kutaisi, self.beslan])
|
self.add_controlpoint(self.soganlug, connected_to=[self.kutaisi, self.beslan])
|
||||||
self.add_controlpoint(self.beslan, connected_to=[self.soganlug, self.mozdok, self.nalchik])
|
self.add_controlpoint(self.beslan, connected_to=[self.soganlug, self.mozdok, self.nalchik])
|
||||||
self.add_controlpoint(self.nalchik, connected_to=[self.beslan, self.mozdok, self.mineralnye])
|
self.add_controlpoint(self.nalchik, connected_to=[self.beslan, self.mozdok, self.mineralnye])
|
||||||
@ -74,6 +76,6 @@ class CaucasusTheater(ConflictTheater):
|
|||||||
self.soganlug.captured = True
|
self.soganlug.captured = True
|
||||||
|
|
||||||
def add_controlpoint(self, point: ControlPoint, connected_to: typing.Collection[ControlPoint] = []):
|
def add_controlpoint(self, point: ControlPoint, connected_to: typing.Collection[ControlPoint] = []):
|
||||||
point.name = " ".join(re.split(r" |-", point.name)[:1])
|
point.name = " ".join(re.split(r"[ -]", point.name)[:1])
|
||||||
|
|
||||||
super(CaucasusTheater, self).add_controlpoint(point, connected_to=connected_to)
|
super(CaucasusTheater, self).add_controlpoint(point, connected_to=connected_to)
|
||||||
|
|||||||
@ -4,8 +4,9 @@ import itertools
|
|||||||
import dcs
|
import dcs
|
||||||
from dcs.mapping import Point
|
from dcs.mapping import Point
|
||||||
|
|
||||||
from .landmap import ray_tracing
|
from .landmap import Landmap, poly_contains
|
||||||
from .controlpoint import ControlPoint
|
from .controlpoint import ControlPoint
|
||||||
|
from .theatergroundobject import TheaterGroundObject
|
||||||
|
|
||||||
SIZE_TINY = 150
|
SIZE_TINY = 150
|
||||||
SIZE_SMALL = 600
|
SIZE_SMALL = 600
|
||||||
@ -17,6 +18,8 @@ IMPORTANCE_LOW = 1
|
|||||||
IMPORTANCE_MEDIUM = 1.2
|
IMPORTANCE_MEDIUM = 1.2
|
||||||
IMPORTANCE_HIGH = 1.4
|
IMPORTANCE_HIGH = 1.4
|
||||||
|
|
||||||
|
GLOBAL_CP_CONFLICT_DISTANCE_MIN = 340000
|
||||||
|
|
||||||
"""
|
"""
|
||||||
ALL_RADIALS = [0, 45, 90, 135, 180, 225, 270, 315, ]
|
ALL_RADIALS = [0, 45, 90, 135, 180, 225, 270, 315, ]
|
||||||
COAST_NS_E = [45, 90, 135, ]
|
COAST_NS_E = [45, 90, 135, ]
|
||||||
@ -48,9 +51,10 @@ COAST_DR_W = [135, 180, 225, 315]
|
|||||||
class ConflictTheater:
|
class ConflictTheater:
|
||||||
terrain = None # type: dcs.terrain.Terrain
|
terrain = None # type: dcs.terrain.Terrain
|
||||||
controlpoints = None # type: typing.Collection[ControlPoint]
|
controlpoints = None # type: typing.Collection[ControlPoint]
|
||||||
|
|
||||||
reference_points = None # type: typing.Dict
|
reference_points = None # type: typing.Dict
|
||||||
overview_image = None # type: str
|
overview_image = None # type: str
|
||||||
landmap_poly = None
|
landmap = None # type: landmap.Landmap
|
||||||
daytime_map = None # type: typing.Dict[str, typing.Tuple[int, int]]
|
daytime_map = None # type: typing.Dict[str, typing.Tuple[int, int]]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -62,15 +66,33 @@ class ConflictTheater:
|
|||||||
|
|
||||||
self.controlpoints.append(point)
|
self.controlpoints.append(point)
|
||||||
|
|
||||||
|
def is_in_sea(self, point: Point) -> bool:
|
||||||
|
if not self.landmap:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for inclusion_zone in self.landmap[0]:
|
||||||
|
if poly_contains(point.x, point.y, inclusion_zone):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def is_on_land(self, point: Point) -> bool:
|
def is_on_land(self, point: Point) -> bool:
|
||||||
if not self.landmap_poly:
|
if not self.landmap:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
for poly in self.landmap_poly:
|
is_point_included = False
|
||||||
if ray_tracing(point.x, point.y, poly):
|
for inclusion_zone in self.landmap[0]:
|
||||||
return True
|
if poly_contains(point.x, point.y, inclusion_zone):
|
||||||
|
is_point_included = True
|
||||||
|
|
||||||
return False
|
if not is_point_included:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for exclusion_zone in self.landmap[1]:
|
||||||
|
if poly_contains(point.x, point.y, exclusion_zone):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def player_points(self) -> typing.Collection[ControlPoint]:
|
def player_points(self) -> typing.Collection[ControlPoint]:
|
||||||
return [point for point in self.controlpoints if point.captured]
|
return [point for point in self.controlpoints if point.captured]
|
||||||
@ -80,5 +102,9 @@ class ConflictTheater:
|
|||||||
for connected_point in [x for x in cp.connected_points if x.captured != from_player]:
|
for connected_point in [x for x in cp.connected_points if x.captured != from_player]:
|
||||||
yield (cp, connected_point)
|
yield (cp, connected_point)
|
||||||
|
|
||||||
|
for global_cp in [x for x in self.controlpoints if x.is_global and x.captured == from_player]:
|
||||||
|
if global_cp.position.distance_to_point(connected_point.position) < GLOBAL_CP_CONFLICT_DISTANCE_MIN:
|
||||||
|
yield (global_cp, connected_point)
|
||||||
|
|
||||||
def enemy_points(self) -> typing.Collection[ControlPoint]:
|
def enemy_points(self) -> typing.Collection[ControlPoint]:
|
||||||
return [point for point in self.controlpoints if not point.captured]
|
return [point for point in self.controlpoints if not point.captured]
|
||||||
|
|||||||
@ -5,22 +5,33 @@ from dcs.mapping import *
|
|||||||
from dcs.country import *
|
from dcs.country import *
|
||||||
from dcs.terrain import Airport
|
from dcs.terrain import Airport
|
||||||
|
|
||||||
|
from .theatergroundobject import TheaterGroundObject
|
||||||
|
|
||||||
|
|
||||||
class ControlPoint:
|
class ControlPoint:
|
||||||
connected_points = [] # type: typing.List[ControlPoint]
|
id = 0
|
||||||
position = None # type: Point
|
position = None # type: Point
|
||||||
captured = False
|
name = None # type: str
|
||||||
has_frontline = True
|
full_name = None # type: str
|
||||||
base = None # type: theater.base.Base
|
base = None # type: theater.base.Base
|
||||||
at = None # type: db.StartPosition
|
at = None # type: db.StartPosition
|
||||||
|
|
||||||
def __init__(self, name: str, position: Point, at, radials: typing.Collection[int], size: int, importance: int, has_frontline=True):
|
connected_points = None # type: typing.List[ControlPoint]
|
||||||
|
ground_objects = None # type: typing.List[TheaterGroundObject]
|
||||||
|
|
||||||
|
captured = False
|
||||||
|
has_frontline = True
|
||||||
|
frontline_offset = 0.0
|
||||||
|
|
||||||
|
def __init__(self, id: int, name: str, position: Point, at, radials: typing.Collection[int], size: int, importance: float, has_frontline=True):
|
||||||
import theater.base
|
import theater.base
|
||||||
|
|
||||||
|
self.id = id
|
||||||
self.name = " ".join(re.split(r" |-", name)[:2])
|
self.name = " ".join(re.split(r" |-", name)[:2])
|
||||||
self.full_name = name
|
self.full_name = name
|
||||||
self.position = position
|
self.position = position
|
||||||
self.at = at
|
self.at = at
|
||||||
|
self.ground_objects = []
|
||||||
|
|
||||||
self.size = size
|
self.size = size
|
||||||
self.importance = importance
|
self.importance = importance
|
||||||
@ -31,14 +42,14 @@ class ControlPoint:
|
|||||||
self.base = theater.base.Base()
|
self.base = theater.base.Base()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_airport(cls, airport: Airport, radials: typing.Collection[int], size: int, importance: int, has_frontline=True):
|
def from_airport(cls, airport: Airport, radials: typing.Collection[int], size: int, importance: float, has_frontline=True):
|
||||||
assert airport
|
assert airport
|
||||||
return cls(airport.name, airport.position, airport, radials, size, importance, has_frontline)
|
return cls(airport.id, airport.name, airport.position, airport, radials, size, importance, has_frontline)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def carrier(cls, name: str, at: Point):
|
def carrier(cls, name: str, at: Point):
|
||||||
import theater.conflicttheater
|
import theater.conflicttheater
|
||||||
return cls(name, at, at, theater.conflicttheater.LAND, theater.conflicttheater.SIZE_SMALL, 1)
|
return cls(0, name, at, at, theater.conflicttheater.LAND, theater.conflicttheater.SIZE_SMALL, 1, has_frontline=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
import pickle
|
import pickle
|
||||||
|
import typing
|
||||||
|
|
||||||
|
Zone = typing.Collection[typing.Tuple[float, float]]
|
||||||
|
Landmap = typing.Tuple[typing.Collection[Zone], typing.Collection[Zone]]
|
||||||
|
|
||||||
|
|
||||||
def load_poly(filename: str):
|
def load_landmap(filename: str) -> Landmap:
|
||||||
try:
|
try:
|
||||||
with open(filename, "rb") as f:
|
with open(filename, "rb") as f:
|
||||||
return pickle.load(f)
|
return pickle.load(f)
|
||||||
@ -9,7 +13,7 @@ def load_poly(filename: str):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def ray_tracing(x, y, poly):
|
def poly_contains(x, y, poly):
|
||||||
n = len(poly)
|
n = len(poly)
|
||||||
inside = False
|
inside = False
|
||||||
xints = 0.0
|
xints = 0.0
|
||||||
@ -25,3 +29,11 @@ def ray_tracing(x, y, poly):
|
|||||||
inside = not inside
|
inside = not inside
|
||||||
p1x, p1y = p2x, p2y
|
p1x, p1y = p2x, p2y
|
||||||
return inside
|
return inside
|
||||||
|
|
||||||
|
def poly_centroid(poly) -> typing.Tuple[float, float]:
|
||||||
|
x_list = [vertex[0] for vertex in poly]
|
||||||
|
y_list = [vertex[1] for vertex in poly]
|
||||||
|
x = sum(x_list) / len(poly)
|
||||||
|
y = sum(y_list) / len(poly)
|
||||||
|
return (x, y)
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
from dcs.terrain import nevada
|
from dcs.terrain import nevada
|
||||||
from dcs import mapping
|
from dcs import mapping
|
||||||
|
|
||||||
|
from .landmap import *
|
||||||
from .conflicttheater import *
|
from .conflicttheater import *
|
||||||
from .base import *
|
from .base import *
|
||||||
|
|
||||||
@ -10,10 +11,11 @@ class NevadaTheater(ConflictTheater):
|
|||||||
overview_image = "nevada.gif"
|
overview_image = "nevada.gif"
|
||||||
reference_points = {(nevada.Mina_Airport_3Q0.position.x, nevada.Mina_Airport_3Q0.position.y): (45, -360),
|
reference_points = {(nevada.Mina_Airport_3Q0.position.x, nevada.Mina_Airport_3Q0.position.y): (45, -360),
|
||||||
(nevada.Laughlin_Airport.position.x, nevada.Laughlin_Airport.position.y): (440, 80), }
|
(nevada.Laughlin_Airport.position.x, nevada.Laughlin_Airport.position.y): (440, 80), }
|
||||||
|
landmap = load_landmap("resources\\nev_landmap.p")
|
||||||
daytime_map = {
|
daytime_map = {
|
||||||
"dawn": (4, 6),
|
"dawn": (4, 6),
|
||||||
"day": (6, 17),
|
"day": (6, 17),
|
||||||
"dusk": (17, 19),
|
"dusk": (17, 18),
|
||||||
"night": (0, 5),
|
"night": (0, 5),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ from dcs import mapping
|
|||||||
|
|
||||||
from .conflicttheater import *
|
from .conflicttheater import *
|
||||||
from .base import *
|
from .base import *
|
||||||
from .landmap import load_poly
|
from .landmap import load_landmap
|
||||||
|
|
||||||
|
|
||||||
class PersianGulfTheater(ConflictTheater):
|
class PersianGulfTheater(ConflictTheater):
|
||||||
@ -11,7 +11,7 @@ class PersianGulfTheater(ConflictTheater):
|
|||||||
overview_image = "persiangulf.gif"
|
overview_image = "persiangulf.gif"
|
||||||
reference_points = {(persiangulf.Sir_Abu_Nuayr.position.x, persiangulf.Sir_Abu_Nuayr.position.y): (321, 145),
|
reference_points = {(persiangulf.Sir_Abu_Nuayr.position.x, persiangulf.Sir_Abu_Nuayr.position.y): (321, 145),
|
||||||
(persiangulf.Sirri_Island.position.x, persiangulf.Sirri_Island.position.y): (347, 82), }
|
(persiangulf.Sirri_Island.position.x, persiangulf.Sirri_Island.position.y): (347, 82), }
|
||||||
landmap_poly = load_poly("resources\\gulflandmap.p")
|
landmap = load_landmap("resources\\gulflandmap.p")
|
||||||
daytime_map = {
|
daytime_map = {
|
||||||
"dawn": (6, 8),
|
"dawn": (6, 8),
|
||||||
"day": (8, 16),
|
"day": (8, 16),
|
||||||
@ -19,31 +19,32 @@ class PersianGulfTheater(ConflictTheater):
|
|||||||
"night": (0, 5),
|
"night": (0, 5),
|
||||||
}
|
}
|
||||||
|
|
||||||
al_dhafra = ControlPoint.from_airport(persiangulf.Al_Dhafra_AB, LAND, SIZE_BIG, IMPORTANCE_HIGH)
|
al_dhafra = ControlPoint.from_airport(persiangulf.Al_Dhafra_AB, LAND, SIZE_BIG, IMPORTANCE_LOW)
|
||||||
al_maktoum = ControlPoint.from_airport(persiangulf.Al_Maktoum_Intl, LAND, SIZE_BIG, IMPORTANCE_HIGH)
|
al_maktoum = ControlPoint.from_airport(persiangulf.Al_Maktoum_Intl, LAND, SIZE_BIG, IMPORTANCE_LOW)
|
||||||
al_minhad = ControlPoint.from_airport(persiangulf.Al_Minhad_AB, LAND, SIZE_REGULAR, IMPORTANCE_HIGH)
|
al_minhad = ControlPoint.from_airport(persiangulf.Al_Minhad_AB, LAND, SIZE_REGULAR, 1.1)
|
||||||
sir_abu_nuayr = ControlPoint.from_airport(persiangulf.Sir_Abu_Nuayr, [0, 330], SIZE_SMALL, 1.3, has_frontline=False)
|
sir_abu_nuayr = ControlPoint.from_airport(persiangulf.Sir_Abu_Nuayr, [0, 330], SIZE_SMALL, 1.1, has_frontline=False)
|
||||||
|
|
||||||
dubai = ControlPoint.from_airport(persiangulf.Dubai_Intl, COAST_DL_E, SIZE_LARGE, IMPORTANCE_HIGH)
|
dubai = ControlPoint.from_airport(persiangulf.Dubai_Intl, COAST_DL_E, SIZE_LARGE, IMPORTANCE_MEDIUM)
|
||||||
sharjah = ControlPoint.from_airport(persiangulf.Sharjah_Intl, LAND, SIZE_BIG, 1.3)
|
sharjah = ControlPoint.from_airport(persiangulf.Sharjah_Intl, LAND, SIZE_BIG, 1.0)
|
||||||
fujairah = ControlPoint.from_airport(persiangulf.Fujairah_Intl, COAST_V_W, SIZE_REGULAR, IMPORTANCE_HIGH)
|
fujairah = ControlPoint.from_airport(persiangulf.Fujairah_Intl, COAST_V_W, SIZE_REGULAR, 1.0)
|
||||||
khasab = ControlPoint.from_airport(persiangulf.Khasab, LAND, SIZE_SMALL, IMPORTANCE_HIGH)
|
khasab = ControlPoint.from_airport(persiangulf.Khasab, LAND, SIZE_SMALL, IMPORTANCE_MEDIUM)
|
||||||
|
|
||||||
sirri = ControlPoint.from_airport(persiangulf.Sirri_Island, COAST_DL_W, SIZE_REGULAR, IMPORTANCE_LOW, has_frontline=False)
|
sirri = ControlPoint.from_airport(persiangulf.Sirri_Island, COAST_DL_W, SIZE_REGULAR, IMPORTANCE_LOW, has_frontline=False)
|
||||||
abu_musa = ControlPoint.from_airport(persiangulf.Abu_Musa_Island_Airport, LAND, SIZE_SMALL, IMPORTANCE_MEDIUM, has_frontline=False)
|
abu_musa = ControlPoint.from_airport(persiangulf.Abu_Musa_Island_Airport, LAND, SIZE_SMALL, IMPORTANCE_MEDIUM, has_frontline=False)
|
||||||
tunb_island = ControlPoint.from_airport(persiangulf.Tunb_Island_AFB, [0, 270, 330], IMPORTANCE_LOW, 1.1, has_frontline=False)
|
tunb_island = ControlPoint.from_airport(persiangulf.Tunb_Island_AFB, [0, 270, 330], SIZE_SMALL, IMPORTANCE_MEDIUM, has_frontline=False)
|
||||||
tunb_kochak = ControlPoint.from_airport(persiangulf.Tunb_Kochak, [135, 180], SIZE_SMALL, 1.2, has_frontline=False)
|
tunb_kochak = ControlPoint.from_airport(persiangulf.Tunb_Kochak, [135, 180], SIZE_SMALL, 1.1, has_frontline=False)
|
||||||
|
|
||||||
bandar_lengeh = ControlPoint.from_airport(persiangulf.Bandar_Lengeh, [270, 315, 0, 45], SIZE_SMALL, 1.1)
|
bandar_lengeh = ControlPoint.from_airport(persiangulf.Bandar_Lengeh, [270, 315, 0, 45], SIZE_SMALL, IMPORTANCE_HIGH)
|
||||||
qeshm = ControlPoint.from_airport(persiangulf.Qeshm_Island, [270, 315, 0, 45, 90, 135, 180], SIZE_SMALL, 1.2, has_frontline=False)
|
qeshm = ControlPoint.from_airport(persiangulf.Qeshm_Island, [270, 315, 0, 45, 90, 135, 180], SIZE_SMALL, 1.1, has_frontline=False)
|
||||||
|
|
||||||
havadarya = ControlPoint.from_airport(persiangulf.Havadarya, COAST_DL_W, SIZE_REGULAR, 1.1)
|
havadarya = ControlPoint.from_airport(persiangulf.Havadarya, COAST_DL_W, SIZE_REGULAR, IMPORTANCE_HIGH)
|
||||||
bandar_abbas = ControlPoint.from_airport(persiangulf.Bandar_Abbas_Intl, LAND, SIZE_BIG, 1.2)
|
bandar_abbas = ControlPoint.from_airport(persiangulf.Bandar_Abbas_Intl, LAND, SIZE_BIG, IMPORTANCE_HIGH)
|
||||||
lar = ControlPoint.from_airport(persiangulf.Lar_Airbase, LAND, SIZE_REGULAR, IMPORTANCE_LOW)
|
lar = ControlPoint.from_airport(persiangulf.Lar_Airbase, LAND, SIZE_REGULAR, IMPORTANCE_HIGH)
|
||||||
shiraz = ControlPoint.from_airport(persiangulf.Shiraz_International_Airport, LAND, SIZE_BIG, IMPORTANCE_LOW)
|
shiraz = ControlPoint.from_airport(persiangulf.Shiraz_International_Airport, LAND, SIZE_BIG, IMPORTANCE_HIGH)
|
||||||
kerman = ControlPoint.from_airport(persiangulf.Kerman_Airport, LAND, SIZE_BIG, IMPORTANCE_LOW)
|
kerman = ControlPoint.from_airport(persiangulf.Kerman_Airport, LAND, SIZE_BIG, IMPORTANCE_HIGH)
|
||||||
|
|
||||||
west_carrier = ControlPoint.carrier("East carrier", Point(-100531.972946, 60939.275818))
|
west_carrier = ControlPoint.carrier("West carrier", Point(-69043.813952358, -159916.65947136))
|
||||||
|
east_carrier = ControlPoint.carrier("East carrier", Point(59514.324335475, 28165.517980635))
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(PersianGulfTheater, self).__init__()
|
super(PersianGulfTheater, self).__init__()
|
||||||
@ -72,17 +73,8 @@ class PersianGulfTheater(ConflictTheater):
|
|||||||
self.add_controlpoint(self.bandar_abbas, connected_to=[self.havadarya])
|
self.add_controlpoint(self.bandar_abbas, connected_to=[self.havadarya])
|
||||||
|
|
||||||
self.add_controlpoint(self.west_carrier)
|
self.add_controlpoint(self.west_carrier)
|
||||||
|
self.add_controlpoint(self.east_carrier)
|
||||||
|
|
||||||
self.west_carrier.captured = True
|
self.west_carrier.captured = True
|
||||||
self.kerman.captured = True
|
self.east_carrier.captured = True
|
||||||
|
self.al_dhafra.captured = True
|
||||||
"""
|
|
||||||
Mid game:
|
|
||||||
self.al_maktoum.captured = True
|
|
||||||
self.al_minhad.captured = True
|
|
||||||
self.dubai.captured = True
|
|
||||||
self.sharjah.captured = True
|
|
||||||
self.fujairah.captured = True
|
|
||||||
self.khasab.captured = True
|
|
||||||
self.sir_abu_nuayr.captured = True
|
|
||||||
"""
|
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
import math
|
import math
|
||||||
|
import pickle
|
||||||
|
import random
|
||||||
|
import typing
|
||||||
|
import logging
|
||||||
|
|
||||||
from theater.base import *
|
from theater.base import *
|
||||||
from theater.conflicttheater import *
|
from theater.conflicttheater import *
|
||||||
@ -15,7 +19,7 @@ COUNT_BY_TASK = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def generate_initial(theater: ConflictTheater, enemy: str, sams: bool, multiplier: float):
|
def generate_inital_units(theater: ConflictTheater, enemy: str, sams: bool, multiplier: float):
|
||||||
for cp in theater.enemy_points():
|
for cp in theater.enemy_points():
|
||||||
if cp.captured:
|
if cp.captured:
|
||||||
continue
|
continue
|
||||||
@ -37,3 +41,71 @@ def generate_initial(theater: ConflictTheater, enemy: str, sams: bool, multiplie
|
|||||||
for unit_type in unittypes:
|
for unit_type in unittypes:
|
||||||
logging.info("{} - {} {}".format(cp.name, db.unit_type_name(unit_type), count_per_type))
|
logging.info("{} - {} {}".format(cp.name, db.unit_type_name(unit_type), count_per_type))
|
||||||
cp.base.commision_units({unit_type: count_per_type})
|
cp.base.commision_units({unit_type: count_per_type})
|
||||||
|
|
||||||
|
|
||||||
|
def generate_groundobjects(theater: ConflictTheater):
|
||||||
|
with open("resources/groundobject_templates.p", "rb") as f:
|
||||||
|
tpls = pickle.load(f)
|
||||||
|
|
||||||
|
def find_location(on_ground, near, theater, min, max) -> typing.Optional[Point]:
|
||||||
|
point = None
|
||||||
|
for _ in range(1000):
|
||||||
|
p = near.random_point_within(max, min)
|
||||||
|
if on_ground and theater.is_on_land(p):
|
||||||
|
point = p
|
||||||
|
elif not on_ground and theater.is_in_sea(p):
|
||||||
|
point = p
|
||||||
|
|
||||||
|
if point:
|
||||||
|
for angle in range(0, 360, 45):
|
||||||
|
p = point.point_from_heading(angle, 2500)
|
||||||
|
if on_ground and not theater.is_on_land(p):
|
||||||
|
point = None
|
||||||
|
break
|
||||||
|
elif not on_ground and not theater.is_in_sea(p):
|
||||||
|
point = None
|
||||||
|
break
|
||||||
|
|
||||||
|
if point:
|
||||||
|
return point
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
group_id = 0
|
||||||
|
for cp in theater.enemy_points():
|
||||||
|
for _ in range(0, random.randrange(2, 4)):
|
||||||
|
available_categories = list(tpls) + ["aa", "aa"]
|
||||||
|
tpl_category = random.choice(available_categories)
|
||||||
|
|
||||||
|
tpl = random.choice(list(tpls[tpl_category].values()))
|
||||||
|
|
||||||
|
point = find_location(tpl_category != "oil", cp.position, theater, 15000, 80000)
|
||||||
|
|
||||||
|
if point is None:
|
||||||
|
print("Couldn't find point for {}".format(cp))
|
||||||
|
continue
|
||||||
|
|
||||||
|
"""
|
||||||
|
dist = point.distance_to_point(cp.position) - 15000
|
||||||
|
for another_cp in theater.enemy_points():
|
||||||
|
if another_cp.position.distance_to_point(point) < dist:
|
||||||
|
cp = another_cp
|
||||||
|
"""
|
||||||
|
|
||||||
|
group_id += 1
|
||||||
|
object_id = 0
|
||||||
|
|
||||||
|
logging.info("generated {} for {}".format(tpl_category, cp))
|
||||||
|
for object in tpl:
|
||||||
|
object_id += 1
|
||||||
|
|
||||||
|
g = TheaterGroundObject()
|
||||||
|
g.group_id = group_id
|
||||||
|
g.object_id = object_id
|
||||||
|
g.cp_id = cp.id
|
||||||
|
|
||||||
|
g.dcs_identifier = object["type"]
|
||||||
|
g.heading = object["heading"]
|
||||||
|
g.position = Point(point.x + object["offset"].x, point.y + object["offset"].y)
|
||||||
|
|
||||||
|
cp.ground_objects.append(g)
|
||||||
|
|||||||
81
theater/theatergroundobject.py
Normal file
81
theater/theatergroundobject.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import typing
|
||||||
|
|
||||||
|
from dcs.mapping import Point
|
||||||
|
from dcs.statics import *
|
||||||
|
|
||||||
|
NAME_BY_CATEGORY = {
|
||||||
|
"power": "Power plant",
|
||||||
|
"ammo": "Ammo depot",
|
||||||
|
"fuel": "Fuel depot",
|
||||||
|
"aa": "AA Defense Site",
|
||||||
|
"warehouse": "Warehouse",
|
||||||
|
"farp": "FARP",
|
||||||
|
"fob": "FOB",
|
||||||
|
"factory": "Factory",
|
||||||
|
"comms": "Comms. tower",
|
||||||
|
"oil": "Oil platform"
|
||||||
|
}
|
||||||
|
|
||||||
|
ABBREV_NAME = {
|
||||||
|
"power": "PLANT",
|
||||||
|
"ammo": "AMMO",
|
||||||
|
"fuel": "FUEL",
|
||||||
|
"aa": "AA",
|
||||||
|
"warehouse": "WARE",
|
||||||
|
"farp": "FARP",
|
||||||
|
"fob": "FOB",
|
||||||
|
"factory": "FACTORY",
|
||||||
|
"comms": "COMMST",
|
||||||
|
"oil": "OILP"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CATEGORY_MAP = {
|
||||||
|
"aa": ["AA"],
|
||||||
|
"power": ["Workshop A", "Electric power box", "Garage small A"],
|
||||||
|
"warehouse": ["Warehouse", "Hangar A"],
|
||||||
|
"fuel": ["Tank", "Tank 2", "Tank 3", "Fuel tank"],
|
||||||
|
"ammo": [".Ammunition depot", "Hangar B"],
|
||||||
|
"farp": ["FARP Tent", "FARP Ammo Dump Coating", "FARP Fuel Depot", "FARP Command Post", "FARP CP Blindage"],
|
||||||
|
"fob": ["Bunker 2", "Bunker 1", "Garage small B", ".Command Center", "Barracks 2"],
|
||||||
|
"factory": ["Tech combine", "Tech hangar A"],
|
||||||
|
"comms": ["TV tower", "Comms tower M"],
|
||||||
|
"oil": ["Oil platform"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TheaterGroundObject:
|
||||||
|
cp_id = 0
|
||||||
|
group_id = 0
|
||||||
|
object_id = 0
|
||||||
|
|
||||||
|
dcs_identifier = None # type: str
|
||||||
|
is_dead = False
|
||||||
|
|
||||||
|
heading = 0
|
||||||
|
position = None # type: Point
|
||||||
|
|
||||||
|
@property
|
||||||
|
def category(self) -> str:
|
||||||
|
for k, v in CATEGORY_MAP.items():
|
||||||
|
if self.dcs_identifier in v:
|
||||||
|
return k
|
||||||
|
assert False, "Identifier not found in mapping: {}".format(self.dcs_identifier)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def string_identifier(self):
|
||||||
|
return "{}|{}|{}|{}".format(self.category, self.cp_id, self.group_id, self.object_id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def group_identifier(self) -> str:
|
||||||
|
return "{}|{}".format(self.category, self.group_id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name_abbrev(self) -> str:
|
||||||
|
return ABBREV_NAME[self.category]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return NAME_BY_CATEGORY[self.category]
|
||||||
|
|
||||||
|
def matches_string_identifier(self, id):
|
||||||
|
return self.string_identifier == id
|
||||||
@ -1,6 +1,7 @@
|
|||||||
from ui.eventmenu import *
|
from ui.eventmenu import *
|
||||||
|
|
||||||
from game.game import *
|
from game.game import *
|
||||||
|
from .styles import STYLES
|
||||||
|
|
||||||
|
|
||||||
class BaseMenu(Menu):
|
class BaseMenu(Menu):
|
||||||
@ -9,7 +10,6 @@ class BaseMenu(Menu):
|
|||||||
|
|
||||||
def __init__(self, window: Window, parent, game: Game, cp: ControlPoint):
|
def __init__(self, window: Window, parent, game: Game, cp: ControlPoint):
|
||||||
super(BaseMenu, self).__init__(window, parent, game)
|
super(BaseMenu, self).__init__(window, parent, game)
|
||||||
|
|
||||||
self.cp = cp
|
self.cp = cp
|
||||||
self.base = cp.base
|
self.base = cp.base
|
||||||
self.frame = window.right_pane
|
self.frame = window.right_pane
|
||||||
@ -18,45 +18,60 @@ class BaseMenu(Menu):
|
|||||||
|
|
||||||
def display(self):
|
def display(self):
|
||||||
self.window.clear_right_pane()
|
self.window.clear_right_pane()
|
||||||
row = 0
|
|
||||||
|
|
||||||
def purchase_row(unit_type, unit_price):
|
|
||||||
nonlocal row
|
|
||||||
|
|
||||||
existing_units = self.base.total_units_of_type(unit_type)
|
|
||||||
scheduled_units = self.event.units.get(unit_type, 0)
|
|
||||||
|
|
||||||
Label(self.frame, text="{}".format(db.unit_type_name(unit_type))).grid(row=row, sticky=W)
|
|
||||||
|
|
||||||
label = Label(self.frame, text="({})".format(existing_units))
|
|
||||||
label.grid(column=1, row=row)
|
|
||||||
self.bought_amount_labels[unit_type] = label
|
|
||||||
|
|
||||||
Label(self.frame, text="{}m".format(unit_price)).grid(column=2, row=row)
|
|
||||||
Button(self.frame, text="+", command=self.buy(unit_type)).grid(column=3, row=row)
|
|
||||||
Button(self.frame, text="-", command=self.sell(unit_type)).grid(column=4, row=row)
|
|
||||||
row += 1
|
|
||||||
|
|
||||||
units = {
|
units = {
|
||||||
PinpointStrike: db.find_unittype(PinpointStrike, self.game.player),
|
|
||||||
Embarking: db.find_unittype(Embarking, self.game.player),
|
|
||||||
CAS: db.find_unittype(CAS, self.game.player),
|
|
||||||
CAP: db.find_unittype(CAP, self.game.player),
|
CAP: db.find_unittype(CAP, self.game.player),
|
||||||
|
Embarking: db.find_unittype(Embarking, self.game.player),
|
||||||
AirDefence: db.find_unittype(AirDefence, self.game.player),
|
AirDefence: db.find_unittype(AirDefence, self.game.player),
|
||||||
|
CAS: db.find_unittype(CAS, self.game.player),
|
||||||
|
PinpointStrike: db.find_unittype(PinpointStrike, self.game.player),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.budget_label = Label(self.frame, text="Budget: {}m".format(self.game.budget))
|
# Header
|
||||||
self.budget_label.grid(row=row, sticky=W)
|
head = Frame(self.frame, **STYLES["header"])
|
||||||
Button(self.frame, text="Back", command=self.dismiss).grid(column=4, row=row)
|
head.grid(row=0, column=0, columnspan=99, sticky=NSEW, pady=5)
|
||||||
row += 1
|
Label(head, text=self.cp.name, **STYLES["title"]).grid(row=0, column=0, sticky=NW+S)
|
||||||
|
units_title = "{}/{}/{}".format(self.cp.base.total_planes, self.cp.base.total_armor, self.cp.base.total_aa)
|
||||||
|
Label(head, text=units_title, **STYLES["strong-grey"]).grid(row=0, column=1, sticky=NE+S)
|
||||||
|
|
||||||
for task_type, units in units.items():
|
self.budget_label = Label(self.frame, text="Budget: {}m".format(self.game.budget), **STYLES["widget"])
|
||||||
Label(self.frame, text="{}".format(db.task_name(task_type))).grid(row=row, columnspan=5); row += 1
|
self.budget_label.grid(row=1, sticky=W)
|
||||||
|
Button(self.frame, text="Back", command=self.dismiss, **STYLES["btn-primary"]).grid(column=9, row=1, padx=(0,15), pady=(0,5))
|
||||||
|
|
||||||
units = list(set(units))
|
tasks = list(units.keys())
|
||||||
units.sort(key=lambda x: db.PRICES[x])
|
tasks_per_column = 3
|
||||||
for unit_type in units:
|
|
||||||
purchase_row(unit_type, db.PRICES[unit_type])
|
column = 0
|
||||||
|
for i, tasks_column in [(i, tasks[idx:idx+tasks_per_column]) for i, idx in enumerate(range(0, len(tasks), tasks_per_column))]:
|
||||||
|
row = 2
|
||||||
|
|
||||||
|
def purchase_row(unit_type, unit_price):
|
||||||
|
nonlocal row
|
||||||
|
nonlocal column
|
||||||
|
|
||||||
|
existing_units = self.base.total_units_of_type(unit_type)
|
||||||
|
scheduled_units = self.event.units.get(unit_type, 0)
|
||||||
|
|
||||||
|
Label(self.frame, text="{}".format(db.unit_type_name(unit_type)), **STYLES["widget"]).grid(row=row, column=column, sticky=W)
|
||||||
|
|
||||||
|
label = Label(self.frame, text="({}) ".format(existing_units), **STYLES["widget"])
|
||||||
|
label.grid(column=column + 1, row=row)
|
||||||
|
self.bought_amount_labels[unit_type] = label
|
||||||
|
|
||||||
|
Label(self.frame, text="{}m".format(unit_price), **STYLES["widget"]).grid(column=column + 2, row=row, sticky=E)
|
||||||
|
Button(self.frame, text="+", command=self.buy(unit_type), **STYLES["btn-primary"]).grid(column=column + 3, row=row, padx=(10,0))
|
||||||
|
Button(self.frame, text="-", command=self.sell(unit_type), **STYLES["btn-warning"]).grid(column=column + 4, row=row, padx=(10,5))
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
for task_type in tasks_column:
|
||||||
|
Label(self.frame, text="{}".format(db.task_name(task_type)), **STYLES["strong"]).grid(row=row, column=column, columnspan=5, sticky=NSEW)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
units_column = list(set(units[task_type]))
|
||||||
|
units_column.sort(key=lambda x: db.PRICES[x])
|
||||||
|
for unit_type in units_column:
|
||||||
|
purchase_row(unit_type, db.PRICES[unit_type])
|
||||||
|
|
||||||
|
column += 5
|
||||||
|
|
||||||
def dismiss(self):
|
def dismiss(self):
|
||||||
if sum([x for x in self.event.units.values()]) == 0:
|
if sum([x for x in self.event.units.values()]) == 0:
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
|
import webbrowser
|
||||||
|
|
||||||
from tkinter import *
|
from tkinter import *
|
||||||
from tkinter.ttk import *
|
from tkinter.ttk import *
|
||||||
|
from .styles import STYLES
|
||||||
|
|
||||||
|
from userdata.logging import ShowLogsException
|
||||||
from ui.window import *
|
from ui.window import *
|
||||||
|
|
||||||
|
|
||||||
@ -14,6 +18,9 @@ class ConfigurationMenu(Menu):
|
|||||||
self.enemy_skill_var = StringVar()
|
self.enemy_skill_var = StringVar()
|
||||||
self.enemy_skill_var.set(self.game.settings.enemy_skill)
|
self.enemy_skill_var.set(self.game.settings.enemy_skill)
|
||||||
|
|
||||||
|
self.enemy_vehicle_var = StringVar()
|
||||||
|
self.enemy_vehicle_var.set(self.game.settings.enemy_vehicle_skill)
|
||||||
|
|
||||||
self.takeoff_var = BooleanVar()
|
self.takeoff_var = BooleanVar()
|
||||||
self.takeoff_var.set(self.game.settings.only_player_takeoff)
|
self.takeoff_var.set(self.game.settings.only_player_takeoff)
|
||||||
|
|
||||||
@ -26,6 +33,7 @@ class ConfigurationMenu(Menu):
|
|||||||
def dismiss(self):
|
def dismiss(self):
|
||||||
self.game.settings.player_skill = self.player_skill_var.get()
|
self.game.settings.player_skill = self.player_skill_var.get()
|
||||||
self.game.settings.enemy_skill = self.enemy_skill_var.get()
|
self.game.settings.enemy_skill = self.enemy_skill_var.get()
|
||||||
|
self.game.settings.enemy_vehicle_skill = self.enemy_vehicle_var.get()
|
||||||
self.game.settings.only_player_takeoff = self.takeoff_var.get()
|
self.game.settings.only_player_takeoff = self.takeoff_var.get()
|
||||||
self.game.settings.night_disabled = self.night_var.get()
|
self.game.settings.night_disabled = self.night_var.get()
|
||||||
self.game.settings.cold_start = self.cold_start_var.get()
|
self.game.settings.cold_start = self.cold_start_var.get()
|
||||||
@ -34,22 +42,66 @@ class ConfigurationMenu(Menu):
|
|||||||
def display(self):
|
def display(self):
|
||||||
self.window.clear_right_pane()
|
self.window.clear_right_pane()
|
||||||
|
|
||||||
Label(self.frame, text="Player coalition skill").grid(row=0, column=0)
|
# Header
|
||||||
Label(self.frame, text="Enemy coalition skill").grid(row=1, column=0)
|
head = Frame(self.frame, **STYLES["header"])
|
||||||
|
head.grid(row=0, column=0, sticky=NSEW)
|
||||||
|
head.grid_columnconfigure(0, weight=100)
|
||||||
|
Label(head, text="Configuration", **STYLES["title"]).grid(row=0, sticky=W)
|
||||||
|
Button(head, text="Back", command=self.dismiss, **STYLES["btn-primary"]).grid(row=0, column=1, sticky=E)
|
||||||
|
|
||||||
OptionMenu(self.frame, self.player_skill_var, "Average", "Good", "High", "Excellent").grid(row=0, column=1)
|
# Body
|
||||||
OptionMenu(self.frame, self.enemy_skill_var, "Average", "Good", "High", "Excellent").grid(row=1, column=1)
|
body = Frame(self.frame, **STYLES["body"])
|
||||||
|
body.grid(row=1, column=0, sticky=NSEW)
|
||||||
|
row = 0
|
||||||
|
|
||||||
Label(self.frame, text="Aircraft cold start").grid(row=2, column=0)
|
Label(body, text="Player coalition skill", **STYLES["widget"]).grid(row=row, column=0, sticky=W)
|
||||||
Label(self.frame, text="Takeoff only for player group").grid(row=3, column=0)
|
p_skill = OptionMenu(body, self.player_skill_var, "Average", "Good", "High", "Excellent")
|
||||||
Label(self.frame, text="Disable night missions").grid(row=4, column=0)
|
p_skill.grid(row=row, column=1, sticky=E, pady=5)
|
||||||
|
p_skill.configure(**STYLES["btn-primary"])
|
||||||
|
row += 1
|
||||||
|
|
||||||
Checkbutton(self.frame, variable=self.cold_start_var).grid(row=2, column=1)
|
Label(body, text="Enemy coalition skill", **STYLES["widget"]).grid(row=row, column=0, sticky=W)
|
||||||
Checkbutton(self.frame, variable=self.takeoff_var).grid(row=3, column=1)
|
e_skill = OptionMenu(body, self.enemy_skill_var, "Average", "Good", "High", "Excellent")
|
||||||
Checkbutton(self.frame, variable=self.night_var).grid(row=4, column=1)
|
e_skill.grid(row=row, column=1, sticky=E)
|
||||||
|
e_skill.configure(**STYLES["btn-primary"])
|
||||||
|
row += 1
|
||||||
|
|
||||||
Button(self.frame, text="Back", command=self.dismiss).grid(row=5, column=0, columnspan=1)
|
Label(body, text="Enemy AA and vehicle skill", **STYLES["widget"]).grid(row=row, column=0, sticky=W)
|
||||||
Button(self.frame, text="Cheat +200m", command=self.cheat_money).grid(row=6, column=1)
|
e_skill = OptionMenu(body, self.enemy_vehicle_var, "Average", "Good", "High", "Excellent")
|
||||||
|
e_skill.grid(row=row, column=1, sticky=E)
|
||||||
|
e_skill.configure(**STYLES["btn-primary"])
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
Label(body, text="Aircraft cold start", **STYLES["widget"]).grid(row=row, column=0, sticky=W)
|
||||||
|
Checkbutton(body, variable=self.cold_start_var, **STYLES["radiobutton"]).grid(row=row, column=1, sticky=E)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
Label(body, text="Takeoff only for player group", **STYLES["widget"]).grid(row=row, column=0, sticky=W)
|
||||||
|
Checkbutton(body, variable=self.takeoff_var, **STYLES["radiobutton"]).grid(row=row, column=1, sticky=E)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
Label(body, text="Disable night missions", **STYLES["widget"]).grid(row=row, column=0, sticky=W)
|
||||||
|
Checkbutton(body, variable=self.night_var, **STYLES["radiobutton"]).grid(row=row, column=1, sticky=E)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
Button(body, text="Display logs", command=self.display_logs, **STYLES["btn-primary"]).grid(row=row, column=1, sticky=E, pady=30)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
Label(body, text="Contributors: ", **STYLES["strong"]).grid(row=row, column=0, columnspan=2, sticky=EW)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
Label(body, text="shdwp - author, maintainer", **STYLES["widget"]).grid(row=row, column=0, sticky=W)
|
||||||
|
Button(body, text="[github]", command=lambda: webbrowser.open_new_tab("http://github.com/shdwp"), **STYLES["widget"]).grid(row=row, column=1, sticky=E)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
Label(body, text="Khopa - contributions", **STYLES["widget"]).grid(row=row, column=0, sticky=W)
|
||||||
|
Button(body, text="[github]", command=lambda: webbrowser.open_new_tab("http://github.com/Khopa"), **STYLES["widget"]).grid(row=row, column=1, sticky=E)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
Button(body, text="Cheat +200m", command=self.cheat_money, **STYLES["btn-danger"]).grid(row=row, column=1, pady=30)
|
||||||
|
|
||||||
|
def display_logs(self):
|
||||||
|
raise ShowLogsException()
|
||||||
|
|
||||||
def cheat_money(self):
|
def cheat_money(self):
|
||||||
self.game.budget += 200
|
self.game.budget += 200
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from tkinter import *
|
from tkinter import *
|
||||||
from tkinter.ttk import *
|
from tkinter.ttk import *
|
||||||
|
from .styles import STYLES
|
||||||
|
|
||||||
from ui.window import *
|
from ui.window import *
|
||||||
|
|
||||||
@ -12,6 +13,6 @@ class CorruptedSaveMenu(Menu):
|
|||||||
def display(self):
|
def display(self):
|
||||||
self.window.clear_right_pane()
|
self.window.clear_right_pane()
|
||||||
|
|
||||||
Label(text="Your save game was corrupted!").grid(row=0, column=0)
|
Label(text="Your save game is either incompatible or was corrupted!", **STYLES["widget"]).grid(row=0, column=0)
|
||||||
Label(text="Please restore it by replacing \"liberation_save\" file with \"liberation_save_tmp\" to restore last saved copy.").grid(row=1, column=0)
|
Label(text="Please restore it by replacing \"liberation_save\" file with \"liberation_save_tmp\" to restore last saved copy.", **STYLES["widget"]).grid(row=1, column=0)
|
||||||
Label(text="You can find those files under user DCS directory.").grid(row=2, column=0)
|
Label(text="You can find those files under user Saved Games\\DCS directory.", **STYLES["widget"]).grid(row=2, column=0)
|
||||||
|
|||||||
327
ui/eventmenu.py
327
ui/eventmenu.py
@ -4,33 +4,20 @@ from ui.eventresultsmenu import *
|
|||||||
|
|
||||||
from game import *
|
from game import *
|
||||||
from game.event import *
|
from game.event import *
|
||||||
|
from .styles import STYLES, RED
|
||||||
|
|
||||||
UNITTYPES_FOR_EVENTS = {
|
|
||||||
FrontlineAttackEvent: [CAS, PinpointStrike],
|
|
||||||
FrontlinePatrolEvent: [CAP, PinpointStrike],
|
|
||||||
BaseAttackEvent: [CAP, CAS, PinpointStrike],
|
|
||||||
InterceptEvent: [CAP],
|
|
||||||
InsurgentAttackEvent: [CAS],
|
|
||||||
NavalInterceptEvent: [CAS],
|
|
||||||
AntiAAStrikeEvent: [CAS],
|
|
||||||
InfantryTransportEvent: [Embarking],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class EventMenu(Menu):
|
class EventMenu(Menu):
|
||||||
aircraft_scramble_entries = None # type: typing.Dict[PlaneType , Entry]
|
scramble_entries = None # type: typing.Dict[typing.Type[Task], typing.Dict[typing.Type[UnitType], typing.Tuple[Entry, Entry]]]
|
||||||
aircraft_client_entries = None # type: typing.Dict[PlaneType, Entry]
|
ca_slot_entry = None # type: Entry
|
||||||
armor_scramble_entries = None # type: typing.Dict[VehicleType, Entry]
|
error_label = None # type: Label
|
||||||
awacs = None # type: IntVar
|
awacs = None # type: IntVar
|
||||||
|
|
||||||
def __init__(self, window: Window, parent, game: Game, event: event.Event):
|
def __init__(self, window: Window, parent, game: Game, event: event.Event):
|
||||||
super(EventMenu, self).__init__(window, parent, game)
|
super(EventMenu, self).__init__(window, parent, game)
|
||||||
|
|
||||||
self.event = event
|
self.event = event
|
||||||
self.aircraft_scramble_entries = {}
|
self.scramble_entries = {k: {} for k in self.event.tasks}
|
||||||
self.armor_scramble_entries = {}
|
|
||||||
self.aircraft_client_entries = {}
|
|
||||||
|
|
||||||
if self.event.attacker_name == self.game.player:
|
if self.event.attacker_name == self.game.player:
|
||||||
self.base = self.event.from_cp.base
|
self.base = self.event.from_cp.base
|
||||||
@ -44,118 +31,111 @@ class EventMenu(Menu):
|
|||||||
self.window.clear_right_pane()
|
self.window.clear_right_pane()
|
||||||
row = 0
|
row = 0
|
||||||
|
|
||||||
def label(text, _row=None, _column=None, sticky=None):
|
def header(text, style="strong"):
|
||||||
nonlocal row
|
nonlocal row
|
||||||
Label(self.frame, text=text).grid(row=_row and _row or row, column=_column and _column or 0, sticky=sticky)
|
head = Frame(self.frame, **STYLES["header"])
|
||||||
|
head.grid(row=row, column=0, sticky=N+EW, columnspan=5)
|
||||||
|
Label(head, text=text, **STYLES[style]).grid()
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
def label(text, _row=None, _column=None, columnspan=None, sticky=None):
|
||||||
|
nonlocal row
|
||||||
|
new_label = Label(self.frame, text=text, **STYLES["widget"])
|
||||||
|
new_label.grid(row=_row and _row or row, column=_column and _column or 0, columnspan=columnspan, sticky=sticky)
|
||||||
|
|
||||||
if _row is None:
|
if _row is None:
|
||||||
row += 1
|
row += 1
|
||||||
|
|
||||||
def scrable_row(unit_type, unit_count):
|
return new_label
|
||||||
|
|
||||||
|
def scrable_row(task_type, unit_type, unit_count, client_slots: bool):
|
||||||
nonlocal row
|
nonlocal row
|
||||||
Label(self.frame, text="{} ({})".format(db.unit_type_name(unit_type), unit_count)).grid(row=row, sticky=W)
|
Label(self.frame, text="{} ({})".format(db.unit_type_name(unit_type), unit_count), **STYLES["widget"]).grid(row=row, sticky=W)
|
||||||
|
|
||||||
scramble_entry = Entry(self.frame, width=2)
|
scramble_entry = Entry(self.frame, width=2)
|
||||||
scramble_entry.grid(column=1, row=row, sticky=W)
|
scramble_entry.grid(column=1, row=row, sticky=E, padx=5)
|
||||||
scramble_entry.insert(0, "0")
|
scramble_entry.insert(0, "0")
|
||||||
self.aircraft_scramble_entries[unit_type] = scramble_entry
|
Button(self.frame, text="+", command=self.scramble_half(task_type, unit_type), **STYLES["btn-primary"]).grid(column=2, row=row)
|
||||||
Button(self.frame, text="+", command=self.scramble_half(True, unit_type)).grid(column=2, row=row)
|
|
||||||
|
|
||||||
client_entry = Entry(self.frame, width=2)
|
if client_slots:
|
||||||
client_entry.grid(column=3, row=row, sticky=E)
|
client_entry = Entry(self.frame, width=2)
|
||||||
client_entry.insert(0, "0")
|
client_entry.grid(column=3, row=row, sticky=E, padx=5)
|
||||||
self.aircraft_client_entries[unit_type] = client_entry
|
client_entry.insert(0, "0")
|
||||||
Button(self.frame, text="+", command=self.client_one(unit_type)).grid(column=4, row=row)
|
Button(self.frame, text="+", command=self.client_one(task_type, unit_type), **STYLES["btn-primary"]).grid(column=4, row=row)
|
||||||
|
|
||||||
row += 1
|
|
||||||
|
|
||||||
def scramble_armor_row(unit_type, unit_count):
|
|
||||||
nonlocal row
|
|
||||||
Label(self.frame, text="{} ({})".format(db.unit_type_name(unit_type), unit_count)).grid(row=row, sticky=W)
|
|
||||||
scramble_entry = Entry(self.frame, width=2)
|
|
||||||
scramble_entry.insert(0, "0")
|
|
||||||
scramble_entry.grid(column=1, row=row)
|
|
||||||
self.armor_scramble_entries[unit_type] = scramble_entry
|
|
||||||
Button(self.frame, text="+", command=self.scramble_half(False, unit_type)).grid(column=2, row=row)
|
|
||||||
|
|
||||||
row += 1
|
|
||||||
|
|
||||||
threat_descr = self.event.threat_description
|
|
||||||
if threat_descr:
|
|
||||||
threat_descr = "Approx. {}".format(threat_descr)
|
|
||||||
|
|
||||||
Label(self.frame, text="{}. {}".format(self.event, threat_descr)).grid(row=row, column=0, columnspan=5)
|
|
||||||
row += 1
|
|
||||||
|
|
||||||
Button(self.frame, text="Commit", command=self.start).grid(column=3, row=row, sticky=E)
|
|
||||||
Button(self.frame, text="Back", command=self.dismiss).grid(column=4, row=row, sticky=E)
|
|
||||||
|
|
||||||
awacs_enabled = self.game.budget >= AWACS_BUDGET_COST and NORMAL or DISABLED
|
|
||||||
Checkbutton(self.frame, text="AWACS ({}m)".format(AWACS_BUDGET_COST), var=self.awacs, state=awacs_enabled).grid(row=row, column=0, sticky=W)
|
|
||||||
row += 1
|
|
||||||
|
|
||||||
label("Aircraft")
|
|
||||||
|
|
||||||
if self.base.aircraft:
|
|
||||||
Label(self.frame, text="Amount").grid(row=row, column=1, columnspan=2)
|
|
||||||
Label(self.frame, text="Client slots").grid(row=row, column=3, columnspan=2)
|
|
||||||
row += 1
|
|
||||||
|
|
||||||
filter_to = UNITTYPES_FOR_EVENTS[self.event.__class__]
|
|
||||||
for unit_type, count in self.base.aircraft.items():
|
|
||||||
if filter_to and db.unit_task(unit_type) not in filter_to:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if unit_type in helicopter_map and self.event.__class__ != InsurgentAttackEvent:
|
|
||||||
continue
|
|
||||||
|
|
||||||
scrable_row(unit_type, count)
|
|
||||||
|
|
||||||
if not self.base.total_planes:
|
|
||||||
label("None", sticky=W)
|
|
||||||
|
|
||||||
label("Armor")
|
|
||||||
for unit_type, count in self.base.armor.items():
|
|
||||||
if filter_to and db.unit_task(unit_type) not in filter_to:
|
|
||||||
continue
|
|
||||||
|
|
||||||
scramble_armor_row(unit_type, count)
|
|
||||||
|
|
||||||
if not self.base.total_armor:
|
|
||||||
label("None", sticky=W)
|
|
||||||
|
|
||||||
def _scrambled_aircraft_count(self, unit_type: UnitType) -> int:
|
|
||||||
value = self.aircraft_scramble_entries[unit_type].get()
|
|
||||||
if value and int(value) > 0:
|
|
||||||
return min(int(value), self.base.aircraft[unit_type])
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def _scrambled_armor_count(self, unit_type: UnitType) -> int:
|
|
||||||
value = self.armor_scramble_entries[unit_type].get()
|
|
||||||
if value and int(value) > 0:
|
|
||||||
return min(int(value), self.base.armor[unit_type])
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def scramble_half(self, aircraft: bool, unit_type: UnitType) -> typing.Callable:
|
|
||||||
def action():
|
|
||||||
entry = None # type: Entry
|
|
||||||
total_count = 0
|
|
||||||
if aircraft:
|
|
||||||
entry = self.aircraft_scramble_entries[unit_type]
|
|
||||||
total_count = self.base.aircraft[unit_type]
|
|
||||||
else:
|
else:
|
||||||
entry = self.armor_scramble_entries[unit_type]
|
client_entry = None
|
||||||
total_count = self.base.armor[unit_type]
|
|
||||||
|
|
||||||
existing_count = int(entry.get())
|
self.scramble_entries[task_type][unit_type] = scramble_entry, client_entry
|
||||||
|
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# Header
|
||||||
|
header("Mission Menu", "title")
|
||||||
|
|
||||||
|
# Mission Description
|
||||||
|
Label(self.frame, text="{}".format(self.event), **STYLES["mission-preview"]).grid(row=row, column=0, columnspan=5, sticky=S+EW, padx=5, pady=5)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
Label(self.frame, text="Amount", **STYLES["widget"]).grid(row=row, column=1, columnspan=2)
|
||||||
|
Label(self.frame, text="Client slots", **STYLES["widget"]).grid(row=row, column=3, columnspan=2)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
for flight_task in self.event.tasks:
|
||||||
|
header("{}:".format(self.event.flight_name(flight_task)))
|
||||||
|
if flight_task == PinpointStrike:
|
||||||
|
if not self.base.armor:
|
||||||
|
label("No units")
|
||||||
|
for t, c in self.base.armor.items():
|
||||||
|
scrable_row(flight_task, t, c, client_slots=False)
|
||||||
|
else:
|
||||||
|
if not self.base.aircraft:
|
||||||
|
label("No units")
|
||||||
|
for t, c in self.base.aircraft.items():
|
||||||
|
scrable_row(flight_task, t, c, client_slots=True)
|
||||||
|
|
||||||
|
header("Support:")
|
||||||
|
# Options
|
||||||
|
awacs_enabled = self.game.budget >= AWACS_BUDGET_COST and NORMAL or DISABLED
|
||||||
|
Label(self.frame, text="AWACS ({}m)".format(AWACS_BUDGET_COST), **STYLES["widget"]).grid(row=row, column=0, sticky=W, pady=5)
|
||||||
|
Checkbutton(self.frame, var=self.awacs, state=awacs_enabled, **STYLES["radiobutton"]).grid(row=row, column=4, sticky=E)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
Label(self.frame, text="Combined Arms Slots", **STYLES["widget"]).grid(row=row, sticky=W)
|
||||||
|
self.ca_slot_entry = Entry(self.frame, width=2)
|
||||||
|
self.ca_slot_entry.insert(0, "0")
|
||||||
|
self.ca_slot_entry.grid(column=3, row=row, sticky=E, padx=5)
|
||||||
|
Button(self.frame, text="+", command=self.add_ca_slot, **STYLES["btn-primary"]).grid(column=4, row=row, padx=5, sticky=W)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
header("Ready?")
|
||||||
|
self.error_label = label("", columnspan=4)
|
||||||
|
self.error_label["fg"] = RED
|
||||||
|
Button(self.frame, text="Commit", command=self.start, **STYLES["btn-primary"]).grid(column=0, row=row, sticky=E, padx=5, pady=(10,10))
|
||||||
|
Button(self.frame, text="Back", command=self.dismiss, **STYLES["btn-warning"]).grid(column=3, row=row, sticky=E, padx=5, pady=(10,10))
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
def scramble_half(self, task: typing.Type[UnitType], unit_type: UnitType) -> typing.Callable:
|
||||||
|
def action():
|
||||||
|
entry = self.scramble_entries[task][unit_type][0] # type: Entry
|
||||||
|
value = entry.get()
|
||||||
|
|
||||||
|
total_units = self.base.total_units_of_type(unit_type)
|
||||||
|
|
||||||
|
amount = int(value and value or "0")
|
||||||
entry.delete(0, END)
|
entry.delete(0, END)
|
||||||
entry.insert(0, "{}".format(int(existing_count + math.ceil(total_count/2))))
|
entry.insert(0, str(amount + int(math.ceil(total_units/2))))
|
||||||
|
|
||||||
return action
|
return action
|
||||||
|
|
||||||
def client_one(self, unit_type: UnitType) -> typing.Callable:
|
def add_ca_slot(self):
|
||||||
|
value = self.ca_slot_entry.get()
|
||||||
|
amount = int(value and value or "0")
|
||||||
|
self.ca_slot_entry.delete(0, END)
|
||||||
|
self.ca_slot_entry.insert(0, str(amount+1))
|
||||||
|
|
||||||
|
def client_one(self, task: typing.Type[Task], unit_type: UnitType) -> typing.Callable:
|
||||||
def action():
|
def action():
|
||||||
entry = self.aircraft_client_entries[unit_type] # type: Entry
|
entry = self.scramble_entries[task][unit_type][1] # type: Entry
|
||||||
value = entry.get()
|
value = entry.get()
|
||||||
amount = int(value and value or "0")
|
amount = int(value and value or "0")
|
||||||
entry.delete(0, END)
|
entry.delete(0, END)
|
||||||
@ -169,82 +149,61 @@ class EventMenu(Menu):
|
|||||||
else:
|
else:
|
||||||
self.event.is_awacs_enabled = False
|
self.event.is_awacs_enabled = False
|
||||||
|
|
||||||
scrambled_aircraft = {}
|
ca_slot_entry_value = self.ca_slot_entry.get()
|
||||||
scrambled_sweep = {}
|
try:
|
||||||
scrambled_cas = {}
|
ca_slots = int(ca_slot_entry_value and ca_slot_entry_value or "0")
|
||||||
for unit_type, field in self.aircraft_scramble_entries.items():
|
except:
|
||||||
amount = self._scrambled_aircraft_count(unit_type)
|
ca_slots = 0
|
||||||
if amount > 0:
|
self.event.ca_slots = ca_slots
|
||||||
task = db.unit_task(unit_type)
|
|
||||||
|
|
||||||
scrambled_aircraft[unit_type] = amount
|
flights = {k: {} for k in self.event.tasks} # type: db.TaskForceDict
|
||||||
if task == CAS:
|
units_scramble_counts = {} # type: typing.Dict[typing.Type[UnitType], int]
|
||||||
scrambled_cas[unit_type] = amount
|
tasks_scramble_counts = {} # type: typing.Dict[typing.Type[Task], int]
|
||||||
elif task == CAP:
|
tasks_clients_counts = {} # type: typing.Dict[typing.Type[Task], int]
|
||||||
scrambled_sweep[unit_type] = amount
|
|
||||||
|
|
||||||
scrambled_clients = {}
|
def dampen_count(for_task: typing.Type[Task], unit_type: typing.Type[UnitType], count: int) -> int:
|
||||||
for unit_type, field in self.aircraft_client_entries.items():
|
nonlocal units_scramble_counts
|
||||||
value = field.get()
|
total_count = self.base.total_units_of_type(unit_type)
|
||||||
if value and int(value) > 0:
|
|
||||||
amount = int(value)
|
|
||||||
scrambled_clients[unit_type] = amount
|
|
||||||
|
|
||||||
scrambled_armor = {}
|
total_scrambled = units_scramble_counts.get(unit_type, 0)
|
||||||
for unit_type, field in self.armor_scramble_entries.items():
|
dampened_value = count if count + total_scrambled < total_count else total_count - total_scrambled
|
||||||
amount = self._scrambled_armor_count(unit_type)
|
units_scramble_counts[unit_type] = units_scramble_counts.get(unit_type, 0) + dampened_value
|
||||||
if amount > 0:
|
|
||||||
scrambled_armor[unit_type] = amount
|
|
||||||
|
|
||||||
if type(self.event) is BaseAttackEvent:
|
return dampened_value
|
||||||
e = self.event # type: BaseAttackEvent
|
|
||||||
if self.game.is_player_attack(self.event):
|
|
||||||
e.player_attacking(cas=scrambled_cas,
|
|
||||||
escort=scrambled_sweep,
|
|
||||||
armor=scrambled_armor,
|
|
||||||
clients=scrambled_clients)
|
|
||||||
else:
|
|
||||||
e.player_defending(interceptors=scrambled_aircraft,
|
|
||||||
clients=scrambled_clients)
|
|
||||||
elif type(self.event) is InterceptEvent:
|
|
||||||
e = self.event # type: InterceptEvent
|
|
||||||
if self.game.is_player_attack(self.event):
|
|
||||||
e.player_attacking(interceptors=scrambled_aircraft,
|
|
||||||
clients=scrambled_clients)
|
|
||||||
else:
|
|
||||||
e.player_defending(escort=scrambled_aircraft,
|
|
||||||
clients=scrambled_clients)
|
|
||||||
elif type(self.event) is FrontlineAttackEvent:
|
|
||||||
e = self.event # type: FrontlineAttackEvent
|
|
||||||
e.player_attacking(armor=scrambled_armor, strikegroup=scrambled_aircraft, clients=scrambled_clients)
|
|
||||||
elif type(self.event) is FrontlinePatrolEvent:
|
|
||||||
e = self.event # type: FrontlinePatrolEvent
|
|
||||||
e.player_attacking(interceptors=scrambled_aircraft, clients=scrambled_clients, armor=scrambled_armor)
|
|
||||||
elif type(self.event) is NavalInterceptEvent:
|
|
||||||
e = self.event # type: NavalInterceptEvent
|
|
||||||
|
|
||||||
if self.game.is_player_attack(self.event):
|
for task_type, dict in self.scramble_entries.items():
|
||||||
e.player_attacking(strikegroup=scrambled_aircraft, clients=scrambled_clients)
|
for unit_type, (count_entry, clients_entry) in dict.items():
|
||||||
else:
|
try:
|
||||||
e.player_defending(interceptors=scrambled_aircraft, clients=scrambled_clients)
|
count = int(count_entry.get())
|
||||||
elif type(self.event) is AntiAAStrikeEvent:
|
except:
|
||||||
e = self.event # type: AntiAAStrikeEvent
|
count = 0
|
||||||
if self.game.is_player_attack(self.event):
|
|
||||||
e.player_attacking(strikegroup=scrambled_aircraft, clients=scrambled_clients)
|
try:
|
||||||
else:
|
clients_count = int(clients_entry and clients_entry.get() or 0)
|
||||||
e.player_defending(interceptors=scrambled_aircraft, clients=scrambled_clients)
|
except:
|
||||||
elif type(self.event) is InsurgentAttackEvent:
|
clients_count = 0
|
||||||
e = self.event # type: InsurgentAttackEvent
|
|
||||||
if self.game.is_player_attack(self.event):
|
dampened_count = dampen_count(task_type, unit_type, count)
|
||||||
assert False
|
tasks_clients_counts[task_type] = tasks_clients_counts.get(task_type, 0) + clients_count
|
||||||
else:
|
tasks_scramble_counts[task_type] = tasks_scramble_counts.get(task_type, 0) + dampened_count
|
||||||
e.player_defending(strikegroup=scrambled_aircraft, clients=scrambled_clients)
|
|
||||||
elif type(self.event) is InfantryTransportEvent:
|
flights[task_type][unit_type] = dampened_count, clients_count
|
||||||
e = self.event # type: InfantryTransportEvent
|
|
||||||
if self.game.is_player_attack(self.event):
|
for task in self.event.ai_banned_tasks:
|
||||||
e.player_attacking(transport=scrambled_aircraft, clients=scrambled_clients)
|
if tasks_clients_counts.get(task, 0) == 0 and tasks_scramble_counts.get(task, 0) > 0:
|
||||||
else:
|
self.error_label["text"] = "Need at least one player in flight {}".format(self.event.flight_name(task))
|
||||||
assert False
|
return
|
||||||
|
|
||||||
|
if isinstance(self.event, FrontlineAttackEvent) or isinstance(self.event, FrontlinePatrolEvent):
|
||||||
|
if tasks_scramble_counts.get(PinpointStrike, 0) == 0:
|
||||||
|
self.error_label["text"] = "No ground vehicles assigned to attack!"
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
if self.game.is_player_attack(self.event):
|
||||||
|
self.event.player_attacking(flights)
|
||||||
|
else:
|
||||||
|
self.event.player_defending(flights)
|
||||||
|
|
||||||
self.game.initiate_event(self.event)
|
self.game.initiate_event(self.event)
|
||||||
EventResultsMenu(self.window, self.parent, self.game, self.event).display()
|
EventResultsMenu(self.window, self.parent, self.game, self.event).display()
|
||||||
|
|||||||
@ -3,6 +3,7 @@ from ui.window import *
|
|||||||
|
|
||||||
from game.game import *
|
from game.game import *
|
||||||
from userdata.debriefing import *
|
from userdata.debriefing import *
|
||||||
|
from .styles import STYLES
|
||||||
|
|
||||||
|
|
||||||
class EventResultsMenu(Menu):
|
class EventResultsMenu(Menu):
|
||||||
@ -13,6 +14,7 @@ class EventResultsMenu(Menu):
|
|||||||
def __init__(self, window: Window, parent, game: Game, event: Event):
|
def __init__(self, window: Window, parent, game: Game, event: Event):
|
||||||
super(EventResultsMenu, self).__init__(window, parent, game)
|
super(EventResultsMenu, self).__init__(window, parent, game)
|
||||||
self.frame = window.right_pane
|
self.frame = window.right_pane
|
||||||
|
self.frame.grid_rowconfigure(0, weight=0)
|
||||||
self.event = event
|
self.event = event
|
||||||
self.finished = False
|
self.finished = False
|
||||||
|
|
||||||
@ -21,50 +23,92 @@ class EventResultsMenu(Menu):
|
|||||||
def display(self):
|
def display(self):
|
||||||
self.window.clear_right_pane()
|
self.window.clear_right_pane()
|
||||||
|
|
||||||
|
row = 0
|
||||||
|
|
||||||
|
def header(text, style="strong"):
|
||||||
|
nonlocal row
|
||||||
|
head = Frame(self.frame, **STYLES["header"])
|
||||||
|
head.grid(row=row, column=0, sticky=N + EW, columnspan=2, pady=(0, 10))
|
||||||
|
Label(head, text=text, **STYLES[style]).grid()
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
def label(text, style="widget"):
|
||||||
|
nonlocal row
|
||||||
|
Label(self.frame, text=text, **STYLES[style]).grid(row=row, column=0, sticky=NW, columnspan=2)
|
||||||
|
row += 1
|
||||||
|
|
||||||
if not self.finished:
|
if not self.finished:
|
||||||
Label(self.frame, text="Play the mission and save debriefing to").grid(row=0, column=0)
|
|
||||||
Label(self.frame, text=debriefing_directory_location()).grid(row=1, column=0)
|
header("You are clear for takeoff!")
|
||||||
|
|
||||||
|
label("In DCS, open and play the mission:")
|
||||||
|
label("liberation_nextturn", "italic")
|
||||||
|
label("or")
|
||||||
|
label("liberation_nextturn_quick", "italic")
|
||||||
|
header("Then save the debriefing to the folder:")
|
||||||
|
label(debriefing_directory_location(), "italic")
|
||||||
|
header("Waiting for results...")
|
||||||
|
|
||||||
|
pg = Progressbar(self.frame, orient="horizontal", length=200, mode="determinate")
|
||||||
|
pg.grid(row=row, column=0, columnspan=2, sticky=EW, pady=5, padx=5)
|
||||||
|
pg.start(10)
|
||||||
|
row += 1
|
||||||
|
|
||||||
"""
|
"""
|
||||||
For debugging purposes
|
Label(self.frame, text="Cheat operation results: ", **STYLES["strong"]).grid(column=0, row=row,
|
||||||
|
columnspan=2, sticky=NSEW,
|
||||||
|
pady=5)
|
||||||
|
|
||||||
|
row += 1
|
||||||
|
Button(self.frame, text="full enemy losses", command=self.simulate_result(0, 1),
|
||||||
|
**STYLES["btn-warning"]).grid(column=0, row=row, padx=5, pady=5)
|
||||||
|
Button(self.frame, text="full player losses", command=self.simulate_result(1, 0),
|
||||||
|
**STYLES["btn-warning"]).grid(column=1, row=row, padx=5, pady=5)
|
||||||
|
row += 1
|
||||||
|
Button(self.frame, text="some enemy losses", command=self.simulate_result(0, 0.8),
|
||||||
|
**STYLES["btn-warning"]).grid(column=0, row=row, padx=5, pady=5)
|
||||||
|
Button(self.frame, text="some player losses", command=self.simulate_result(0.8, 0),
|
||||||
|
**STYLES["btn-warning"]).grid(column=1, row=row, padx=5, pady=5)
|
||||||
|
row += 1
|
||||||
"""
|
"""
|
||||||
|
|
||||||
row = 3
|
|
||||||
Separator(self.frame, orient=HORIZONTAL).grid(row=row, sticky=EW); row += 1
|
|
||||||
Label(self.frame, text="Cheat operation results: ").grid(row=row); row += 1
|
|
||||||
Button(self.frame, text="full enemy losses", command=self.simulate_result(0, 1)).grid(row=row); row += 1
|
|
||||||
Button(self.frame, text="full player losses", command=self.simulate_result(1, 0)).grid(row=row); row += 1
|
|
||||||
Button(self.frame, text="some enemy losses", command=self.simulate_result(0, 0.8)).grid(row=row); row += 1
|
|
||||||
Button(self.frame, text="some player losses", command=self.simulate_result(0.8, 0)).grid(row=row); row += 1
|
|
||||||
else:
|
else:
|
||||||
row = 0
|
row = 0
|
||||||
if self.event.is_successfull(self.debriefing):
|
if self.event.is_successfull(self.debriefing):
|
||||||
Label(self.frame, text="Operation success").grid(row=row, columnspan=1); row += 1
|
header("Operation success", "title-green")
|
||||||
else:
|
else:
|
||||||
Label(self.frame, text="Operation failed").grid(row=row, columnspan=1); row += 1
|
header("Operation failed", "title-red")
|
||||||
|
|
||||||
|
header("Player losses")
|
||||||
|
|
||||||
Separator(self.frame, orient='horizontal').grid(row=row, columnspan=1, sticky=NE); row += 1
|
|
||||||
Label(self.frame, text="Player losses").grid(row=row, columnspan=1); row += 1
|
|
||||||
for unit_type, count in self.player_losses.items():
|
for unit_type, count in self.player_losses.items():
|
||||||
Label(self.frame, text=db.unit_type_name(unit_type)).grid(row=row)
|
Label(self.frame, text=db.unit_type_name(unit_type), **STYLES["widget"]).grid(row=row)
|
||||||
Label(self.frame, text="{}".format(count)).grid(column=1, row=row)
|
Label(self.frame, text="{}".format(count), **STYLES["widget"]).grid(column=1, row=row)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
header("Enemy losses")
|
||||||
|
|
||||||
|
if self.debriefing.destroyed_objects:
|
||||||
|
Label(self.frame, text="Ground assets", **STYLES["widget"]).grid(row=row)
|
||||||
|
Label(self.frame, text="{}".format(len(self.debriefing.destroyed_objects)), **STYLES["widget"]).grid(column=1, row=row)
|
||||||
row += 1
|
row += 1
|
||||||
|
|
||||||
Separator(self.frame, orient='horizontal').grid(row=row, columnspan=1, sticky=NE); row += 1
|
|
||||||
Label(self.frame, text="Enemy losses").grid(columnspan=1, row=row); row += 1
|
|
||||||
for unit_type, count in self.enemy_losses.items():
|
for unit_type, count in self.enemy_losses.items():
|
||||||
if count == 0:
|
if count == 0:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
Label(self.frame, text=db.unit_type_name(unit_type)).grid(row=row)
|
Label(self.frame, text=db.unit_type_name(unit_type), **STYLES["widget"]).grid(row=row)
|
||||||
Label(self.frame, text="{}".format(count)).grid(column=1, row=row)
|
Label(self.frame, text="{}".format(count), **STYLES["widget"]).grid(column=1, row=row)
|
||||||
row += 1
|
row += 1
|
||||||
|
|
||||||
Button(self.frame, text="Okay", command=self.dismiss).grid(columnspan=1, row=row); row += 1
|
Button(self.frame, text="Okay", command=self.dismiss, **STYLES["btn-primary"]).grid(columnspan=1, row=row)
|
||||||
|
row += 1
|
||||||
|
|
||||||
def process_debriefing(self, debriefing: Debriefing):
|
def process_debriefing(self, debriefing: Debriefing):
|
||||||
self.debriefing = debriefing
|
self.debriefing = debriefing
|
||||||
debriefing.calculate_units(mission=self.event.operation.mission,
|
|
||||||
|
debriefing.calculate_units(regular_mission=self.event.operation.regular_mission,
|
||||||
|
quick_mission=self.event.operation.quick_mission,
|
||||||
player_name=self.game.player,
|
player_name=self.game.player,
|
||||||
enemy_name=self.game.enemy)
|
enemy_name=self.game.enemy)
|
||||||
|
|
||||||
@ -106,8 +150,10 @@ class EventResultsMenu(Menu):
|
|||||||
alive_player_units = count(player)
|
alive_player_units = count(player)
|
||||||
alive_enemy_units = count(enemy)
|
alive_enemy_units = count(enemy)
|
||||||
|
|
||||||
destroyed_player_units = db.unitdict_restrict_count(alive_player_units, math.ceil(sum(alive_player_units.values()) * player_factor))
|
destroyed_player_units = db.unitdict_restrict_count(alive_player_units, math.ceil(
|
||||||
destroyed_enemy_units = db.unitdict_restrict_count(alive_enemy_units, math.ceil(sum(alive_enemy_units.values()) * enemy_factor))
|
sum(alive_player_units.values()) * player_factor))
|
||||||
|
destroyed_enemy_units = db.unitdict_restrict_count(alive_enemy_units, math.ceil(
|
||||||
|
sum(alive_enemy_units.values()) * enemy_factor))
|
||||||
|
|
||||||
alive_player_units = {k: v - destroyed_player_units.get(k, 0) for k, v in alive_player_units.items()}
|
alive_player_units = {k: v - destroyed_player_units.get(k, 0) for k, v in alive_player_units.items()}
|
||||||
alive_enemy_units = {k: v - destroyed_enemy_units.get(k, 0) for k, v in alive_enemy_units.items()}
|
alive_enemy_units = {k: v - destroyed_enemy_units.get(k, 0) for k, v in alive_enemy_units.items()}
|
||||||
|
|||||||
101
ui/mainmenu.py
101
ui/mainmenu.py
@ -1,11 +1,13 @@
|
|||||||
import pickle
|
|
||||||
|
|
||||||
from ui.basemenu import *
|
|
||||||
from ui.overviewcanvas import *
|
|
||||||
from ui.configurationmenu import *
|
|
||||||
|
|
||||||
from game.game import *
|
from game.game import *
|
||||||
|
from ui.basemenu import *
|
||||||
|
from ui.configurationmenu import *
|
||||||
|
from ui.overviewcanvas import *
|
||||||
from userdata import persistency
|
from userdata import persistency
|
||||||
|
from .styles import STYLES
|
||||||
|
|
||||||
|
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
|
||||||
class MainMenu(Menu):
|
class MainMenu(Menu):
|
||||||
@ -18,53 +20,84 @@ class MainMenu(Menu):
|
|||||||
self.upd.update()
|
self.upd.update()
|
||||||
|
|
||||||
self.frame = self.window.right_pane
|
self.frame = self.window.right_pane
|
||||||
self.frame.grid_columnconfigure(0, weight=1)
|
self.frame.rowconfigure(0, weight=0)
|
||||||
|
self.frame.rowconfigure(1, weight=1)
|
||||||
|
|
||||||
def display(self):
|
def display(self):
|
||||||
persistency.save_game(self.game)
|
persistency.save_game(self.game)
|
||||||
|
|
||||||
self.window.clear_right_pane()
|
self.window.clear_right_pane()
|
||||||
self.upd.update()
|
self.upd.update()
|
||||||
row = 1
|
|
||||||
|
# Header :
|
||||||
|
header = Frame(self.frame, **STYLES["header"])
|
||||||
|
Button(header, text="Configuration", command=self.configuration_menu, **STYLES["btn-primary"]).grid(column=0, row=0, sticky=NW)
|
||||||
|
Label(header, text="Budget: {}m (+{}m)".format(self.game.budget, self.game.budget_reward_amount), **STYLES["strong"]).grid(column=1, row=0, sticky=N+EW, padx=50)
|
||||||
|
Button(header, text="Pass turn", command=self.pass_turn, **STYLES["btn-primary"]).grid(column=2, row=0, sticky=NE)
|
||||||
|
header.grid(column=0, row=0, sticky=N+EW)
|
||||||
|
|
||||||
|
content = Frame(self.frame, **STYLES["body"])
|
||||||
|
content.grid(column=0, row=1, sticky=NSEW)
|
||||||
|
column = 0
|
||||||
|
row = 0
|
||||||
|
|
||||||
def label(text):
|
def label(text):
|
||||||
nonlocal row
|
nonlocal row, body
|
||||||
Label(self.frame, text=text).grid(row=row, sticky=NW)
|
frame = LabelFrame(body, **STYLES["label-frame"])
|
||||||
|
frame.grid(row=row, sticky=N+EW, columnspan=2)
|
||||||
|
Label(frame, text=text, **STYLES["widget"]).grid(row=row, sticky=NS)
|
||||||
row += 1
|
row += 1
|
||||||
|
|
||||||
def event_button(event):
|
def event_button(event):
|
||||||
nonlocal row
|
nonlocal row, body
|
||||||
Message(self.frame, text="{}{} at {}".format(
|
frame = LabelFrame(body, **STYLES["label-frame"])
|
||||||
event.defender_name == self.game.player and "Enemy attacking: " or "",
|
frame.grid(row=row, sticky=N+EW)
|
||||||
event,
|
Message(frame, text="{}".format(
|
||||||
event.to_cp,
|
event
|
||||||
), aspect=1600).grid(column=0, row=row, sticky=NW)
|
), aspect=1600, **STYLES["widget"]).grid(column=0, row=0, sticky=N+EW)
|
||||||
Button(self.frame, text=">", command=self.start_event(event)).grid(column=0, row=row, sticky=NE+S); row += 1
|
Button(body, text=">", command=self.start_event(event), **STYLES["btn-primary"]).grid(column=1, row=row, sticky=E)
|
||||||
|
row += 1
|
||||||
|
|
||||||
def destination_header(text, separator=True):
|
def departure_header(text, style="strong"):
|
||||||
nonlocal row
|
nonlocal row, body
|
||||||
if separator:
|
Label(body, text=text, **STYLES[style]).grid(column=0, columnspan=2, row=row, sticky=N+EW, pady=(0, 5))
|
||||||
Separator(self.frame, orient=HORIZONTAL).grid(row=row, sticky=EW); row += 1
|
row += 1
|
||||||
Label(self.frame, text=text).grid(column=0, row=row, sticky=N); row += 1
|
|
||||||
|
|
||||||
Button(self.frame, text="Configuration", command=self.configuration_menu).grid(column=0, row=0, sticky=NE)
|
def destination_header(text):
|
||||||
Button(self.frame, text="Pass turn", command=self.pass_turn).grid(column=0, row=0, sticky=NW)
|
nonlocal row, body
|
||||||
Label(self.frame, text="Budget: {}m (+{}m)".format(self.game.budget, self.game.budget_reward_amount)).grid(column=0, row=0, sticky=N)
|
Label(body, text=text, **STYLES["substrong"]).grid(column=0, columnspan=2, row=row, sticky=N+EW)
|
||||||
Separator(self.frame, orient='horizontal').grid(row=row, sticky=EW); row += 1
|
row += 1
|
||||||
|
|
||||||
events = self.game.events
|
events = self.game.events
|
||||||
|
events.sort(key=lambda x: x.to_cp.name)
|
||||||
events.sort(key=lambda x: x.from_cp.name)
|
events.sort(key=lambda x: x.from_cp.name)
|
||||||
events.sort(key=lambda x: x.informational and 2 or (self.game.is_player_attack(x) and 1 or 0))
|
events.sort(key=lambda x: x.informational and 1 or (self.game.is_player_attack(x) and 2 or 0))
|
||||||
|
|
||||||
destination = None
|
destination = None
|
||||||
|
departure = None
|
||||||
|
|
||||||
for event in events:
|
for event in events:
|
||||||
|
if event.informational:
|
||||||
|
new_departure = "Deliveries"
|
||||||
|
elif not self.game.is_player_attack(event):
|
||||||
|
new_departure = "Enemy attack"
|
||||||
|
else:
|
||||||
|
new_departure = event.from_cp.name
|
||||||
|
|
||||||
|
if new_departure != departure:
|
||||||
|
body = Frame(content, **STYLES["body"])
|
||||||
|
body.grid(column=column, row=1, sticky=N+EW)
|
||||||
|
row = 0
|
||||||
|
column += 1
|
||||||
|
|
||||||
|
departure = new_departure
|
||||||
|
departure_header(new_departure, style="strong" if self.game.is_player_attack(event) else "supstrong")
|
||||||
|
destination = None
|
||||||
|
|
||||||
if not event.informational:
|
if not event.informational:
|
||||||
if self.game.is_player_attack(event):
|
new_destination = "At {}".format(event.to_cp.name)
|
||||||
new_destination = event.from_cp.name
|
|
||||||
else:
|
|
||||||
new_destination = "Enemy attack"
|
|
||||||
if destination != new_destination:
|
if destination != new_destination:
|
||||||
destination_header(new_destination, destination is not None)
|
destination_header(new_destination)
|
||||||
destination = new_destination
|
destination = new_destination
|
||||||
|
|
||||||
if event.informational:
|
if event.informational:
|
||||||
@ -92,3 +125,7 @@ class MainMenu(Menu):
|
|||||||
|
|
||||||
self.basemenu = BaseMenu(self.window, self, self.game, cp)
|
self.basemenu = BaseMenu(self.window, self, self.game, cp)
|
||||||
self.basemenu.display()
|
self.basemenu.display()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
|
import os
|
||||||
from tkinter import *
|
from tkinter import *
|
||||||
from tkinter.ttk import *
|
from tkinter.ttk import *
|
||||||
|
|
||||||
from ui.window import *
|
from ui.window import *
|
||||||
|
from .styles import STYLES
|
||||||
|
|
||||||
|
|
||||||
class NewGameMenu(Menu):
|
class NewGameMenu(Menu):
|
||||||
@ -14,6 +16,7 @@ class NewGameMenu(Menu):
|
|||||||
def __init__(self, window: Window, callback: typing.Callable):
|
def __init__(self, window: Window, callback: typing.Callable):
|
||||||
super(NewGameMenu, self).__init__(window, None, None)
|
super(NewGameMenu, self).__init__(window, None, None)
|
||||||
self.frame = window.right_pane
|
self.frame = window.right_pane
|
||||||
|
window.left_pane.configure(background="black")
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
|
|
||||||
self.selected_country = IntVar()
|
self.selected_country = IntVar()
|
||||||
@ -57,23 +60,69 @@ class NewGameMenu(Menu):
|
|||||||
def display(self):
|
def display(self):
|
||||||
self.window.clear_right_pane()
|
self.window.clear_right_pane()
|
||||||
|
|
||||||
Label(self.frame, text="Player country").grid(row=0, column=0)
|
# Header
|
||||||
Radiobutton(self.frame, text="USA", variable=self.selected_country, value=0).grid(row=1, column=0)
|
head = Frame(self.frame, **STYLES["header"])
|
||||||
Radiobutton(self.frame, text="Russia", variable=self.selected_country, value=1).grid(row=2, column=0)
|
head.grid(row=0, column=0, sticky=NSEW)
|
||||||
|
Label(head, text="Start a new game", **STYLES["title"]).grid()
|
||||||
|
|
||||||
Label(self.frame, text="Terrain").grid(row=0, column=1)
|
# Body
|
||||||
Radiobutton(self.frame, text="Caucasus", variable=self.selected_terrain, value=0).grid(row=1, column=1)
|
body = Frame(self.frame, **STYLES["body"])
|
||||||
Radiobutton(self.frame, text="Nevada", variable=self.selected_terrain, value=1).grid(row=2, column=1)
|
body.grid(row=1, column=0, sticky=NSEW)
|
||||||
Radiobutton(self.frame, text="Persian Gulf", variable=self.selected_terrain, value=2).grid(row=3, column=1)
|
|
||||||
|
|
||||||
Label(self.frame, text="Options").grid(row=1, column=2)
|
# Country Selection
|
||||||
Checkbutton(self.frame, text="SAMs", variable=self.sams).grid(row=1, column=2)
|
country = LabelFrame(body, text="Player Side", **STYLES["label-frame"])
|
||||||
Checkbutton(self.frame, text="Mid game", variable=self.midgame).grid(row=2, column=2)
|
country.grid(row=0, column=0, sticky=NW, padx=5)
|
||||||
|
Radiobutton(country, variable=self.selected_country, value=0, **STYLES["radiobutton"]).grid(row=0, column=0,
|
||||||
|
sticky=W)
|
||||||
|
Label(country, text="USA", **STYLES["widget"]).grid(row=0, column=1, sticky=W)
|
||||||
|
Radiobutton(country, variable=self.selected_country, value=1, **STYLES["radiobutton"]).grid(row=1, column=0,
|
||||||
|
sticky=W)
|
||||||
|
Label(country, text="Russia", **STYLES["widget"]).grid(row=1, column=1, sticky=W)
|
||||||
|
|
||||||
Label(self.frame, text="Multiplier").grid(row=0, column=3)
|
# Terrain Selection
|
||||||
Entry(self.frame, textvariable=self.multiplier).grid(row=1, column=3)
|
terrain = LabelFrame(body, text="Terrain", **STYLES["label-frame"])
|
||||||
|
terrain.grid(row=0, column=1, sticky=N, padx=5)
|
||||||
|
|
||||||
Button(self.frame, text="Proceed", command=self.proceed).grid(row=5, column=0, columnspan=4)
|
Radiobutton(terrain, variable=self.selected_terrain, value=0, **STYLES["radiobutton"]) \
|
||||||
|
.grid(row=0, column=0, sticky=W)
|
||||||
|
Label(terrain, text="Caucasus", **STYLES["widget"]).grid(row=0, column=1, sticky=W)
|
||||||
|
self.create_label_image(terrain, "terrain_caucasus.gif").grid(row=0, column=2, padx=5)
|
||||||
|
|
||||||
|
Radiobutton(terrain, variable=self.selected_terrain, value=1, **STYLES["radiobutton"]) \
|
||||||
|
.grid(row=1, column=0, sticky=W)
|
||||||
|
Label(terrain, text="Nevada", **STYLES["widget"]).grid(row=1, column=1, sticky=W)
|
||||||
|
self.create_label_image(terrain, "terrain_nevada.gif").grid(row=1, column=2, padx=5)
|
||||||
|
|
||||||
|
Radiobutton(terrain, variable=self.selected_terrain, value=2, **STYLES["radiobutton"]) \
|
||||||
|
.grid(row=2, column=0, sticky=W)
|
||||||
|
Label(terrain, text="Persian Gulf", **STYLES["widget"]).grid(row=2, column=1, sticky=W)
|
||||||
|
self.create_label_image(terrain, "terrain_pg.gif").grid(row=2, column=2, padx=5)
|
||||||
|
|
||||||
|
# Misc Options
|
||||||
|
options = LabelFrame(body, text="Misc Options", **STYLES["label-frame"])
|
||||||
|
options.grid(row=0, column=2, sticky=NE, padx=5)
|
||||||
|
|
||||||
|
Checkbutton(options, variable=self.sams, **STYLES["radiobutton"]).grid(row=0, column=0, sticky=W)
|
||||||
|
Label(options, text="SAMs", **STYLES["widget"]).grid(row=0, column=1, sticky=W)
|
||||||
|
|
||||||
|
Checkbutton(options, variable=self.midgame, **STYLES["radiobutton"]).grid(row=1, column=0, sticky=W)
|
||||||
|
Label(options, text="Mid Game", **STYLES["widget"]).grid(row=1, column=1, sticky=W)
|
||||||
|
|
||||||
|
Label(options, text="Multiplier", **STYLES["widget"]).grid(row=2, column=0, sticky=W)
|
||||||
|
Entry(options, textvariable=self.multiplier).grid(row=2, column=1, sticky=W)
|
||||||
|
|
||||||
|
# Footer with Proceed Button
|
||||||
|
footer = Frame(self.frame, **STYLES["header"])
|
||||||
|
footer.grid(row=2, sticky=N + E + W)
|
||||||
|
Button(footer, text="Proceed", command=self.proceed, **STYLES["btn-primary"]).grid(row=0, column=0, sticky=SE,
|
||||||
|
padx=5, pady=5)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_label_image(parent, image):
|
||||||
|
im = PhotoImage(file=os.path.join("resources", "ui", image))
|
||||||
|
label = Label(parent, image=im)
|
||||||
|
label.image = im
|
||||||
|
return label
|
||||||
|
|
||||||
def proceed(self):
|
def proceed(self):
|
||||||
self.callback(self.player_country_name,
|
self.callback(self.player_country_name,
|
||||||
|
|||||||
@ -50,11 +50,12 @@ class OverviewCanvas:
|
|||||||
title = cp.name
|
title = cp.name
|
||||||
font = ("Helvetica", 10)
|
font = ("Helvetica", 10)
|
||||||
|
|
||||||
id = self.canvas.create_text(coords[0]+1, coords[1]+1, text=title, fill='white', font=font)
|
|
||||||
self.canvas.tag_bind(id, "<Button-1>", self.display(cp))
|
|
||||||
id = self.canvas.create_text(coords[0], coords[1], text=title, font=font)
|
id = self.canvas.create_text(coords[0], coords[1], text=title, font=font)
|
||||||
self.canvas.tag_bind(id, "<Button-1>", self.display(cp))
|
self.canvas.tag_bind(id, "<Button-1>", self.display(cp))
|
||||||
|
|
||||||
|
id = self.canvas.create_text(coords[0]+1, coords[1]+1, text=title, fill='white', font=font)
|
||||||
|
self.canvas.tag_bind(id, "<Button-1>", self.display(cp))
|
||||||
|
|
||||||
def _player_color(self):
|
def _player_color(self):
|
||||||
return self.game.player == "USA" and "blue" or "red"
|
return self.game.player == "USA" and "blue" or "red"
|
||||||
|
|
||||||
@ -66,6 +67,14 @@ class OverviewCanvas:
|
|||||||
self.canvas.create_image((self.image.width()/2, self.image.height()/2), image=self.image)
|
self.canvas.create_image((self.image.width()/2, self.image.height()/2), image=self.image)
|
||||||
|
|
||||||
for cp in self.game.theater.controlpoints:
|
for cp in self.game.theater.controlpoints:
|
||||||
|
for ground_object in cp.ground_objects:
|
||||||
|
x, y = self.transform_point(ground_object.position)
|
||||||
|
self.canvas.create_text(x,
|
||||||
|
y,
|
||||||
|
text=".",
|
||||||
|
fill="black" if ground_object.is_dead else self._enemy_color(),
|
||||||
|
font=("Helvetica", 18))
|
||||||
|
|
||||||
coords = self.transform_point(cp.position)
|
coords = self.transform_point(cp.position)
|
||||||
for connected_cp in cp.connected_points:
|
for connected_cp in cp.connected_points:
|
||||||
connected_coords = self.transform_point(connected_cp.position)
|
connected_coords = self.transform_point(connected_cp.position)
|
||||||
@ -79,7 +88,15 @@ class OverviewCanvas:
|
|||||||
self.canvas.create_line((coords[0], coords[1], connected_coords[0], connected_coords[1]), width=2, fill=color)
|
self.canvas.create_line((coords[0], coords[1], connected_coords[0], connected_coords[1]), width=2, fill=color)
|
||||||
|
|
||||||
if cp.captured and not connected_cp.captured and Conflict.has_frontline_between(cp, connected_cp):
|
if cp.captured and not connected_cp.captured and Conflict.has_frontline_between(cp, connected_cp):
|
||||||
frontline_pos, heading, distance = Conflict.frontline_vector(cp, connected_cp, self.game.theater)
|
frontline = Conflict.frontline_vector(cp, connected_cp, self.game.theater)
|
||||||
|
if not frontline:
|
||||||
|
continue
|
||||||
|
|
||||||
|
frontline_pos, heading, distance = frontline
|
||||||
|
if distance < 10000:
|
||||||
|
frontline_pos = frontline_pos.point_from_heading(heading + 180, 5000)
|
||||||
|
distance = 10000
|
||||||
|
|
||||||
start_coords = self.transform_point(frontline_pos, treshold=10)
|
start_coords = self.transform_point(frontline_pos, treshold=10)
|
||||||
end_coords = self.transform_point(frontline_pos.point_from_heading(heading, distance), treshold=60)
|
end_coords = self.transform_point(frontline_pos.point_from_heading(heading, distance), treshold=60)
|
||||||
|
|
||||||
@ -97,7 +114,7 @@ class OverviewCanvas:
|
|||||||
color = self._enemy_color()
|
color = self._enemy_color()
|
||||||
|
|
||||||
cp_id = self.canvas.create_arc((coords[0] - arc_size/2, coords[1] - arc_size/2),
|
cp_id = self.canvas.create_arc((coords[0] - arc_size/2, coords[1] - arc_size/2),
|
||||||
(coords[0]+arc_size/2, coords[1]+arc_size/2),
|
(coords[0] + arc_size/2, coords[1] + arc_size/2),
|
||||||
fill=color,
|
fill=color,
|
||||||
style=PIESLICE,
|
style=PIESLICE,
|
||||||
start=start,
|
start=start,
|
||||||
@ -116,7 +133,8 @@ class OverviewCanvas:
|
|||||||
self.create_cp_title((coords[0] + arc_size/4, coords[1] + arc_size/4), cp)
|
self.create_cp_title((coords[0] + arc_size/4, coords[1] + arc_size/4), cp)
|
||||||
|
|
||||||
units_title = "{}/{}/{}".format(cp.base.total_planes, cp.base.total_armor, cp.base.total_aa)
|
units_title = "{}/{}/{}".format(cp.base.total_planes, cp.base.total_armor, cp.base.total_aa)
|
||||||
self.canvas.create_text(coords[0], coords[1] - arc_size / 1.5, text=units_title, font=("Helvetica", 10))
|
self.canvas.create_text(coords[0]+1, coords[1] - arc_size / 1.5 +1, text=units_title, font=("Helvetica", 8), fill=color)
|
||||||
|
self.canvas.create_text(coords[0], coords[1] - arc_size / 1.5, text=units_title, font=("Helvetica", 8), fill="white")
|
||||||
|
|
||||||
def display(self, cp: ControlPoint):
|
def display(self, cp: ControlPoint):
|
||||||
def action(_):
|
def action(_):
|
||||||
|
|||||||
49
ui/styles.py
Normal file
49
ui/styles.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# Style for UI
|
||||||
|
|
||||||
|
# Padding
|
||||||
|
PADDING_X = 5
|
||||||
|
PADDING_Y = 5
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
FG_COLOR = "white"
|
||||||
|
FG_COLOR_LIGHT = "#dddddd"
|
||||||
|
BG_COLOR = "#4E5760"
|
||||||
|
GREEN = "#699245"
|
||||||
|
YELLOW = "#BF9A46"
|
||||||
|
RED = "#D0232E"
|
||||||
|
BG_TITLE_COLOR = "#2D3E50"
|
||||||
|
BG_SUBTITLE_COLOR = "#3E4F61"
|
||||||
|
|
||||||
|
# Fonts
|
||||||
|
FONT_FAMILY = "Trebuchet MS"
|
||||||
|
DEFAULT_FONT = (FONT_FAMILY, 8)
|
||||||
|
ITALIC = (FONT_FAMILY, 8, "italic")
|
||||||
|
BOLD_FONT = (FONT_FAMILY, 10, "bold italic")
|
||||||
|
TITLE_FONT = (FONT_FAMILY, 16, "bold italic")
|
||||||
|
|
||||||
|
# List of styles
|
||||||
|
STYLES = {}
|
||||||
|
STYLES["label-frame"] = {"font": BOLD_FONT, "bg": BG_COLOR, "fg": FG_COLOR}
|
||||||
|
STYLES["frame-wrapper"] = {"bg": BG_COLOR, "relief":"sunken"}
|
||||||
|
|
||||||
|
STYLES["body"] = {"bg": BG_COLOR, "padx": 10, "pady": 10}
|
||||||
|
STYLES["strong"] = {"font": BOLD_FONT, "bg": BG_TITLE_COLOR, "fg": FG_COLOR}
|
||||||
|
STYLES["substrong"] = {"font": BOLD_FONT, "bg": BG_SUBTITLE_COLOR, "fg": FG_COLOR}
|
||||||
|
STYLES["supstrong"] = {"font": BOLD_FONT, "bg": RED, "fg": FG_COLOR}
|
||||||
|
STYLES["strong-grey"] = {"font": BOLD_FONT, "bg": BG_TITLE_COLOR, "fg": FG_COLOR_LIGHT}
|
||||||
|
|
||||||
|
STYLES["mission-preview"] = {"font": BOLD_FONT, "bg": YELLOW, "fg": FG_COLOR}
|
||||||
|
|
||||||
|
STYLES["widget"] = {"bg": BG_COLOR, "fg": FG_COLOR, "padx": PADDING_X, "pady": PADDING_Y, "font": DEFAULT_FONT}
|
||||||
|
STYLES["italic"] = {"bg": BG_COLOR, "fg": FG_COLOR, "padx": PADDING_X, "pady": PADDING_Y, "font": ITALIC}
|
||||||
|
STYLES["radiobutton"] = {"bg": BG_COLOR, "fg": "black", "padx": PADDING_X, "pady": PADDING_Y, "font": DEFAULT_FONT,
|
||||||
|
"activebackground": BG_COLOR, "highlightbackground": BG_COLOR, "selectcolor": "white"}
|
||||||
|
STYLES["title"] = {"bg": BG_TITLE_COLOR, "fg": FG_COLOR, "padx": PADDING_X, "pady": PADDING_Y, "font": TITLE_FONT}
|
||||||
|
STYLES["title-green"] = {"bg": GREEN, "fg": FG_COLOR, "padx": PADDING_X, "pady": PADDING_Y, "font": TITLE_FONT}
|
||||||
|
STYLES["title-red"] = {"bg": RED, "fg": FG_COLOR, "padx": PADDING_X, "pady": PADDING_Y, "font": TITLE_FONT}
|
||||||
|
STYLES["header"] = {"bg": BG_TITLE_COLOR}
|
||||||
|
STYLES["subheader"] = {"bg": BG_SUBTITLE_COLOR}
|
||||||
|
|
||||||
|
STYLES["btn-primary"] = {"bg": GREEN, "fg": FG_COLOR, "padx": PADDING_X, "pady": 2, "font": DEFAULT_FONT}
|
||||||
|
STYLES["btn-danger"] = {"bg": RED, "fg": FG_COLOR, "padx": PADDING_X, "pady": 2, "font": DEFAULT_FONT}
|
||||||
|
STYLES["btn-warning"] = {"bg": YELLOW, "fg": FG_COLOR, "padx": PADDING_X, "pady": 2, "font": DEFAULT_FONT}
|
||||||
19
ui/window.py
19
ui/window.py
@ -1,6 +1,6 @@
|
|||||||
from tkinter import *
|
from tkinter import *
|
||||||
from game.game import *
|
from game.game import *
|
||||||
|
from .styles import BG_COLOR,BG_TITLE_COLOR
|
||||||
|
|
||||||
class Window:
|
class Window:
|
||||||
image = None
|
image = None
|
||||||
@ -9,26 +9,33 @@ class Window:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.tk = Tk()
|
self.tk = Tk()
|
||||||
|
self.tk.title("DCS Liberation")
|
||||||
|
self.tk.iconbitmap("icon.ico")
|
||||||
|
self.tk.resizable(False, False)
|
||||||
self.tk.grid_columnconfigure(0, weight=1)
|
self.tk.grid_columnconfigure(0, weight=1)
|
||||||
self.tk.grid_rowconfigure(0, weight=1)
|
self.tk.grid_rowconfigure(0, weight=1)
|
||||||
|
|
||||||
self.frame = Frame(self.tk)
|
self.frame = Frame(self.tk, bg=BG_COLOR)
|
||||||
self.frame.grid(column=0, row=0, sticky=NSEW)
|
self.frame.grid(column=0, row=0, sticky=NSEW)
|
||||||
self.frame.grid_columnconfigure(0, minsize=300)
|
self.frame.grid_columnconfigure(0)
|
||||||
self.frame.grid_columnconfigure(1, minsize=400)
|
self.frame.grid_columnconfigure(1)
|
||||||
|
|
||||||
self.frame.grid_columnconfigure(0, weight=0)
|
self.frame.grid_columnconfigure(0, weight=0)
|
||||||
self.frame.grid_columnconfigure(1, weight=1)
|
self.frame.grid_columnconfigure(1, weight=1)
|
||||||
self.frame.grid_rowconfigure(0, weight=1)
|
self.frame.grid_rowconfigure(0, weight=1)
|
||||||
|
|
||||||
self.left_pane = Frame(self.frame)
|
self.left_pane = Frame(self.frame, bg=BG_TITLE_COLOR)
|
||||||
self.left_pane.grid(row=0, column=0, sticky=NSEW)
|
self.left_pane.grid(row=0, column=0, sticky=NSEW)
|
||||||
self.right_pane = Frame(self.frame)
|
self.right_pane = Frame(self.frame, bg=BG_COLOR)
|
||||||
self.right_pane.grid(row=0, column=1, sticky=NSEW)
|
self.right_pane.grid(row=0, column=1, sticky=NSEW)
|
||||||
|
|
||||||
self.tk.focus()
|
self.tk.focus()
|
||||||
|
|
||||||
def clear_right_pane(self):
|
def clear_right_pane(self):
|
||||||
|
for i in range(100):
|
||||||
|
self.right_pane.grid_columnconfigure(1, weight=0)
|
||||||
|
self.right_pane.grid_rowconfigure(1, weight=0)
|
||||||
|
|
||||||
for x in self.right_pane.winfo_children():
|
for x in self.right_pane.winfo_children():
|
||||||
x.grid_remove()
|
x.grid_remove()
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ from dcs.unit import UnitType
|
|||||||
from game import db
|
from game import db
|
||||||
|
|
||||||
from .persistency import base_path
|
from .persistency import base_path
|
||||||
|
from theater.theatergroundobject import CATEGORY_MAP
|
||||||
|
|
||||||
DEBRIEFING_LOG_EXTENSION = "log"
|
DEBRIEFING_LOG_EXTENSION = "log"
|
||||||
|
|
||||||
@ -59,14 +60,33 @@ def parse_mutliplayer_debriefing(contents: str):
|
|||||||
|
|
||||||
|
|
||||||
class Debriefing:
|
class Debriefing:
|
||||||
def __init__(self, dead_units):
|
def __init__(self, dead_units, trigger_state):
|
||||||
self.destroyed_units = {} # type: typing.Dict[str, typing.Dict[UnitType, int]]
|
self.destroyed_units = {} # type: typing.Dict[str, typing.Dict[UnitType, int]]
|
||||||
self.alive_units = {} # type: typing.Dict[str, typing.Dict[UnitType, int]]
|
self.alive_units = {} # type: typing.Dict[str, typing.Dict[UnitType, int]]
|
||||||
|
self.destroyed_objects = [] # type: typing.List[str]
|
||||||
|
|
||||||
|
self._trigger_state = trigger_state
|
||||||
self._dead_units = dead_units
|
self._dead_units = dead_units
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse(cls, path: str):
|
def parse(cls, path: str):
|
||||||
|
dead_units = []
|
||||||
|
|
||||||
|
def append_dead_object(object_mission_id_str):
|
||||||
|
nonlocal dead_units
|
||||||
|
object_mission_id = int(object_mission_id_str)
|
||||||
|
if object_mission_id in dead_units:
|
||||||
|
logging.info("debriefing: failed to append_dead_object {}: already exists!".format(object_mission_id))
|
||||||
|
return
|
||||||
|
|
||||||
|
dead_units.append(object_mission_id)
|
||||||
|
|
||||||
|
def parse_dead_object(event):
|
||||||
|
try:
|
||||||
|
append_dead_object(event["initiatorMissionID"])
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(e)
|
||||||
|
|
||||||
with open(path, "r") as f:
|
with open(path, "r") as f:
|
||||||
table_string = f.read()
|
table_string = f.read()
|
||||||
try:
|
try:
|
||||||
@ -75,50 +95,21 @@ class Debriefing:
|
|||||||
table = parse_mutliplayer_debriefing(table_string)
|
table = parse_mutliplayer_debriefing(table_string)
|
||||||
|
|
||||||
events = table.get("debriefing", {}).get("events", {})
|
events = table.get("debriefing", {}).get("events", {})
|
||||||
dead_units = {}
|
|
||||||
|
|
||||||
for event in events.values():
|
for event in events.values():
|
||||||
event_type = event.get("type", None)
|
event_type = event.get("type", None)
|
||||||
if event_type != "crash" and event_type != "dead":
|
if event_type in ["crash", "dead"]:
|
||||||
continue
|
parse_dead_object(event)
|
||||||
|
|
||||||
try:
|
trigger_state = table.get("debriefing", {}).get("triggers_state", {})
|
||||||
components = event["initiator"].split("|")
|
|
||||||
category, country_id, group_id, unit_type = components[0], int(components[1]), int(components[2]), db.unit_type_from_name(components[3])
|
|
||||||
if unit_type is None:
|
|
||||||
logging.info("Skipped due to no unit type")
|
|
||||||
continue
|
|
||||||
|
|
||||||
if category != "unit":
|
return Debriefing(dead_units, trigger_state)
|
||||||
logging.info("Skipped due to category")
|
|
||||||
continue
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(e)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if country_id not in dead_units:
|
def calculate_units(self, regular_mission: Mission, quick_mission: Mission, player_name: str, enemy_name: str):
|
||||||
dead_units[country_id] = {}
|
|
||||||
|
|
||||||
if unit_type not in dead_units[country_id]:
|
|
||||||
dead_units[country_id][unit_type] = 0
|
|
||||||
|
|
||||||
dead_units[country_id][unit_type] += 1
|
|
||||||
|
|
||||||
return Debriefing(dead_units)
|
|
||||||
|
|
||||||
def calculate_units(self, mission: Mission, player_name: str, enemy_name: str):
|
|
||||||
def count_groups(groups: typing.List[UnitType]) -> typing.Dict[UnitType, int]:
|
def count_groups(groups: typing.List[UnitType]) -> typing.Dict[UnitType, int]:
|
||||||
result = {}
|
result = {}
|
||||||
for group in groups:
|
for group in groups:
|
||||||
for unit in group.units:
|
for unit in group.units:
|
||||||
unit_type = None
|
unit_type = db.unit_type_of(unit)
|
||||||
if isinstance(unit, Vehicle):
|
|
||||||
unit_type = vehicle_map[unit.type]
|
|
||||||
elif isinstance(unit, Ship):
|
|
||||||
unit_type = ship_map[unit.type]
|
|
||||||
else:
|
|
||||||
unit_type = unit.unit_type
|
|
||||||
|
|
||||||
if unit_type in db.EXTRA_AA.values():
|
if unit_type in db.EXTRA_AA.values():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -126,6 +117,8 @@ class Debriefing:
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
mission = regular_mission if len(self._trigger_state) else quick_mission
|
||||||
|
|
||||||
player = mission.country(player_name)
|
player = mission.country(player_name)
|
||||||
enemy = mission.country(enemy_name)
|
enemy = mission.country(enemy_name)
|
||||||
|
|
||||||
@ -133,10 +126,40 @@ class Debriefing:
|
|||||||
enemy_units = count_groups(enemy.plane_group + enemy.vehicle_group + enemy.ship_group)
|
enemy_units = count_groups(enemy.plane_group + enemy.vehicle_group + enemy.ship_group)
|
||||||
|
|
||||||
self.destroyed_units = {
|
self.destroyed_units = {
|
||||||
player.name: self._dead_units.get(player.id, {}),
|
player.name: {},
|
||||||
enemy.name: self._dead_units.get(enemy.id, {}),
|
enemy.name: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
all_groups = {
|
||||||
|
player.name: player.plane_group + player.helicopter_group + player.vehicle_group + player.ship_group,
|
||||||
|
enemy.name: enemy.plane_group + enemy.helicopter_group + enemy.vehicle_group + enemy.ship_group,
|
||||||
|
}
|
||||||
|
|
||||||
|
static_groups = enemy.static_group
|
||||||
|
|
||||||
|
for country_name, country_groups in all_groups.items():
|
||||||
|
for group in country_groups:
|
||||||
|
for unit in group.units:
|
||||||
|
if unit.id in self._dead_units:
|
||||||
|
unit_type = db.unit_type_of(unit)
|
||||||
|
logging.info("debriefing: found dead unit {} ({}, {})".format(str(unit.name), unit.id, unit_type))
|
||||||
|
|
||||||
|
assert country_name
|
||||||
|
assert unit_type
|
||||||
|
self.destroyed_units[country_name][unit_type] = self.destroyed_units[country_name].get(unit_type, 0) + 1
|
||||||
|
self._dead_units.remove(unit.id)
|
||||||
|
|
||||||
|
for group in static_groups:
|
||||||
|
identifier = group.units[0].id
|
||||||
|
if identifier in self._dead_units:
|
||||||
|
logging.info("debriefing: found dead static {} ({})".format(str(group.name), identifier))
|
||||||
|
|
||||||
|
assert str(group.name)
|
||||||
|
self.destroyed_objects.append(str(group.name))
|
||||||
|
self._dead_units.remove(identifier)
|
||||||
|
|
||||||
|
logging.info("debriefing: unsatistied ids: {}".format(self._dead_units))
|
||||||
|
|
||||||
self.alive_units = {
|
self.alive_units = {
|
||||||
player.name: {k: v - self.destroyed_units[player.name].get(k, 0) for k, v in player_units.items()},
|
player.name: {k: v - self.destroyed_units[player.name].get(k, 0) for k, v in player_units.items()},
|
||||||
enemy.name: {k: v - self.destroyed_units[enemy.name].get(k, 0) for k, v in enemy_units.items()},
|
enemy.name: {k: v - self.destroyed_units[enemy.name].get(k, 0) for k, v in enemy_units.items()},
|
||||||
|
|||||||
@ -9,9 +9,14 @@ from tkinter.scrolledtext import *
|
|||||||
_version_string = None
|
_version_string = None
|
||||||
|
|
||||||
|
|
||||||
def _error_prompt():
|
class ShowLogsException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _error_prompt(oops=True):
|
||||||
tk = Tk()
|
tk = Tk()
|
||||||
Label(tk, text="Oops, something went wrong.").grid(row=0)
|
if oops:
|
||||||
|
Label(tk, text="Oops, something went wrong.").grid(row=0)
|
||||||
Label(tk, text="Please send following text to the developer:").grid(row=1)
|
Label(tk, text="Please send following text to the developer:").grid(row=1)
|
||||||
|
|
||||||
text = ScrolledText(tk)
|
text = ScrolledText(tk)
|
||||||
@ -22,7 +27,7 @@ def _error_prompt():
|
|||||||
|
|
||||||
def _handle_exception(self, exception: BaseException, *args):
|
def _handle_exception(self, exception: BaseException, *args):
|
||||||
logging.exception(exception)
|
logging.exception(exception)
|
||||||
_error_prompt()
|
_error_prompt(isinstance(exception, ShowLogsException))
|
||||||
|
|
||||||
|
|
||||||
def setup_version_string(str):
|
def setup_version_string(str):
|
||||||
|
|||||||
@ -17,11 +17,11 @@ def base_path() -> str:
|
|||||||
global _user_folder
|
global _user_folder
|
||||||
assert _user_folder
|
assert _user_folder
|
||||||
|
|
||||||
openbeta_path = os.path.join(_user_folder, "Saved Games", "DCS.openbeta")
|
openbeta_path = os.path.join(_user_folder, "DCS.openbeta")
|
||||||
if "--force-stable-DCS" not in sys.argv and os.path.exists(openbeta_path):
|
if "--force-stable-DCS" not in sys.argv and os.path.exists(openbeta_path):
|
||||||
return openbeta_path
|
return openbeta_path
|
||||||
else:
|
else:
|
||||||
return os.path.join(_user_folder, "Saved Games", "DCS")
|
return os.path.join(_user_folder, "DCS")
|
||||||
|
|
||||||
|
|
||||||
def _save_file() -> str:
|
def _save_file() -> str:
|
||||||
@ -44,11 +44,8 @@ def restore_game():
|
|||||||
if not _save_file_exists():
|
if not _save_file_exists():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
with open(_save_file(), "rb") as f:
|
||||||
with open(_save_file(), "rb") as f:
|
return pickle.load(f)
|
||||||
return pickle.load(f)
|
|
||||||
except Exception as e:
|
|
||||||
raise e
|
|
||||||
|
|
||||||
|
|
||||||
def save_game(game) -> bool:
|
def save_game(game) -> bool:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user