Merge branch 'develop'

This commit is contained in:
Vasyl Horbachenko 2018-10-22 02:18:26 +03:00
commit 9053408e13
81 changed files with 2407 additions and 1139 deletions

View File

@ -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()

View File

@ -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()

View File

@ -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 *

View File

@ -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

View File

@ -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())

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View 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

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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
View 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()

View File

@ -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

View File

@ -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

View File

@ -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
) )

View File

@ -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))

View File

@ -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,
) )

View File

@ -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
View 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)

View File

@ -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)

View File

@ -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
View 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))

View File

@ -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)),

View File

@ -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)

View File

@ -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
icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
resources/nevlandmap.p Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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")

View 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)

View 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)

View File

@ -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)

View File

@ -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")

Binary file not shown.

Binary file not shown.

View File

@ -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")

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

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

View File

@ -1,3 +1,3 @@
from .controlpoint import * from .controlpoint import *
from .conflicttheater import * from .conflicttheater import *
from .base import * from .base import *

View File

@ -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:

View File

@ -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)

View File

@ -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]

View File

@ -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

View File

@ -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)

View File

@ -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),
} }

View File

@ -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
"""

View File

@ -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)

View 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

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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()}

View File

@ -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()

View File

@ -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,

View File

@ -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
View 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}

View File

@ -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()

View File

@ -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()},

View File

@ -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):

View File

@ -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: