diff --git a/.idea/dcs_pmcliberation.iml b/.idea/dcs_pmcliberation.iml index c8efcbb4..9eedabcf 100644 --- a/.idea/dcs_pmcliberation.iml +++ b/.idea/dcs_pmcliberation.iml @@ -1,8 +1,10 @@ - - + + + + diff --git a/.idea/misc.xml b/.idea/misc.xml index 65531ca9..e524f659 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/__init__.py b/__init__.py index 97b25218..4cf782c5 100755 --- a/__init__.py +++ b/__init__.py @@ -35,7 +35,22 @@ theater.senaki.base.aa = { AirDefence.AAA_ZU_23_on_Ural_375: 2, } -op = game.mission.CaptureOperation.playerless(m, theater.kutaisi, theater.senaki) +""" +op = game.mission.CaptureOperation(m, m.country("USA"), m.country("Russia"), theater.senaki, theater.batumi, {A_10C: 2}, {F_15C: 2}, {Armor.MBT_M1A2_Abrams: 4}, {Su_27: 4}, {Armor.MBT_T_55: 4}, {}) +op.generate() +""" + +op = game.mission.InterceptOperation(m, + m.country("USA"), + m.country("Russia"), + theater.batumi, + m.terrain.batumi(), + escort={Su_27: 2}, + transport={An_26B: 2}, + interceptors={M_2000C: 2}) op.generate() +if not os.path.exists("./build"): + os.mkdir("./build") + m.save("build/output.miz") diff --git a/game/event.py b/game/event.py new file mode 100644 index 00000000..113e3a9e --- /dev/null +++ b/game/event.py @@ -0,0 +1,55 @@ +import typing + +import dcs + +from theater.controlpoint import * +from .mission import * + +class Event: + silent = False + operation = None # type: Operation + + def failure(self): + pass + + def success(self): + pass + +class InterceptEvent(Event): + pass + +class CaptureEvent(Event): + silent = True + + def __init__(self, from_cp: ControlPoint, to_cp: ControlPoint): + pass + + def player_defending(self, from_cp: ControlPoint, to_cp: ControlPoint, interceptors: typing.Dict[PlaneType, int]): + assert not self.operation + + cas = from_cp.base.scramble_cas(to_cp) + escort = from_cp.base.scramble_sweep(to_cp) + attackers = from_cp.base.assemble_cap(to_cp) + + self.operation = CaptureOperation(from_cp=from_cp, + to_cp=to_cp, + cas=cas, + escort=escort, + attack=attackers, + intercept=interceptors, + defense=to_cp.base.armor, + aa=to_cp.base.aa) + + def player_attacking(self, from_cp: ControlPoint, to_cp: ControlPoint, cas: typing.Dict[PlaneType, int], escort: typing.Dict[PlaneType, int], armor: typing.Dict[Armor, int]): + assert not self.operation + + interceptors = to_cp.base.scramble_sweep() + + self.operation = CaptureOperation(from_cp=from_cp, + to_cp=to_cp, + cas=cas, + escort=escort, + attack=armor, + intercept=interceptors, + defense=to_cp.base.armor, + aa=to_cp.base.aa) \ No newline at end of file diff --git a/game/game.py b/game/game.py index dc43daed..b0a74e78 100644 --- a/game/game.py +++ b/game/game.py @@ -2,9 +2,20 @@ import typing from theater.conflicttheater import * from theater.controlpoint import * +from .event import * class Game: + events = [] # type: typing.List[Event] + def __init__(self, theater: ConflictTheater): self.theater = theater + def _fill_cap_events(self): + for cp in [x for x in self.theater.controlpoints if x.captured]: + for connected_cp in [x for x in cp.connected_points if not x.captured]: + self.events.append(CaptureEvent(cp, connected_cp)) + + def pass_turn(self): + self.events = [] # type: typing.List[Event] + self._fill_cap_events() diff --git a/game/mission.py b/game/mission.py index 52d5fe70..1c95b10a 100644 --- a/game/mission.py +++ b/game/mission.py @@ -23,6 +23,8 @@ class Operation: class CaptureOperation(Operation): def __init__(self, mission: Mission, + attacker: Country, + defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, cas: typing.Dict[PlaneType, int], @@ -31,13 +33,7 @@ class CaptureOperation(Operation): intercept: typing.Dict[PlaneType, int], defense: typing.Dict[Armor, int], aa: typing.Dict[AirDefence, int]): - conflict = None - if from_cp.captured: - assert not to_cp.captured - conflict = to_cp.conflict_attack(from_cp, US, THEM) - else: - assert not from_cp.captured - conflict = to_cp.conflict_attack(from_cp, THEM, US) + conflict = to_cp.conflict_attack(from_cp, attacker, defender) super(CaptureOperation, self).__init__(mission, conflict) self.from_cp = from_cp @@ -51,49 +47,37 @@ class CaptureOperation(Operation): self.aa = aa - @classmethod - def player_defending(self, from_cp: ControlPoint, to_cp: ControlPoint, interceptors: typing.Dict[PlaneType, int]): - cas = from_cp.base.scramble_cas(to_cp) - escort = from_cp.base.scramble_sweep(to_cp) - attackers = from_cp.base.assemble_cap(to_cp) - - return CaptureOperation(from_cp=from_cp, - to_cp=to_cp, - cas=cas, - escort=escort, - attack=attackers, - intercept=interceptors, - defense=to_cp.base.armor, - aa=to_cp.base.aa) - - @classmethod - def player_attacking(self, from_cp: ControlPoint, to_cp: ControlPoint, cas: typing.Dict[PlaneType, int], escort: typing.Dict[PlaneType, int], armor: typing.Dict[Armor, int]): - interceptors = to_cp.base.scramble_sweep() - - return CaptureOperation(from_cp=from_cp, - to_cp=to_cp, - cas=cas, - escort=escort, - attack=armor, - intercept=interceptors, - defense=to_cp.base.armor, - aa=to_cp.base.aa) - - @classmethod - def playerless(self, mission: Mission, from_cp: ControlPoint, to_cp: ControlPoint): - return CaptureOperation(mission=mission, - from_cp=from_cp, - to_cp=to_cp, - cas=from_cp.base.scramble_cas(to_cp), - escort=from_cp.base.scramble_sweep(to_cp), - attack=from_cp.base.assemble_cap(to_cp), - intercept=to_cp.base.scramble_interceptors(0.5), - defense=to_cp.base.assemble_defense(0.5), - aa=to_cp.base.aa) - def generate(self): self.armorgen.generate(self.attack, self.defense) self.airgen.generate_cas(self.cas) - self.airgen.generate_escort(self.escort) - self.airgen.generate_interceptors(self.intercept) + self.airgen.generate_cas_escort(self.escort) + self.airgen.generate_defense(self.intercept) self.aagen.generate(self.aa) + +class InterceptOperation(Operation): + def __init__(self, + mission: Mission, + attacker: Country, + defender: Country, + destination: ControlPoint, + destination_port: Airport, + escort: typing.Dict[PlaneType, int], + transport: typing.Dict[PlaneType, int], + interceptors: typing.Dict[PlaneType, int]): + conflict = Conflict.intercept_conflict( + attacker=attacker, + defender=defender, + position=destination.position, + heading=0 + ) + + super(InterceptOperation, self).__init__(mission, conflict) + self.destination_port = destination_port + self.escort = escort + self.transport = transport + self.interceptors = interceptors + + def generate(self): + self.airgen.generate_transport(self.transport, self.destination_port) + self.airgen.generate_transport_escort(self.escort) + self.airgen.generate_interception(self.interceptors) diff --git a/gen/aircraft.py b/gen/aircraft.py index f5e4181e..db7d498e 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -19,9 +19,12 @@ from dcs.task import * SPREAD_DISTANCE_FACTOR = 1, 2 ESCORT_MAX_DIST = 30000 +WORKAROUND_WAYP_DIST = 1000 WARM_START_ALTITUDE = 6000 WARM_START_AIRSPEED = 300 + +INTERCEPT_ALT = 15000 CAS_ALTITUDE = 3000 INTERCEPT_MAX_DISTANCE_FACTOR = 15 @@ -49,6 +52,7 @@ class AircraftConflictGenerator: at: Point = None, airport: Airport = None) -> PlaneGroup: starttype = airport == None and StartType.Warm or StartType.Cold + print("generating {} ({}) at {} {}".format(unit, count, at, airport, side)) return self.m.flight_group( country=side, name=name, @@ -61,52 +65,112 @@ class AircraftConflictGenerator: start_type=starttype, group_size=count) + def _generate_escort(self, units: typing.Dict[PlaneType, int], airport: Airport, side: Country, location: Point): + assert len(self.escort_targets) > 0 + + for type, count in units.items(): + group = self._generate_group( + name=namegen.next_escort_group_name(), + side=side, + unit=type, + count=count, + at=location, + airport=airport) + + group.task = Escort.name + group.load_task_default_loadout(dcs.task.Escort) + + 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) + + for group in self.escort_targets: + wayp.tasks.append(EscortTaskAction(group.id, engagement_max_dist=ESCORT_MAX_DIST)) + def generate_cas(self, attackers: typing.Dict[PlaneType, int], airport: Airport = None): + assert len(self.escort_targets) == 0 + for type, count in attackers.items(): group = self._generate_group( name=namegen.next_cas_group_name(), side=self.conflict.attackers_side, unit=type, count=count, - at=airport == None and self._group_point(self.conflict.air_attackers_location) or None, + at=airport is None and self._group_point(self.conflict.air_attackers_location) or None, airport=airport) self.escort_targets.append(group) group.add_waypoint(self.conflict.position, CAS_ALTITUDE) group.task = CAS.name + group.load_task_default_loadout(CAS) - def generate_escort(self, attackers: typing.Dict[PlaneType, int], airport: Airport = None): - for type, count in attackers.items(): - group = self._generate_group( - name=namegen.next_escort_group_name(), - side=self.conflict.attackers_side, - unit=type, - count=count, - at=airport == None and self._group_point(self.conflict.air_attackers_location) or None, - airport=airport) + def generate_cas_escort(self, attackers: typing.Dict[PlaneType, int], airport: Airport = None): + self._generate_escort( + units=attackers, + airport=airport, + side=self.conflict.attackers_side, + location=airport is None and self._group_point(self.conflict.air_attackers_location) or None + ) - group.task = Escort.name - group.load_task_default_loadout(dcs.task.Escort.name) + def generate_transport_escort(self, escort: typing.Dict[PlaneType, int], airport: Airport = None): + self._generate_escort( + units=escort, + airport=airport, + side=self.conflict.defenders_side, + location=airport is None and self._group_point(self.conflict.air_defenders_location) or None + ) - heading = group.position.heading_between_point(self.conflict.position) - position = group.position # type: Point - wayp = group.add_waypoint(position.point_from_heading(heading, 3000), CAS_ALTITUDE) - - for group in self.escort_targets: - wayp.tasks.append(EscortTaskAction(group.id, engagement_max_dist=ESCORT_MAX_DIST)) - - def generate_interceptors(self, defenders: typing.Dict[PlaneType, int], airport: Airport = None): + def generate_defense(self, defenders: typing.Dict[PlaneType, int], airport: Airport = None): for type, count in defenders.items(): group = self._generate_group( name=namegen.next_intercept_group_name(), side=self.conflict.defenders_side, unit=type, count=count, - at=airport == None and self._group_point(self.conflict.air_defenders_location) or None, + at=airport is None and self._group_point(self.conflict.air_defenders_location) or None, airport=airport) group.task = FighterSweep.name - group.load_task_default_loadout(dcs.task.FighterSweep()) + group.load_task_default_loadout(FighterSweep) wayp = group.add_waypoint(self.conflict.position, CAS_ALTITUDE) wayp.tasks.append(dcs.task.EngageTargets(max_distance=self.conflict.size * INTERCEPT_MAX_DISTANCE_FACTOR)) wayp.tasks.append(dcs.task.OrbitAction()) + + def generate_transport(self, transport: typing.Dict[PlaneType, int], destination: Airport): + assert len(self.escort_targets) == 0 + + for type, count in transport.items(): + group = self._generate_group( + name=namegen.next_transport_group_name(), + side=self.conflict.defenders_side, + unit=type, + count=count, + at=self._group_point(self.conflict.air_defenders_location), + airport=None + ) + + group.task = Transport.name + + self.escort_targets.append(group) + group.land_at(destination) + + def generate_interception(self, interceptors: typing.Dict[PlaneType, int], airport: Airport = None): + for type, count in interceptors.items(): + group = self._generate_group( + name=namegen.next_intercept_group_name(), + side=self.conflict.attackers_side, + unit=type, + count=count, + at=airport is None and self._group_point(self.conflict.air_attackers_location) or None, + airport=airport + ) + + group.task = FighterSweep.name + group.load_task_default_loadout(FighterSweep) + + heading = group.position.heading_between_point(self.conflict.position) + initial_wayp = group.add_waypoint(group.position.point_from_heading(heading, WORKAROUND_WAYP_DIST), INTERCEPT_ALT) + initial_wayp.tasks.append(EngageTargets()) + + wayp = group.add_waypoint(self.conflict.position, 0) + wayp.tasks.append(EngageTargets()) diff --git a/gen/conflictgen.py b/gen/conflictgen.py index e9cac6f9..28378c38 100644 --- a/gen/conflictgen.py +++ b/gen/conflictgen.py @@ -2,6 +2,7 @@ import typing import pdb import dcs +from random import randint from dcs import Mission from dcs.mission import * @@ -17,18 +18,42 @@ def _opposite_heading(h): return h+180 GROUND_DISTANCE_FACTOR = 2 -AIR_DISTANCE_FACTOR = 5 +AIR_DISTANCE = 8000 + +INTERCEPT_ATTACKERS_HEADING = -45, 45 +INTERCEPT_DEFENDERS_HEADING = -10, 10 +INTERCEPT_ATTACKERS_DISTANCE = 60000 +INTERCEPT_DEFENDERS_DISTANCE = 30000 class Conflict: - def __init__(self, attacker: Country, attack_heading: int, defender: Country, defense_heading: int, position: Point, size: int): - self.attackers_side = attacker - self.defenders_side = defender - self.position = position - self.size = size + @classmethod + def capture_conflict(self, attacker: Country, attack_heading: int, defender: Country, defense_heading: int, position: Point, size: int): + instance = self() + instance.attackers_side = attacker + instance.defenders_side = defender + instance.position = position + instance.size = size - self.ground_attackers_location = self.position.point_from_heading(attack_heading, self.size * GROUND_DISTANCE_FACTOR) - self.ground_defenders_location = self.position.point_from_heading(defense_heading, self.size * GROUND_DISTANCE_FACTOR) + instance.ground_attackers_location = instance.position.point_from_heading(attack_heading, instance.size * GROUND_DISTANCE_FACTOR) + instance.ground_defenders_location = instance.position.point_from_heading(defense_heading, instance.size * GROUND_DISTANCE_FACTOR) - self.air_attackers_location = self.position.point_from_heading(attack_heading, self.size * AIR_DISTANCE_FACTOR) - self.air_defenders_location = self.position.point_from_heading(defense_heading, self.size * AIR_DISTANCE_FACTOR) + instance.air_attackers_location = instance.position.point_from_heading(attack_heading, AIR_DISTANCE) + instance.air_defenders_location = instance.position.point_from_heading(defense_heading, AIR_DISTANCE) + return instance + + @classmethod + def intercept_conflict(self, attacker: Country, defender: Country, position: Point, heading: int): + from theater.conflicttheater import SIZE_REGULAR + + instance = self() + instance.attackers_side = attacker + instance.defenders_side = defender + + instance.position = position + instance.size = SIZE_REGULAR + + instance.air_attackers_location = instance.position.point_from_heading(random.randint(*INTERCEPT_ATTACKERS_HEADING) + heading, INTERCEPT_ATTACKERS_DISTANCE) + instance.air_defenders_location = instance.position.point_from_heading(random.randint(*INTERCEPT_DEFENDERS_HEADING) + heading, INTERCEPT_DEFENDERS_DISTANCE) + + return instance diff --git a/gen/naming.py b/gen/naming.py index f80d363d..96fb9bbd 100644 --- a/gen/naming.py +++ b/gen/naming.py @@ -21,6 +21,10 @@ class NameGenerator: self.number += 1 return "AA Unit {}".format(self.number) + def next_transport_group_name(self): + self.number += 1 + return "Transport Unit {}".format(self.number) + namegen = NameGenerator() diff --git a/theater/controlpoint.py b/theater/controlpoint.py index 5f0f58cf..03497e00 100644 --- a/theater/controlpoint.py +++ b/theater/controlpoint.py @@ -5,7 +5,7 @@ import math from dcs.mapping import * from dcs.country import * -from gen.conflictgen import Conflict +from gen.conflictgen import * class ControlPoint: connected_points = [] # type: typing.List[ControlPoint] @@ -27,12 +27,12 @@ class ControlPoint: def connect(self, to): self.connected_points.append(to) - def find_radial(self, heading: int): + def find_radial(self, heading: int, ignored_radial: int = None): closest_radial = 0 closest_radial_delta = 360 - for radial in self.radials: + for radial in [x for x in self.radials if x != ignored_radial]: delta = math.fabs(radial - heading) - if closest_radial_delta < delta: + if delta < closest_radial_delta: closest_radial = radial closest_radial_delta = delta @@ -42,13 +42,13 @@ class ControlPoint: cp = from_cp # type: ControlPoint attack_radial = self.find_radial(cp.position.heading_between_point(self.position)) - defense_radial = self.find_radial(self.position.heading_between_point(cp.position)) + defense_radial = self.find_radial(self.position.heading_between_point(cp.position), ignored_radial=attack_radial) - return Conflict(attacker=attacker, - attack_heading=attack_radial, - defender=defender, - defense_heading=defense_radial, - position=self.position, - size=self.size) + return Conflict.capture_conflict(attacker=attacker, + attack_heading=attack_radial, + defender=defender, + defense_heading=defense_radial, + position=self.position, + size=self.size)