From 2b6227f3b14431fee36cdf32a0fde911ae2beb6b Mon Sep 17 00:00:00 2001 From: walterroach Date: Fri, 20 Nov 2020 20:29:30 -0600 Subject: [PATCH 1/9] remove dead code --- game/event/frontlineattack.py | 2 +- game/operation/frontlineattack.py | 38 ---- gen/conflictgen.py | 288 ------------------------------ qt_ui/widgets/QTopPanel.py | 2 +- 4 files changed, 2 insertions(+), 328 deletions(-) delete mode 100644 game/operation/frontlineattack.py diff --git a/game/event/frontlineattack.py b/game/event/frontlineattack.py index 0046526d..e38c45f6 100644 --- a/game/event/frontlineattack.py +++ b/game/event/frontlineattack.py @@ -3,7 +3,7 @@ from typing import List, Type from dcs.task import CAP, CAS, Task from game import db -from game.operation.frontlineattack import FrontlineAttackOperation +# from game.operation.frontlineattack import FrontlineAttackOperation from .event import Event from ..debriefing import Debriefing diff --git a/game/operation/frontlineattack.py b/game/operation/frontlineattack.py deleted file mode 100644 index 4dc18dae..00000000 --- a/game/operation/frontlineattack.py +++ /dev/null @@ -1,38 +0,0 @@ -from dcs.terrain.terrain import Terrain - -from gen.conflictgen import Conflict -from .operation import Operation -from .. import db - -MAX_DISTANCE_BETWEEN_GROUPS = 12000 - - -class FrontlineAttackOperation(Operation): - interceptors = None # type: db.AssignedUnitsDict - escort = None # type: db.AssignedUnitsDict - strikegroup = None # type: db.AssignedUnitsDict - - attackers = None # type: db.ArmorDict - defenders = None # type: db.ArmorDict - - def prepare(self, terrain: Terrain, is_quick: bool): - super(FrontlineAttackOperation, self).prepare(terrain, is_quick) - if self.defender_name == self.game.player_name: - self.attackers_starting_position = None - self.defenders_starting_position = None - - conflict = Conflict.frontline_cas_conflict( - attacker_name=self.attacker_name, - defender_name=self.defender_name, - attacker=self.current_mission.country(self.attacker_country), - defender=self.current_mission.country(self.defender_country), - 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): - super(FrontlineAttackOperation, self).generate() diff --git a/gen/conflictgen.py b/gen/conflictgen.py index 6a5a8e07..ecce03e4 100644 --- a/gen/conflictgen.py +++ b/gen/conflictgen.py @@ -109,24 +109,6 @@ class Conflict: def to_size(self): return self.to_cp.size * GROUND_DISTANCE_FACTOR - def find_insertion_point(self, other_point: Point) -> Point: - if self.is_vector: - dx = self.position.x - self.tail.x - 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 - if lerp < 0: - lerp = 0 - elif lerp > 1: - lerp = 1 - - x = lerp * dx + self.tail.x - y = lerp * dy + self.tail.y - return Point(x, y) - else: - return self.position - def find_ground_position(self, at: Point, heading: int, max_distance: int = 40000) -> Point: return Conflict._find_ground_position(at, max_distance, heading, self.theater) @@ -227,273 +209,3 @@ class Conflict: logging.error("Didn't find ground position ({})!".format(initial)) return initial - - @classmethod - def capture_conflict(cls, attacker_name: str, defender_name: str, 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 = GROUND_DISTANCE - attackers_location = position.point_from_heading(attack_heading, distance) - attackers_location = Conflict._find_ground_position(attackers_location, distance * 2, attack_heading, theater) - - defenders_location = position.point_from_heading(defense_heading, 0) - defenders_location = Conflict._find_ground_position(defenders_location, distance * 2, defense_heading, theater) - - return cls( - position=position, - theater=theater, - from_cp=from_cp, - to_cp=to_cp, - attackers_side=attacker_name, - defenders_side=defender_name, - attackers_country=attacker, - defenders_country=defender, - ground_attackers_location=attackers_location, - ground_defenders_location=defenders_location, - air_attackers_location=position.point_from_heading(attack_raw_heading, CAPTURE_AIR_ATTACKERS_DISTANCE), - air_defenders_location=position.point_from_heading(_opposite_heading(attack_raw_heading), CAPTURE_AIR_DEFENDERS_DISTANCE) - ) - - @classmethod - def strike_conflict(cls, attacker_name: str, defender_name: str, 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, int(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, int(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_name, - defenders_side=defender_name, - attackers_country=attacker, - defenders_country=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 - def intercept_position(cls, from_cp: ControlPoint, to_cp: ControlPoint) -> Point: - raw_distance = from_cp.position.distance_to_point(to_cp.position) * 1.5 - distance = max(min(raw_distance, INTERCEPT_MAX_DISTANCE), INTERCEPT_MIN_DISTANCE) - heading = _heading_sum(from_cp.position.heading_between_point(to_cp.position), random.choice([-1, 1]) * random.randint(60, 100)) - return from_cp.position.point_from_heading(heading, distance) - - @classmethod - def intercept_conflict(cls, attacker_name: str, defender_name: str, attacker: Country, defender: Country, position: Point, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): - heading = from_cp.position.heading_between_point(position) - return cls( - position=position.point_from_heading(position.heading_between_point(to_cp.position), INTERCEPT_CONFLICT_DISTANCE), - theater=theater, - from_cp=from_cp, - to_cp=to_cp, - attackers_side=attacker_name, - defenders_side=defender_name, - attackers_country=attacker, - defenders_country=defender, - ground_attackers_location=None, - ground_defenders_location=None, - air_attackers_location=position.point_from_heading(random.randint(*INTERCEPT_ATTACKERS_HEADING) + heading, INTERCEPT_ATTACKERS_DISTANCE), - air_defenders_location=position - ) - - @classmethod - def ground_attack_conflict(cls, attacker_name: str, defender_name: str, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): - heading = random.choice(to_cp.radials) - initial_location = to_cp.position.random_point_within(*GROUND_ATTACK_DISTANCE) - position = Conflict._find_ground_position(initial_location, GROUND_INTERCEPT_SPREAD, _heading_sum(heading, 180), theater) - if not 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) - - return cls( - position=position, - theater=theater, - from_cp=from_cp, - to_cp=to_cp, - attackers_side=attacker_name, - defenders_side=defender_name, - attackers_country=attacker, - defenders_country=defender, - ground_attackers_location=position, - ground_defenders_location=None, - air_attackers_location=None, - air_defenders_location=position.point_from_heading(heading, AIR_DISTANCE), - ) - - @classmethod - def convoy_strike_conflict(cls, attacker_name: str, defender_name: str, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): - frontline_position, frontline_heading, frontline_length = Conflict.frontline_vector(from_cp, to_cp, theater) - if not frontline_position: - assert False - - heading = frontline_heading - starting_position = Conflict._find_ground_position(frontline_position.point_from_heading(heading, 7000), - GROUND_INTERCEPT_SPREAD, - _opposite_heading(heading), theater) - if not starting_position: - starting_position = frontline_position - destination_position = frontline_position - else: - destination_position = frontline_position - - return cls( - position=destination_position, - theater=theater, - from_cp=from_cp, - to_cp=to_cp, - attackers_side=attacker_name, - defenders_side=defender_name, - attackers_country=attacker, - defenders_country=defender, - ground_attackers_location=None, - ground_defenders_location=starting_position, - air_attackers_location=starting_position.point_from_heading(_opposite_heading(heading), AIR_DISTANCE), - air_defenders_location=starting_position.point_from_heading(heading, AIR_DISTANCE), - ) - - @classmethod - def frontline_cas_conflict(cls, attacker_name: str, defender_name: str, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): - assert cls.has_frontline_between(from_cp, to_cp) - position, heading, distance = cls.frontline_vector(from_cp, to_cp, theater) - - return cls( - position=position, - heading=heading, - distance=distance, - theater=theater, - from_cp=from_cp, - to_cp=to_cp, - attackers_side=attacker_name, - defenders_side=defender_name, - attackers_country=attacker, - defenders_country=defender, - ground_attackers_location=None, - ground_defenders_location=None, - air_attackers_location=position.point_from_heading(random.randint(*INTERCEPT_ATTACKERS_HEADING) + heading, AIR_DISTANCE), - air_defenders_location=position.point_from_heading(random.randint(*INTERCEPT_ATTACKERS_HEADING) + _opposite_heading(heading), AIR_DISTANCE), - ) - - @classmethod - def frontline_cap_conflict(cls, attacker_name: str, defender_name: str, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): - assert cls.has_frontline_between(from_cp, to_cp) - - position, heading, distance = cls.frontline_vector(from_cp, to_cp, theater) - attack_position = position.point_from_heading(heading, random.randint(0, int(distance))) - attackers_position = attack_position.point_from_heading(heading - 90, AIR_DISTANCE) - defenders_position = attack_position.point_from_heading(heading + 90, random.randint(*CAP_CAS_DISTANCE)) - - return cls( - position=position, - heading=heading, - distance=distance, - theater=theater, - from_cp=from_cp, - to_cp=to_cp, - attackers_side=attacker_name, - defenders_side=defender_name, - attackers_country=attacker, - defenders_country=defender, - air_attackers_location=attackers_position, - air_defenders_location=defenders_position, - ) - - @classmethod - def ground_base_attack(cls, attacker_name: str, defender_name: str, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): - position = to_cp.position - attack_heading = to_cp.find_radial(to_cp.position.heading_between_point(from_cp.position)) - 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 - defenders_location = position.point_from_heading(defense_heading, distance) - defenders_location = Conflict._find_ground_position( - defenders_location, int(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_name, - defenders_side=defender_name, - attackers_country=attacker, - defenders_country=defender, - ground_attackers_location=None, - ground_defenders_location=defenders_location, - air_attackers_location=position.point_from_heading(attack_heading, AIR_DISTANCE), - air_defenders_location=position - ) - - @classmethod - def naval_intercept_position(cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): - radial = random.choice(to_cp.sea_radials) - - initial_distance = min(int(from_cp.position.distance_to_point(to_cp.position) * NAVAL_INTERCEPT_DISTANCE_FACTOR), NAVAL_INTERCEPT_DISTANCE_MAX) - initial_position = to_cp.position.point_from_heading(radial, initial_distance) - for offset in range(0, initial_distance, NAVAL_INTERCEPT_STEP): - position = initial_position.point_from_heading(_opposite_heading(radial), offset) - - if not theater.is_on_land(position): - break - return position - - @classmethod - def naval_intercept_conflict(cls, attacker_name: str, defender_name: str, attacker: Country, defender: Country, position: Point, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): - attacker_heading = from_cp.position.heading_between_point(to_cp.position) - return cls( - position=position, - theater=theater, - from_cp=from_cp, - to_cp=to_cp, - attackers_side=attacker_name, - defenders_side=defender_name, - attackers_country=attacker, - defenders_country=defender, - ground_attackers_location=None, - ground_defenders_location=position, - air_attackers_location=position.point_from_heading(attacker_heading, AIR_DISTANCE), - air_defenders_location=position.point_from_heading(_opposite_heading(attacker_heading), AIR_DISTANCE) - ) - - @classmethod - def transport_conflict(cls, attacker_name: str, defender_name: str, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): - frontline_position, heading = cls.frontline_position(from_cp, to_cp, theater) - initial_dest = frontline_position.point_from_heading(heading, TRANSPORT_FRONTLINE_DIST) - dest = cls._find_ground_position(initial_dest, from_cp.position.distance_to_point(to_cp.position) / 3, heading, theater) - if not dest: - 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) - - return cls( - position=dest, - theater=theater, - from_cp=from_cp, - to_cp=to_cp, - attackers_side=attacker_name, - defenders_side=defender_name, - attackers_country=attacker, - defenders_country=defender, - ground_attackers_location=from_cp.position, - ground_defenders_location=frontline_position, - air_attackers_location=from_cp.position.point_from_heading(0, 100), - air_defenders_location=frontline_position - ) \ No newline at end of file diff --git a/qt_ui/widgets/QTopPanel.py b/qt_ui/widgets/QTopPanel.py index 4f7e74bc..6202cb48 100644 --- a/qt_ui/widgets/QTopPanel.py +++ b/qt_ui/widgets/QTopPanel.py @@ -229,7 +229,7 @@ class QTopPanel(QFrame): game_event.is_awacs_enabled = True game_event.ca_slots = 1 game_event.departure_cp = self.game.theater.controlpoints[0] - game_event.player_attacking({CAS: {}, CAP: {}}) + # game_event.player_attacking({CAS: {}, CAP: {}}) game_event.depart_from = self.game.theater.controlpoints[0] self.game.initiate_event(game_event) From 9a640bf7eb5caffc72effe28c757c623edd581d9 Mon Sep 17 00:00:00 2001 From: walterroach <37820425+walterroach@users.noreply.github.com> Date: Fri, 20 Nov 2020 20:38:32 -0600 Subject: [PATCH 2/9] inherent resolve frontlines --- resources/campaigns/inherent_resolve.miz | Bin 45228 -> 45041 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/campaigns/inherent_resolve.miz b/resources/campaigns/inherent_resolve.miz index 6b1e4be3d6e13481feb6c6b6596d63b240dcec9d..cb4e2f875b1a7869e93ccb3e0cec6e6e3c134f56 100644 GIT binary patch literal 45041 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 bBJAG_ts;-|^!LQmM}Qtz#p`hj0pY&@HPVXf literal 45228 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 V00QFg0mNq*JWnO(^AHTo{{TnyR Date: Fri, 20 Nov 2020 21:01:45 -0600 Subject: [PATCH 3/9] remove dead code --- gen/conflictgen.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/gen/conflictgen.py b/gen/conflictgen.py index 2cfe8223..17e11beb 100644 --- a/gen/conflictgen.py +++ b/gen/conflictgen.py @@ -67,10 +67,7 @@ class Conflict: position: Point, heading=None, distance=None, - ground_attackers_location: Point = None, - ground_defenders_location: Point = None, - air_attackers_location: Point = None, - air_defenders_location: Point = None): + ): self.attackers_side = attackers_side self.defenders_side = defenders_side @@ -84,11 +81,6 @@ class Conflict: self.heading = heading self.distance = distance self.size = to_cp.size - self.radials = to_cp.radials - self.ground_attackers_location = ground_attackers_location - self.ground_defenders_location = ground_defenders_location - self.air_attackers_location = air_attackers_location - self.air_defenders_location = air_defenders_location @property def center(self) -> Point: From 708b615ad740c7fbcc849fd4c8ccd4a76a7d64cc Mon Sep 17 00:00:00 2001 From: walterroach <37820425+walterroach@users.noreply.github.com> Date: Sat, 21 Nov 2020 11:18:30 -0600 Subject: [PATCH 4/9] Merge conflict --- .../windows/mission/flight/waypoints/QFlightWaypointTab.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py index 6bc74eaa..791c7d5b 100644 --- a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py +++ b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py @@ -1,7 +1,4 @@ -<<<<<<< HEAD import logging -======= ->>>>>>> develop_2_2_x from typing import Iterable, List, Optional from PySide2.QtCore import Signal @@ -20,10 +17,7 @@ from gen.flights.flight import Flight, FlightType, FlightWaypoint from gen.flights.flightplan import ( CustomFlightPlan, FlightPlanBuilder, -<<<<<<< HEAD PlanningError, -======= ->>>>>>> develop_2_2_x StrikeFlightPlan, ) from qt_ui.windows.mission.flight.waypoints.QFlightWaypointList import \ From f6e0dbbb6a4c5ce56df13b8aaf3ee46631f531b1 Mon Sep 17 00:00:00 2001 From: walterroach <37820425+walterroach@users.noreply.github.com> Date: Sat, 21 Nov 2020 17:00:22 -0600 Subject: [PATCH 5/9] operation refactoring --- game/event/event.py | 2 +- game/event/frontlineattack.py | 15 +- game/operation/operation.py | 389 ++++++++++++++++++---------------- game/theater/controlpoint.py | 3 + gen/aircraft.py | 3 +- gen/armor.py | 29 ++- gen/conflictgen.py | 18 ++ gen/forcedoptionsgen.py | 9 +- gen/groundobjectsgen.py | 26 +-- gen/triggergen.py | 2 +- gen/visualgen.py | 3 +- qt_ui/widgets/QTopPanel.py | 3 +- 12 files changed, 268 insertions(+), 234 deletions(-) diff --git a/game/event/event.py b/game/event/event.py index db8e5ee6..acc929fe 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -92,7 +92,7 @@ class Event: 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) self.operation.generate() self.operation.current_mission.save(persistency.mission_path_for("liberation_nextturn.miz")) self.environment_settings = self.operation.environment_settings diff --git a/game/event/frontlineattack.py b/game/event/frontlineattack.py index e38c45f6..fa3d3416 100644 --- a/game/event/frontlineattack.py +++ b/game/event/frontlineattack.py @@ -1,11 +1,10 @@ from typing import List, Type from dcs.task import CAP, CAS, Task +from game.operation.operation import Operation -from game import db -# from game.operation.frontlineattack import FrontlineAttackOperation -from .event import Event from ..debriefing import Debriefing +from .event import Event class FrontlineAttackEvent(Event): @@ -38,12 +37,6 @@ class FrontlineAttackEvent(Event): if self.to_cp.captured: self.to_cp.base.affect_strength(-0.1) - def player_attacking(self, flights: db.TaskForceDict): + def player_attacking(self): assert self.departure_cp is not None - op = FrontlineAttackOperation(game=self.game, - attacker_name=self.attacker_name, - defender_name=self.defender_name, - from_cp=self.from_cp, - departure_cp=self.departure_cp, - to_cp=self.to_cp) - self.operation = op + self.operation = Operation(departure_cp=self.departure_cp,) diff --git a/game/operation/operation.py b/game/operation/operation.py index 1e01065b..32c13198 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -1,7 +1,9 @@ +from __future__ import annotations + import logging import os from pathlib import Path -from typing import List, Optional, Set +from typing import TYPE_CHECKING, Iterable, List, Optional, Set from dcs import Mission from dcs.action import DoScript, DoScriptFile @@ -9,11 +11,9 @@ from dcs.coalition import Coalition from dcs.countries import country_dict from dcs.lua.parse import loads from dcs.mapping import Point -from dcs.terrain.terrain import Terrain from dcs.translation import String from dcs.triggers import TriggerStart from dcs.unittype import UnitType - from game.plugins import LuaPluginManager from game.theater import ControlPoint from gen import Conflict, FlightType, VisualGenerator @@ -30,18 +30,19 @@ from gen.kneeboard import KneeboardGenerator from gen.radios import RadioFrequency, RadioRegistry from gen.tacan import TacanRegistry from gen.triggergen import TRIGGER_RADIUS_MEDIUM, TriggersGenerator + from .. import db from ..debriefing import Debriefing +if TYPE_CHECKING: + from game import Game + class Operation: attackers_starting_position = None # type: db.StartingPosition defenders_starting_position = None # type: db.StartingPosition current_mission = None # type: Mission - regular_mission = None # type: Mission - quick_mission = None # type: Mission - conflict = None # type: Conflict airgen = None # type: AircraftConflictGenerator triggersgen = None # type: TriggersGenerator airsupportgen = None # type: AirSupportConflictGenerator @@ -51,7 +52,7 @@ class Operation: forcedoptionsgen = None # type: ForcedOptionsGenerator radio_registry: Optional[RadioRegistry] = None tacan_registry: Optional[TacanRegistry] = None - + game = None # type: Game environment_settings = None trigger_radius = TRIGGER_RADIUS_MEDIUM is_quick = None @@ -59,23 +60,35 @@ class Operation: ca_slots = 0 def __init__(self, - game, - attacker_name: str, - defender_name: str, - from_cp: ControlPoint, departure_cp: ControlPoint, - to_cp: ControlPoint): - self.game = game - self.attacker_name = attacker_name - self.attacker_country = db.FACTIONS[attacker_name].country - self.defender_name = defender_name - self.defender_country = db.FACTIONS[defender_name].country - print(self.defender_country, self.attacker_country) - self.from_cp = from_cp + ): self.departure_cp = departure_cp - self.to_cp = to_cp - self.is_quick = False self.plugin_scripts: List[str] = [] + self.jtacs: List[JtacInfo] = [] + + @classmethod + def prepare(cls, game: Game): + with open("resources/default_options.lua", "r") as f: + options_dict = loads(f.read())["options"] + cls._set_mission(Mission(game.theater.terrain)) + cls.game = game + cls._setup_mission_coalitions() + cls.current_mission.options.load_from_dict(options_dict) + + @classmethod + def conflicts(cls) -> Iterable[Conflict]: + assert cls.game + for frontline in cls.game.theater.conflicts(): + yield Conflict( + cls.game.theater, + frontline.control_point_a, + frontline.control_point_b, + cls.game.player_name, + cls.game.enemy_name, + cls.game.player_country, + cls.game.enemy_country, + frontline.position + ) def units_of(self, country_name: str) -> List[UnitType]: return [] @@ -87,51 +100,21 @@ class Operation: def is_player_attack(self) -> bool: return self.from_cp.captured - def initialize(self, mission: Mission, conflict: Conflict): - self.current_mission = mission - self.conflict = conflict - # self.briefinggen = BriefingGenerator(self.current_mission, self.game) Is it safe to remove this, or does it also break save compat? + @classmethod + def _set_mission(cls, mission: Mission) -> None: + cls.current_mission = mission - def prepare(self, terrain: Terrain, is_quick: bool): - with open("resources/default_options.lua", "r") as f: - options_dict = loads(f.read())["options"] + @classmethod + def _setup_mission_coalitions(cls): + cls.current_mission.coalition["blue"] = Coalition("blue") + cls.current_mission.coalition["red"] = Coalition("red") - self.current_mission = Mission(terrain) - - print(self.game.player_country) - print(country_dict[db.country_id_from_name(self.game.player_country)]) - print(country_dict[db.country_id_from_name(self.game.player_country)]()) - - # Setup coalition : - self.current_mission.coalition["blue"] = Coalition("blue") - self.current_mission.coalition["red"] = Coalition("red") - - p_country = self.game.player_country - e_country = self.game.enemy_country - self.current_mission.coalition["blue"].add_country(country_dict[db.country_id_from_name(p_country)]()) - self.current_mission.coalition["red"].add_country(country_dict[db.country_id_from_name(e_country)]()) - - print([c for c in self.current_mission.coalition["blue"].countries.keys()]) - print([c for c in self.current_mission.coalition["red"].countries.keys()]) - - 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 - - if is_quick: - self.attackers_starting_position = None - self.defenders_starting_position = None - else: - self.attackers_starting_position = self.departure_cp.at - # TODO: Is this possible? - if self.to_cp is not None: - self.defenders_starting_position = self.to_cp.at - else: - self.defenders_starting_position = None + p_country = cls.game.player_country + e_country = cls.game.enemy_country + cls.current_mission.coalition["blue"].add_country( + country_dict[db.country_id_from_name(p_country)]()) + cls.current_mission.coalition["red"].add_country( + country_dict[db.country_id_from_name(e_country)]()) def inject_lua_trigger(self, contents: str, comment: str) -> None: trigger = TriggerStart(comment=comment) @@ -161,7 +144,8 @@ class Operation: trigger = TriggerStart(comment=f"Load {script_mnemonic}") filename = script_path.resolve() - fileref = self.current_mission.map_resource.add_resource_file(filename) + fileref = self.current_mission.map_resource.add_resource_file( + filename) trigger.add_action(DoScriptFile(fileref)) self.current_mission.triggerrules.triggers.append(trigger) @@ -171,13 +155,13 @@ class Operation: airsupportgen: AirSupportConflictGenerator, jtacs: List[JtacInfo], airgen: AircraftConflictGenerator, - ): + ): """Generates subscribed MissionInfoGenerator objects (currently kneeboards and briefings) """ gens: List[MissionInfoGenerator] = [ KneeboardGenerator(self.current_mission, self.game), BriefingGenerator(self.current_mission, self.game) - ] + ] for gen in gens: for dynamic_runway in groundobjectgen.runways.values(): gen.add_dynamic_runway(dynamic_runway) @@ -195,15 +179,46 @@ class Operation: for flight in airgen.flights: gen.add_flight(flight) gen.generate() + + @classmethod + def create_radio_registries(cls) -> None: + unique_map_frequencies = set() # type: Set[RadioFrequency] + cls._create_tacan_registry(unique_map_frequencies) + cls._create_radio_registry(unique_map_frequencies) + + def assign_channels_to_flights(self, flights: List[FlightData], + air_support: AirSupport) -> None: + """Assigns preset radio channels for client flights.""" + for flight in flights: + if not flight.client_units: + continue + self.assign_channels_to_flight(flight, air_support) - def generate(self): - radio_registry = RadioRegistry() - tacan_registry = TacanRegistry() + def assign_channels_to_flight(self, flight: FlightData, + air_support: AirSupport) -> None: + """Assigns preset radio channels for a client flight.""" + airframe = flight.aircraft_type + + try: + aircraft_data = AIRCRAFT_DATA[airframe.id] + except KeyError: + logging.warning(f"No aircraft data for {airframe.id}") + return + + if aircraft_data.channel_allocator is not None: + aircraft_data.channel_allocator.assign_channels_for_flight( + flight, air_support + ) + + @classmethod + def _create_tacan_registry(cls, unique_map_frequencies: Set[RadioFrequency]) -> None: + """ + Dedup beacon/radio frequencies, since some maps have some frequencies + used multiple times. + """ + cls.tacan_registry = TacanRegistry() + beacons = load_beacons_for_terrain(cls.game.theater.terrain.name) - # Dedup beacon/radio frequencies, since some maps have some frequencies - # used multiple times. - beacons = load_beacons_for_terrain(self.game.theater.terrain.name) - unique_map_frequencies: Set[RadioFrequency] = set() for beacon in beacons: unique_map_frequencies.add(beacon.frequency) if beacon.is_tacan: @@ -211,36 +226,35 @@ class Operation: logging.error( f"TACAN beacon has no channel: {beacon.callsign}") else: - tacan_registry.reserve(beacon.tacan_channel) + cls.tacan_registry.reserve(beacon.tacan_channel) - for airfield, data in AIRFIELD_DATA.items(): - if data.theater == self.game.theater.terrain.name: + @classmethod + def _create_radio_registry(cls, unique_map_frequencies: Set[RadioFrequency]) -> None: + cls.radio_registry = RadioRegistry() + for data in AIRFIELD_DATA.values(): + if data.theater == cls.game.theater.terrain.name: unique_map_frequencies.add(data.atc.hf) unique_map_frequencies.add(data.atc.vhf_fm) unique_map_frequencies.add(data.atc.vhf_am) unique_map_frequencies.add(data.atc.uhf) # No need to reserve ILS or TACAN because those are in the # beacon list. - + unique_map_frequencies: Set[RadioFrequency] = set() for frequency in unique_map_frequencies: - radio_registry.reserve(frequency) + cls.radio_registry.reserve(frequency) - # Set mission time and weather conditions. - EnvironmentGenerator(self.current_mission, - self.game.conditions).generate() - - # Generate ground object first - - groundobjectgen = GroundObjectsGenerator( - self.current_mission, - self.conflict, - self.game, - radio_registry, - tacan_registry + @classmethod + def _generate_ground_units(cls): + cls.groundobjectgen = GroundObjectsGenerator( + cls.current_mission, + cls.game, + cls.radio_registry, + cls.tacan_registry ) - groundobjectgen.generate() + cls.groundobjectgen.generate() - # Generate destroyed units + def _generate_destroyed_units(self) -> None: + """Add destroyed units to the Mission""" for d in self.game.get_destroyed_units(): try: utype = db.unit_type_from_name(d["type"]) @@ -250,7 +264,8 @@ class Operation: pos = Point(d["x"], d["z"]) if utype is not None and not self.game.position_culled(pos) and self.game.settings.perf_destroyed_units: self.current_mission.static_group( - country=self.current_mission.country(self.game.player_country), + country=self.current_mission.country( + self.game.player_country), name="", _type=utype, hidden=True, @@ -258,44 +273,25 @@ class Operation: heading=d["orientation"], dead=True, ) + + def generate(self): + self.create_radio_registries() + # Set mission time and weather conditions. + EnvironmentGenerator(self.current_mission, + self.game.conditions).generate() + self._generate_ground_units() + self._generate_destroyed_units() + self._generate_air_units() + self.assign_channels_to_flights(self.airgen.flights, + self.airsupportgen.air_support) + self._generate_ground_conflicts() - # Air Support (Tanker & Awacs) - airsupportgen = AirSupportConflictGenerator( - self.current_mission, self.conflict, self.game, radio_registry, - tacan_registry) - airsupportgen.generate(self.is_awacs_enabled) - - # Generate Activity on the map - airgen = AircraftConflictGenerator( - self.current_mission, self.conflict, self.game.settings, self.game, - radio_registry) - - airgen.generate_flights( - self.current_mission.country(self.game.player_country), - self.game.blue_ato, - groundobjectgen.runways - ) - airgen.generate_flights( - self.current_mission.country(self.game.enemy_country), - self.game.red_ato, - groundobjectgen.runways - ) - - # Generate ground units on frontline everywhere - jtacs: List[JtacInfo] = [] - for front_line in self.game.theater.conflicts(True): - player_cp = front_line.control_point_a - enemy_cp = front_line.control_point_b - conflict = Conflict.frontline_cas_conflict(self.attacker_name, self.defender_name, - self.current_mission.country(self.attacker_country), - self.current_mission.country(self.defender_country), - player_cp, enemy_cp, self.game.theater) - # Generate frontline ops - player_gp = self.game.ground_planners[player_cp.id].units_per_cp[enemy_cp.id] - enemy_gp = self.game.ground_planners[enemy_cp.id].units_per_cp[player_cp.id] - groundConflictGen = GroundConflictGenerator(self.current_mission, conflict, self.game, player_gp, enemy_gp, player_cp.stances[enemy_cp.id]) - groundConflictGen.generate() - jtacs.extend(groundConflictGen.jtacs) + # TODO: This is silly, once Bulls position is defined without Conflict this should be removed. + default_conflict = [i for i in self.conflicts()][0] + # Triggers + triggersgen = TriggersGenerator(self.current_mission, default_conflict, + self.game) + triggersgen.generate() # Setup combined arms parameters self.current_mission.groundControl.pilot_can_control_vehicles = self.ca_slots > 0 @@ -304,34 +300,32 @@ class Operation: else: self.current_mission.groundControl.red_tactical_commander = self.ca_slots - # Triggers - triggersgen = TriggersGenerator(self.current_mission, self.conflict, - self.game) - triggersgen.generate() - # Options - forcedoptionsgen = ForcedOptionsGenerator(self.current_mission, - self.conflict, self.game) + forcedoptionsgen = ForcedOptionsGenerator(self.current_mission, self.game) forcedoptionsgen.generate() # Generate Visuals Smoke Effects - visualgen = VisualGenerator(self.current_mission, self.conflict, - self.game) + visualgen = VisualGenerator(self.current_mission, self.game) if self.game.settings.perf_smoke_gen: visualgen.generate() - luaData = {} - luaData["AircraftCarriers"] = {} - luaData["Tankers"] = {} - luaData["AWACs"] = {} - luaData["JTACs"] = {} - luaData["TargetPoints"] = {} + self.notify_info_generators( + self.groundobjectgen, + self.airsupportgen, + self.jtacs, + self.airgen + ) - self.assign_channels_to_flights(airgen.flights, - airsupportgen.air_support) + luaData = { + "AircraftCarriers": {}, + "Tankers": {}, + "AWACs": {}, + "JTACs": {}, + "TargetPoints": {}, + } - for tanker in airsupportgen.air_support.tankers: - luaData["Tankers"][tanker.callsign] = { + for tanker in self.airsupportgen.air_support.tankers: + luaData["Tankers"][tanker.callsign] = { "dcsGroupName": tanker.dcsGroupName, "callsign": tanker.callsign, "variant": tanker.variant, @@ -340,15 +334,15 @@ class Operation: } if self.is_awacs_enabled: - for awacs in airsupportgen.air_support.awacs: - luaData["AWACs"][awacs.callsign] = { + for awacs in self.airsupportgen.air_support.awacs: + luaData["AWACs"][awacs.callsign] = { "dcsGroupName": awacs.dcsGroupName, "callsign": awacs.callsign, "radio": awacs.freq.mhz } - for jtac in jtacs: - luaData["JTACs"][jtac.callsign] = { + for jtac in self.jtacs: + luaData["JTACs"][jtac.callsign] = { "dcsGroupName": jtac.dcsGroupName, "callsign": jtac.callsign, "zone": jtac.region, @@ -356,7 +350,7 @@ class Operation: "laserCode": jtac.code } - for flight in airgen.flights: + for flight in self.airgen.flights: if flight.friendly and flight.flight_type in [FlightType.ANTISHIP, FlightType.DEAD, FlightType.SEAD, FlightType.STRIKE]: flightType = flight.flight_type.name flightTarget = flight.package.target @@ -365,14 +359,15 @@ class Operation: flightTargetType = None if hasattr(flightTarget, 'obj_name'): flightTargetName = flightTarget.obj_name - flightTargetType = flightType + f" TGT ({flightTarget.category})" + flightTargetType = flightType + \ + f" TGT ({flightTarget.category})" elif hasattr(flightTarget, 'name'): flightTargetName = flightTarget.name flightTargetType = flightType + " TGT (Airbase)" - luaData["TargetPoints"][flightTargetName] = { + luaData["TargetPoints"][flightTargetName] = { "name": flightTargetName, "type": flightTargetType, - "position": { "x": flightTarget.position.x, "y": flightTarget.position.y} + "position": {"x": flightTarget.position.x, "y": flightTarget.position.y} } # set a LUA table with data from Liberation that we want to set @@ -398,7 +393,7 @@ dcsLiberation.Tankers = { """ for key in luaData["Tankers"]: data = luaData["Tankers"][key] - dcsGroupName= data["dcsGroupName"] + dcsGroupName = data["dcsGroupName"] callsign = data["callsign"] variant = data["variant"] tacan = data["tacan"] @@ -415,7 +410,7 @@ dcsLiberation.AWACs = { """ for key in luaData["AWACs"]: data = luaData["AWACs"][key] - dcsGroupName= data["dcsGroupName"] + dcsGroupName = data["dcsGroupName"] callsign = data["callsign"] radio = data["radio"] lua += f" {{dcsGroupName='{dcsGroupName}', callsign='{callsign}', radio='{radio}' }}, \n" @@ -430,7 +425,7 @@ dcsLiberation.JTACs = { """ for key in luaData["JTACs"]: data = luaData["JTACs"][key] - dcsGroupName= data["dcsGroupName"] + dcsGroupName = data["dcsGroupName"] callsign = data["callsign"] zone = data["zone"] laserCode = data["laserCode"] @@ -467,7 +462,6 @@ dcsLiberation.TargetPoints = { """ - trigger = TriggerStart(comment="Set DCS Liberation data") trigger.add_action(DoScript(String(lua))) self.current_mission.triggerrules.triggers.append(trigger) @@ -478,30 +472,57 @@ dcsLiberation.TargetPoints = { plugin.inject_scripts(self) plugin.inject_configuration(self) - self.assign_channels_to_flights(airgen.flights, - airsupportgen.air_support) - self.notify_info_generators(groundobjectgen, airsupportgen, jtacs, airgen) + @classmethod + def _generate_air_units(cls) -> None: + """Generate the air units for the Operation""" + # TODO: this is silly, once AirSupportConflictGenerator doesn't require Conflict this can be removed. + default_conflict = [i for i in cls.conflicts()][0] - def assign_channels_to_flights(self, flights: List[FlightData], - air_support: AirSupport) -> None: - """Assigns preset radio channels for client flights.""" - for flight in flights: - if not flight.client_units: - continue - self.assign_channels_to_flight(flight, air_support) + # Air Support (Tanker & Awacs) + cls.airsupportgen = AirSupportConflictGenerator( + cls.current_mission, default_conflict, cls.game, cls.radio_registry, + cls.tacan_registry) + cls.airsupportgen.generate(cls.is_awacs_enabled) - def assign_channels_to_flight(self, flight: FlightData, - air_support: AirSupport) -> None: - """Assigns preset radio channels for a client flight.""" - airframe = flight.aircraft_type + # Generate Aircraft Activity on the map + cls.airgen = AircraftConflictGenerator( + cls.current_mission, cls.game.settings, cls.game, + cls.radio_registry) - try: - aircraft_data = AIRCRAFT_DATA[airframe.id] - except KeyError: - logging.warning(f"No aircraft data for {airframe.id}") - return + cls.airgen.generate_flights( + cls.current_mission.country(cls.game.player_country), + cls.game.blue_ato, + cls.groundobjectgen.runways + ) + cls.airgen.generate_flights( + cls.current_mission.country(cls.game.enemy_country), + cls.game.red_ato, + cls.groundobjectgen.runways + ) - if aircraft_data.channel_allocator is not None: - aircraft_data.channel_allocator.assign_channels_for_flight( - flight, air_support + def _generate_ground_conflicts(self) -> None: + """For each frontline in the Operation, generate the ground conflicts and JTACs""" + self.jtacs: List[JtacInfo] = [] + for front_line in self.game.theater.conflicts(True): + player_cp = front_line.control_point_a + enemy_cp = front_line.control_point_b + conflict = Conflict.frontline_cas_conflict( + self.game.player_name, + self.game.enemy_name, + self.current_mission.country(self.game.player_country), + self.current_mission.country(self.game.enemy_country), + player_cp, + enemy_cp, + self.game.theater ) + # Generate frontline ops + player_gp = self.game.ground_planners[player_cp.id].units_per_cp[enemy_cp.id] + enemy_gp = self.game.ground_planners[enemy_cp.id].units_per_cp[player_cp.id] + ground_conflict_gen = GroundConflictGenerator( + self.current_mission, + conflict, self.game, + player_gp, enemy_gp, + player_cp.stances[enemy_cp.id] + ) + ground_conflict_gen.generate() + self.jtacs.extend(ground_conflict_gen.jtacs) diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 476f831a..a1934b3f 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -160,6 +160,9 @@ class ControlPoint(MissionTarget): self.stances: Dict[int, CombatStance] = {} self.airport = None self.pending_unit_deliveries: Optional[UnitsDeliveryEvent] = None + + def __repr__(self): + return f"<{__class__}: {self.name}>" @property def ground_objects(self) -> List[TheaterGroundObject]: diff --git a/gen/aircraft.py b/gen/aircraft.py index 758bf0b8..ca970958 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -647,12 +647,11 @@ AIRCRAFT_DATA["P-47D-30"] = AIRCRAFT_DATA["P-51D"] class AircraftConflictGenerator: - def __init__(self, mission: Mission, conflict: Conflict, settings: Settings, + def __init__(self, mission: Mission, settings: Settings, game: Game, radio_registry: RadioRegistry): self.m = mission self.game = game self.settings = settings - self.conflict = conflict self.radio_registry = radio_registry self.flights: List[FlightData] = [] diff --git a/gen/armor.py b/gen/armor.py index 0e504021..085cfdfc 100644 --- a/gen/armor.py +++ b/gen/armor.py @@ -75,10 +75,33 @@ class GroundConflictGenerator: self.enemy_planned_combat_groups = enemy_planned_combat_groups self.player_planned_combat_groups = player_planned_combat_groups self.player_stance = CombatStance(player_stance) - self.enemy_stance = random.choice([CombatStance.AGGRESSIVE, CombatStance.AGGRESSIVE, CombatStance.AGGRESSIVE, CombatStance.ELIMINATION, CombatStance.BREAKTHROUGH]) if len(enemy_planned_combat_groups) > len(player_planned_combat_groups) else random.choice([CombatStance.DEFENSIVE, CombatStance.DEFENSIVE, CombatStance.DEFENSIVE, CombatStance.AMBUSH, CombatStance.AGGRESSIVE]) + self.enemy_stance = self._enemy_stance() self.game = game self.jtacs: List[JtacInfo] = [] + def _enemy_stance(self): + """Picks the enemy stance according to the number of planned groups on the frontline for each side""" + if len(self.enemy_planned_combat_groups) > len(self.player_planned_combat_groups): + return random.choice( + [ + CombatStance.AGGRESSIVE, + CombatStance.AGGRESSIVE, + CombatStance.AGGRESSIVE, + CombatStance.ELIMINATION, + CombatStance.BREAKTHROUGH + ] + ) + else: + return random.choice( + [ + CombatStance.DEFENSIVE, + CombatStance.DEFENSIVE, + CombatStance.DEFENSIVE, + CombatStance.AMBUSH, + CombatStance.AGGRESSIVE + ] + ) + def _group_point(self, point) -> Point: distance = random.randint( int(self.conflict.size * SPREAD_DISTANCE_FACTOR[0]), @@ -266,7 +289,7 @@ class GroundConflictGenerator: hold_2.number = 3 dcs_group.add_trigger_action(hold_2) - retreat_task = GoToWaypoint(toIndex=3) + retreat_task = GoToWaypoint(to_index=3) retreat_task.number = 4 dcs_group.add_trigger_action(retreat_task) @@ -362,7 +385,7 @@ class GroundConflictGenerator: dcs_group.add_waypoint(self.find_retreat_point(dcs_group, forward_heading, (int)(RETREAT_DISTANCE / 8)), PointAction.OffRoad) # Fallback task - fallback = ControlledTask(GoToWaypoint(toIndex=len(dcs_group.points))) + fallback = ControlledTask(GoToWaypoint(to_index=len(dcs_group.points))) fallback.enabled = False dcs_group.add_trigger_action(Hold()) dcs_group.add_trigger_action(fallback) diff --git a/gen/conflictgen.py b/gen/conflictgen.py index 17e11beb..136a0dff 100644 --- a/gen/conflictgen.py +++ b/gen/conflictgen.py @@ -157,6 +157,24 @@ class Conflict: return left_position, _heading_sum(heading, 90), int(right_position.distance_to_point(left_position)) + @classmethod + def frontline_cas_conflict(cls, attacker_name: str, defender_name: str, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): + assert cls.has_frontline_between(from_cp, to_cp) + position, heading, distance = cls.frontline_vector(from_cp, to_cp, theater) + + return cls( + position=position, + heading=heading, + distance=distance, + theater=theater, + from_cp=from_cp, + to_cp=to_cp, + attackers_side=attacker_name, + defenders_side=defender_name, + attackers_country=attacker, + defenders_country=defender, + ) + @classmethod def _extend_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point: pos = initial diff --git a/gen/forcedoptionsgen.py b/gen/forcedoptionsgen.py index 8a6684b2..dff54bc4 100644 --- a/gen/forcedoptionsgen.py +++ b/gen/forcedoptionsgen.py @@ -1,5 +1,7 @@ +from __future__ import annotations + import logging -import typing +from typing import TYPE_CHECKING from enum import IntEnum from dcs.mission import Mission @@ -7,6 +9,8 @@ from dcs.forcedoptions import ForcedOptions from .conflictgen import * +if TYPE_CHECKING: + from game.game import Game class Labels(IntEnum): Off = 0 @@ -16,9 +20,8 @@ class Labels(IntEnum): class ForcedOptionsGenerator: - def __init__(self, mission: Mission, conflict: Conflict, game): + def __init__(self, mission: Mission, game: Game): self.mission = mission - self.conflict = conflict self.game = game def _set_options_view(self): diff --git a/gen/groundobjectsgen.py b/gen/groundobjectsgen.py index 09385c78..e2c30846 100644 --- a/gen/groundobjectsgen.py +++ b/gen/groundobjectsgen.py @@ -353,40 +353,16 @@ class GroundObjectsGenerator: locations for spawning ground objects, determining their types, and creating the appropriate generators. """ - FARP_CAPACITY = 4 - def __init__(self, mission: Mission, conflict: Conflict, game: Game, + def __init__(self, mission: Mission, game: Game, radio_registry: RadioRegistry, tacan_registry: TacanRegistry): self.m = mission - self.conflict = conflict self.game = game self.radio_registry = radio_registry self.tacan_registry = tacan_registry self.icls_alloc = iter(range(1, 21)) self.runways: Dict[str, RunwayData] = {} - def generate_farps(self, number_of_units=1) -> Iterator[StaticGroup]: - if self.conflict.is_vector: - center = self.conflict.center - heading = self.conflict.heading - 90 - else: - center, heading = self.conflict.frontline_position(self.conflict.from_cp, self.conflict.to_cp, self.game.theater) - heading -= 90 - - initial_position = center.point_from_heading(heading, FARP_FRONTLINE_DISTANCE) - position = self.conflict.find_ground_position(initial_position, heading) - if not position: - position = initial_position - - 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_country), - name="FARP", - position=position, - ) - def generate(self): for cp in self.game.theater.controlpoints: if cp.captured: diff --git a/gen/triggergen.py b/gen/triggergen.py index ba87bb3e..5344a3b3 100644 --- a/gen/triggergen.py +++ b/gen/triggergen.py @@ -32,7 +32,7 @@ class Silence(Option): class TriggersGenerator: def __init__(self, mission: Mission, conflict: Conflict, game): self.mission = mission - self.conflict = conflict + self.conflict = conflict # TODO: Move conflict out of this class. Only needed for bullseye position self.game = game def _set_allegiances(self, player_coalition: str, enemy_coalition: str): diff --git a/gen/visualgen.py b/gen/visualgen.py index c2636ea6..97dbaa40 100644 --- a/gen/visualgen.py +++ b/gen/visualgen.py @@ -92,9 +92,8 @@ def turn_heading(heading, fac): class VisualGenerator: - def __init__(self, mission: Mission, conflict: Conflict, game: Game): + def __init__(self, mission: Mission, game: Game): self.mission = mission - self.conflict = conflict self.game = game def _generate_frontline_smokes(self): diff --git a/qt_ui/widgets/QTopPanel.py b/qt_ui/widgets/QTopPanel.py index 005962c3..bb010d32 100644 --- a/qt_ui/widgets/QTopPanel.py +++ b/qt_ui/widgets/QTopPanel.py @@ -233,8 +233,7 @@ class QTopPanel(QFrame): game_event.is_awacs_enabled = True game_event.ca_slots = 1 game_event.departure_cp = self.game.theater.controlpoints[0] - # game_event.player_attacking({CAS: {}, CAP: {}}) - game_event.depart_from = self.game.theater.controlpoints[0] + game_event.player_attacking() self.game.initiate_event(game_event) waiting = QWaitingForMissionResultWindow(game_event, self.game) From 939b6c468de72decc4adc18221a1117788e238be Mon Sep 17 00:00:00 2001 From: walterroach <37820425+walterroach@users.noreply.github.com> Date: Sat, 21 Nov 2020 17:19:54 -0600 Subject: [PATCH 6/9] refactoring --- game/operation/operation.py | 132 +++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 63 deletions(-) diff --git a/game/operation/operation.py b/game/operation/operation.py index 32c13198..e2b2923f 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -65,7 +65,7 @@ class Operation: self.departure_cp = departure_cp self.plugin_scripts: List[str] = [] self.jtacs: List[JtacInfo] = [] - + @classmethod def prepare(cls, game: Game): with open("resources/default_options.lua", "r") as f: @@ -74,7 +74,7 @@ class Operation: cls.game = game cls._setup_mission_coalitions() cls.current_mission.options.load_from_dict(options_dict) - + @classmethod def conflicts(cls) -> Iterable[Conflict]: assert cls.game @@ -179,13 +179,13 @@ class Operation: for flight in airgen.flights: gen.add_flight(flight) gen.generate() - + @classmethod def create_radio_registries(cls) -> None: unique_map_frequencies = set() # type: Set[RadioFrequency] cls._create_tacan_registry(unique_map_frequencies) cls._create_radio_registry(unique_map_frequencies) - + def assign_channels_to_flights(self, flights: List[FlightData], air_support: AirSupport) -> None: """Assigns preset radio channels for client flights.""" @@ -273,8 +273,9 @@ class Operation: heading=d["orientation"], dead=True, ) - + def generate(self): + """Build the final Mission to be exported""" self.create_radio_registries() # Set mission time and weather conditions. EnvironmentGenerator(self.current_mission, @@ -283,7 +284,7 @@ class Operation: self._generate_destroyed_units() self._generate_air_units() self.assign_channels_to_flights(self.airgen.flights, - self.airsupportgen.air_support) + self.airsupportgen.air_support) self._generate_ground_conflicts() # TODO: This is silly, once Bulls position is defined without Conflict this should be removed. @@ -301,7 +302,8 @@ class Operation: self.current_mission.groundControl.red_tactical_commander = self.ca_slots # Options - forcedoptionsgen = ForcedOptionsGenerator(self.current_mission, self.game) + forcedoptionsgen = ForcedOptionsGenerator( + self.current_mission, self.game) forcedoptionsgen.generate() # Generate Visuals Smoke Effects @@ -309,13 +311,72 @@ class Operation: if self.game.settings.perf_smoke_gen: visualgen.generate() + self.register_lua_plugins(self) + self.notify_info_generators( self.groundobjectgen, self.airsupportgen, self.jtacs, self.airgen - ) + ) + @classmethod + def _generate_air_units(cls) -> None: + """Generate the air units for the Operation""" + # TODO: this is silly, once AirSupportConflictGenerator doesn't require Conflict this can be removed. + default_conflict = [i for i in cls.conflicts()][0] + + # Air Support (Tanker & Awacs) + cls.airsupportgen = AirSupportConflictGenerator( + cls.current_mission, default_conflict, cls.game, cls.radio_registry, + cls.tacan_registry) + cls.airsupportgen.generate(cls.is_awacs_enabled) + + # Generate Aircraft Activity on the map + cls.airgen = AircraftConflictGenerator( + cls.current_mission, cls.game.settings, cls.game, + cls.radio_registry) + + cls.airgen.generate_flights( + cls.current_mission.country(cls.game.player_country), + cls.game.blue_ato, + cls.groundobjectgen.runways + ) + cls.airgen.generate_flights( + cls.current_mission.country(cls.game.enemy_country), + cls.game.red_ato, + cls.groundobjectgen.runways + ) + + def _generate_ground_conflicts(self) -> None: + """For each frontline in the Operation, generate the ground conflicts and JTACs""" + self.jtacs: List[JtacInfo] = [] + for front_line in self.game.theater.conflicts(True): + player_cp = front_line.control_point_a + enemy_cp = front_line.control_point_b + conflict = Conflict.frontline_cas_conflict( + self.game.player_name, + self.game.enemy_name, + self.current_mission.country(self.game.player_country), + self.current_mission.country(self.game.enemy_country), + player_cp, + enemy_cp, + self.game.theater + ) + # Generate frontline ops + player_gp = self.game.ground_planners[player_cp.id].units_per_cp[enemy_cp.id] + enemy_gp = self.game.ground_planners[enemy_cp.id].units_per_cp[player_cp.id] + ground_conflict_gen = GroundConflictGenerator( + self.current_mission, + conflict, self.game, + player_gp, enemy_gp, + player_cp.stances[enemy_cp.id] + ) + ground_conflict_gen.generate() + self.jtacs.extend(ground_conflict_gen.jtacs) + + def register_lua_plugins(self): + # TODO: Refactor this luaData = { "AircraftCarriers": {}, "Tankers": {}, @@ -471,58 +532,3 @@ dcsLiberation.TargetPoints = { if plugin.enabled: plugin.inject_scripts(self) plugin.inject_configuration(self) - - @classmethod - def _generate_air_units(cls) -> None: - """Generate the air units for the Operation""" - # TODO: this is silly, once AirSupportConflictGenerator doesn't require Conflict this can be removed. - default_conflict = [i for i in cls.conflicts()][0] - - # Air Support (Tanker & Awacs) - cls.airsupportgen = AirSupportConflictGenerator( - cls.current_mission, default_conflict, cls.game, cls.radio_registry, - cls.tacan_registry) - cls.airsupportgen.generate(cls.is_awacs_enabled) - - # Generate Aircraft Activity on the map - cls.airgen = AircraftConflictGenerator( - cls.current_mission, cls.game.settings, cls.game, - cls.radio_registry) - - cls.airgen.generate_flights( - cls.current_mission.country(cls.game.player_country), - cls.game.blue_ato, - cls.groundobjectgen.runways - ) - cls.airgen.generate_flights( - cls.current_mission.country(cls.game.enemy_country), - cls.game.red_ato, - cls.groundobjectgen.runways - ) - - def _generate_ground_conflicts(self) -> None: - """For each frontline in the Operation, generate the ground conflicts and JTACs""" - self.jtacs: List[JtacInfo] = [] - for front_line in self.game.theater.conflicts(True): - player_cp = front_line.control_point_a - enemy_cp = front_line.control_point_b - conflict = Conflict.frontline_cas_conflict( - self.game.player_name, - self.game.enemy_name, - self.current_mission.country(self.game.player_country), - self.current_mission.country(self.game.enemy_country), - player_cp, - enemy_cp, - self.game.theater - ) - # Generate frontline ops - player_gp = self.game.ground_planners[player_cp.id].units_per_cp[enemy_cp.id] - enemy_gp = self.game.ground_planners[enemy_cp.id].units_per_cp[player_cp.id] - ground_conflict_gen = GroundConflictGenerator( - self.current_mission, - conflict, self.game, - player_gp, enemy_gp, - player_cp.stances[enemy_cp.id] - ) - ground_conflict_gen.generate() - self.jtacs.extend(ground_conflict_gen.jtacs) From 58ba9e9d1d07eb744710323a2ffc666623b557fd Mon Sep 17 00:00:00 2001 From: walterroach <37820425+walterroach@users.noreply.github.com> Date: Sat, 21 Nov 2020 18:17:35 -0600 Subject: [PATCH 7/9] bad arg --- game/operation/operation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/operation/operation.py b/game/operation/operation.py index e2b2923f..019272bc 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -311,7 +311,7 @@ class Operation: if self.game.settings.perf_smoke_gen: visualgen.generate() - self.register_lua_plugins(self) + self.register_lua_plugins() self.notify_info_generators( self.groundobjectgen, From b32ca4f92f2cdbe92306823eb24430773def3280 Mon Sep 17 00:00:00 2001 From: walterroach <37820425+walterroach@users.noreply.github.com> Date: Sat, 21 Nov 2020 18:57:15 -0600 Subject: [PATCH 8/9] merge --- game/operation/operation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/operation/operation.py b/game/operation/operation.py index 1f950489..967953ac 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -311,7 +311,7 @@ class Operation: if self.game.settings.perf_smoke_gen: visualgen.generate() - self.generate_lua(airgen, airsupportgen, jtacs) + self.generate_lua(self.airgen, self.airsupportgen, self.jtacs) # Inject Plugins Lua Scripts and data for plugin in LuaPluginManager.plugins(): From 6147e9ac96db49560db3f41490d9fe8e66e59f9d Mon Sep 17 00:00:00 2001 From: walterroach <37820425+walterroach@users.noreply.github.com> Date: Sat, 21 Nov 2020 19:31:30 -0600 Subject: [PATCH 9/9] mypy cleanup --- game/operation/operation.py | 53 +++++++++---------------------------- 1 file changed, 12 insertions(+), 41 deletions(-) diff --git a/game/operation/operation.py b/game/operation/operation.py index 967953ac..ec1ee774 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -1,4 +1,5 @@ from __future__ import annotations +from game.theater.theatergroundobject import TheaterGroundObject import logging import os @@ -96,10 +97,6 @@ class Operation: def is_successfull(self, debriefing: Debriefing) -> bool: return True - @property - def is_player_attack(self) -> bool: - return self.from_cp.captured - @classmethod def _set_mission(cls, mission: Mission) -> None: cls.current_mission = mission @@ -233,15 +230,13 @@ class Operation: cls.radio_registry = RadioRegistry() for data in AIRFIELD_DATA.values(): if data.theater == cls.game.theater.terrain.name: - unique_map_frequencies.add(data.atc.hf) - unique_map_frequencies.add(data.atc.vhf_fm) - unique_map_frequencies.add(data.atc.vhf_am) - unique_map_frequencies.add(data.atc.uhf) - # No need to reserve ILS or TACAN because those are in the - # beacon list. - unique_map_frequencies: Set[RadioFrequency] = set() - for frequency in unique_map_frequencies: - cls.radio_registry.reserve(frequency) + if data.atc: + unique_map_frequencies.add(data.atc.hf) + unique_map_frequencies.add(data.atc.vhf_fm) + unique_map_frequencies.add(data.atc.vhf_am) + unique_map_frequencies.add(data.atc.uhf) + # No need to reserve ILS or TACAN because those are in the + # beacon list. @classmethod def _generate_ground_units(cls): @@ -328,30 +323,6 @@ class Operation: self.airgen ) - def assign_channels_to_flights(self, flights: List[FlightData], - air_support: AirSupport) -> None: - """Assigns preset radio channels for client flights.""" - for flight in flights: - if not flight.client_units: - continue - self.assign_channels_to_flight(flight, air_support) - - def assign_channels_to_flight(self, flight: FlightData, - air_support: AirSupport) -> None: - """Assigns preset radio channels for a client flight.""" - airframe = flight.aircraft_type - - try: - aircraft_data = AIRCRAFT_DATA[airframe.id] - except KeyError: - logging.warning(f"No aircraft data for {airframe.id}") - return - - if aircraft_data.channel_allocator is not None: - aircraft_data.channel_allocator.assign_channels_for_flight( - flight, air_support - ) - @classmethod def _generate_air_units(cls) -> None: """Generate the air units for the Operation""" @@ -359,6 +330,7 @@ class Operation: default_conflict = [i for i in cls.conflicts()][0] # Air Support (Tanker & Awacs) + assert cls.radio_registry and cls.tacan_registry cls.airsupportgen = AirSupportConflictGenerator( cls.current_mission, default_conflict, cls.game, cls.radio_registry, cls.tacan_registry) @@ -382,7 +354,6 @@ class Operation: def _generate_ground_conflicts(self) -> None: """For each frontline in the Operation, generate the ground conflicts and JTACs""" - self.jtacs: List[JtacInfo] = [] for front_line in self.game.theater.conflicts(True): player_cp = front_line.control_point_a enemy_cp = front_line.control_point_b @@ -417,7 +388,7 @@ class Operation: "AWACs": {}, "JTACs": {}, "TargetPoints": {}, - } + } # type: ignore for tanker in airsupportgen.air_support.tankers: luaData["Tankers"][tanker.callsign] = { @@ -455,7 +426,7 @@ class Operation: if flightTarget: flightTargetName = None flightTargetType = None - if hasattr(flightTarget, 'obj_name'): + if isinstance(flightTarget, TheaterGroundObject): flightTargetName = flightTarget.obj_name flightTargetType = flightType + \ f" TGT ({flightTarget.category})" @@ -563,4 +534,4 @@ class Operation: trigger = TriggerStart(comment="Set DCS Liberation data") trigger.add_action(DoScript(String(lua))) - self.current_mission.triggerrules.triggers.append(trigger) + Operation.current_mission.triggerrules.triggers.append(trigger)