Generate WW2 Ship groups, added B17 to allies. Implemented modifiable doctrine to setup flight generator.

This commit is contained in:
Khopa 2020-06-13 18:53:43 +02:00
parent 601375d06f
commit cd41bcf45c
26 changed files with 501 additions and 128 deletions

View File

@ -3,6 +3,7 @@
##Features/Improvements :
* **[Units/Factions]** Added P-47D-30 for factions allies_1944
* **[Units/Factions]** Added factions : Bluefor Coldwar, Germany 1944 Easy
* **[Campaign/Map]** Added a campaign in the Channel map
* **[Campaign/Map]** Changed the Normandy campaign map
@ -22,6 +23,7 @@
* **[Flight Planner]** Added STRIKE mission generator
* **[Flight Planner]** Added buttons to add autogenerated waypoints (ASCEND, DESCEND, RTB)
* **[Flight Planner]** Improved waypoint list
* **[Flight Planner]** WW2 factions uses different parameters for flight planning.
* **[Settings]** Added settings to disallow external views
* **[Settings]** Added settings to choose F10 Map mode (All, Allies only, Player only, Fog of War, Map Only)
@ -37,6 +39,9 @@
* **[Map]** Added "ww2 bunker" building (WW2)
* **[Map]** Added "ally camp" building (WW2)
* **[Map]** Added "V1 Launch Site" building (WW2)
* **[Map]** Added "A camp" building (WW2)
* **[Misc]** Made it possible to setup DCS Saved Games directory and DCS installation directory manually at first start
##Fixed issues :
@ -52,7 +57,7 @@
* **[Maps/Campaign]** Now using Vasiani airbase instead of Soganlung airport in North Caucasus campaign (More parking slots)
* **[Info Panel]** Message displayed on base capture event stated that the ennemy captured an airbase, while it was the player who captured it.
* **[Info Panel]** Message displayed on base capture event stated that the enemy captured an airbase, while it was the player who captured it.
* **[Map View]** Graphical glitch on map when one building of an objective was destroyed, but not the others

113
game/data/doctrine.py Normal file
View File

@ -0,0 +1,113 @@
from game.utils import nm_to_meter, feet_to_meter
MAX_NUMBER_OF_INTERCEPTION_GROUP = 3
MISSION_DURATION = 120 # in minutes
CAP_EVERY_X_MINUTES = 20
CAS_EVERY_X_MINUTES = 30
SEAD_EVERY_X_MINUTES = 40
STRIKE_EVERY_X_MINUTES = 40
INGRESS_EGRESS_DISTANCE = nm_to_meter(45)
INGRESS_ALT = feet_to_meter(20000)
EGRESS_ALT = feet_to_meter(20000)
PATROL_ALT_RANGE = (feet_to_meter(15000), feet_to_meter(33000))
NAV_ALT = 9144
PATTERN_ALTITUDE = feet_to_meter(5000)
CAP_DEFAULT_ENGAGE_DISTANCE = nm_to_meter(40)
MODERN_DOCTRINE = {
"GENERATORS":{
"CAS": True,
"CAP": True,
"SEAD": True,
"STRIKE": True,
"ANTISHIP": True,
},
"STRIKE_MAX_RANGE": 1500000,
"SEAD_MAX_RANGE": 1500000,
"CAP_EVERY_X_MINUTES": 20,
"CAS_EVERY_X_MINUTES": 30,
"SEAD_EVERY_X_MINUTES": 40,
"STRIKE_EVERY_X_MINUTES": 40,
"INGRESS_EGRESS_DISTANCE": nm_to_meter(45),
"INGRESS_ALT": feet_to_meter(20000),
"EGRESS_ALT": feet_to_meter(20000),
"PATROL_ALT_RANGE": (feet_to_meter(15000), feet_to_meter(33000)),
"PATTERN_ALTITUDE": feet_to_meter(5000),
"CAP_PATTERN_LENGTH": (nm_to_meter(15), nm_to_meter(40)),
"FRONTLINE_CAP_DISTANCE_FROM_FRONTLINE": (nm_to_meter(6), nm_to_meter(15)),
"CAP_DISTANCE_FROM_CP": (nm_to_meter(10), nm_to_meter(40)),
"MAX_NUMBER_OF_INTERCEPTION_GROUP": 3,
}
COLDWAR_DOCTRINE = {
"GENERATORS": {
"CAS": True,
"CAP": True,
"SEAD": True,
"STRIKE": True,
"ANTISHIP": True,
},
"STRIKE_MAX_RANGE": 1500000,
"SEAD_MAX_RANGE": 1500000,
"CAP_EVERY_X_MINUTES": 20,
"CAS_EVERY_X_MINUTES": 30,
"SEAD_EVERY_X_MINUTES": 40,
"STRIKE_EVERY_X_MINUTES": 40,
"INGRESS_EGRESS_DISTANCE": nm_to_meter(30),
"INGRESS_ALT": feet_to_meter(18000),
"EGRESS_ALT": feet_to_meter(18000),
"PATROL_ALT_RANGE": (feet_to_meter(10000), feet_to_meter(24000)),
"PATTERN_ALTITUDE": feet_to_meter(5000),
"CAP_PATTERN_LENGTH": (nm_to_meter(12), nm_to_meter(24)),
"FRONTLINE_CAP_DISTANCE_FROM_FRONTLINE": (nm_to_meter(2), nm_to_meter(8)),
"CAP_DISTANCE_FROM_CP": (nm_to_meter(8), nm_to_meter(25)),
"MAX_NUMBER_OF_INTERCEPTION_GROUP": 3,
}
WWII_DOCTRINE = {
"GENERATORS": {
"CAS": True,
"CAP": True,
"SEAD": False,
"STRIKE": True,
"ANTISHIP": True,
},
"STRIKE_MAX_RANGE": 1500000,
"SEAD_MAX_RANGE": 1500000,
"CAP_EVERY_X_MINUTES": 20,
"CAS_EVERY_X_MINUTES": 30,
"SEAD_EVERY_X_MINUTES": 40,
"STRIKE_EVERY_X_MINUTES": 40,
"INGRESS_EGRESS_DISTANCE": nm_to_meter(7),
"INGRESS_ALT": feet_to_meter(8000),
"EGRESS_ALT": feet_to_meter(8000),
"PATROL_ALT_RANGE": (feet_to_meter(4000), feet_to_meter(15000)),
"PATTERN_ALTITUDE": feet_to_meter(5000),
"CAP_PATTERN_LENGTH": (nm_to_meter(8), nm_to_meter(18)),
"FRONTLINE_CAP_DISTANCE_FROM_FRONTLINE": (nm_to_meter(1), nm_to_meter(6)),
"CAP_DISTANCE_FROM_CP": (nm_to_meter(5), nm_to_meter(15)),
"MAX_NUMBER_OF_INTERCEPTION_GROUP": 3,
}

View File

@ -162,6 +162,7 @@ PRICES = {
P_51D_30_NA: 6,
P_51D: 6,
P_47D_30: 6,
B_17G: 18,
# armor
Armor.APC_MTLB: 4,
@ -260,7 +261,6 @@ PRICES = {
Artillery.M12_GMC: 2,
Artillery.Sturmpanzer_IV_Brummbär: 2,
# ship
CV_1143_5_Admiral_Kuznetsov: 100,
CVN_74_John_C__Stennis: 100,
@ -350,6 +350,7 @@ UNIT_BY_TASK = {
A_20G,
P_47D_30,
Ju_88A4,
B_17G
],
Transport: [
IL_76MD,
@ -739,6 +740,8 @@ PLANE_PAYLOAD_OVERRIDES = {
L_39C:COMMON_OVERRIDE,
Su_17M4: COMMON_OVERRIDE,
F_4E: COMMON_OVERRIDE,
P_47D_30:COMMON_OVERRIDE,
B_17G: COMMON_OVERRIDE,
AH_64D:{
CAS: "AGM-114K*16"
@ -1053,4 +1056,5 @@ class DefaultLiveries:
OH_58D.Liveries = DefaultLiveries
F_16C_50.Liveries = DefaultLiveries
P_51D_30_NA.Liveries = DefaultLiveries
Ju_88A4.Liveries = DefaultLiveries
Ju_88A4.Liveries = DefaultLiveries
B_17G.Liveries = DefaultLiveries

View File

@ -1,7 +1,9 @@
from dcs.planes import *
from dcs.ships import Uboat_VIIC_U_flak, Schnellboot_type_S130
from dcs.vehicles import *
from game.data.building_data import WW2_GERMANY_BUILDINGS
from game.data.doctrine import WWII_DOCTRINE
Germany_1944 = {
"country": "Third Reich",
@ -32,11 +34,10 @@ Germany_1944 = {
Infantry.Infantry_Mauser_98,
AirDefence.AAA_8_8cm_Flak_36,
],
"shorad":[
"shorad": [
AirDefence.AAA_8_8cm_Flak_36,
],
"objects": WW2_GERMANY_BUILDINGS,
"doctrine": {
# TODO
}
"doctrine": WWII_DOCTRINE,
"boat": [Uboat_VIIC_U_flak, Schnellboot_type_S130]
}

View File

@ -1,7 +1,9 @@
from dcs.planes import *
from dcs.vehicles import *
from dcs.ships import *
from game.data.building_data import WW2_GERMANY_BUILDINGS
from game.data.doctrine import WWII_DOCTRINE
Germany_1944_Easy = {
"country": "Third Reich",
@ -29,7 +31,6 @@ Germany_1944_Easy = {
AirDefence.AAA_8_8cm_Flak_36,
],
"objects": WW2_GERMANY_BUILDINGS,
"doctrine": {
# TODO
}
"doctrine": WWII_DOCTRINE,
"boat": [Uboat_VIIC_U_flak, Schnellboot_type_S130]
}

View File

@ -3,6 +3,7 @@ from dcs.ships import *
from dcs.vehicles import *
from game.data.building_data import WW2_ALLIES_BUILDINGS
from game.data.doctrine import WWII_DOCTRINE
USA_1944 = {
"country": "USA",
@ -14,6 +15,7 @@ USA_1944 = {
SpitfireLFMkIX,
SpitfireLFMkIXCW,
A_20G,
B_17G,
Armor.MT_M4_Sherman,
Armor.MT_M4A4_Sherman_Firefly,
@ -38,5 +40,8 @@ USA_1944 = {
AirDefence.AAA_Bofors_40mm,
], "shorad":[
AirDefence.AAA_Bofors_40mm,
], "objects": WW2_ALLIES_BUILDINGS
],
"objects": WW2_ALLIES_BUILDINGS,
"doctrine": WWII_DOCTRINE,
"boat": [LS_Samuel_Chase]
}

View File

@ -73,6 +73,15 @@ class Game:
self.informations = []
self.informations.append(Information("Game Start", "-" * 40, 0))
@property
def player_faction(self):
return db.FACTIONS[self.player_name]
@property
def enemy_faction(self):
return db.FACTIONS[self.enemy_name]
def _roll(self, prob, mult):
if self.settings.version == "dev":
# always generate all events for dev

14
game/utils.py Normal file
View File

@ -0,0 +1,14 @@
def meter_to_feet(value_in_meter):
return int(3.28084 * value_in_meter)
def feet_to_meter(value_in_feet):
return int(float(value_in_feet)/3.048)
def meter_to_nm(value_in_meter):
return int(float(value_in_meter)*0.000539957)
def nm_to_meter(value_in_nm):
return int(float(value_in_nm)*1852)

View File

@ -5,38 +5,22 @@ from dcs.terrain.terrain import NoParkingSlotError
from dcs.triggers import TriggerOnce, Event
from game.settings import Settings
from gen.flights.ai_flight_planner import FlightPlanner, CAP_DEFAULT_ENGAGE_DISTANCE, nm_to_meter
from gen.flights.ai_flight_planner import FlightPlanner
from gen.flights.flight import Flight, FlightType, FlightWaypointType
from .conflictgen import *
from .naming import *
from .triggergen import TRIGGER_WAYPOINT_OFFSET
SPREAD_DISTANCE_FACTOR = 1, 2
ESCORT_ENGAGEMENT_MAX_DIST = 100000
WORKAROUND_WAYP_DIST = 1000
WARM_START_HELI_AIRSPEED = 120
WARM_START_HELI_ALT = 500
WARM_START_ALTITUDE = 3000
WARM_START_AIRSPEED = 550
INTERCEPTION_AIRSPEED = 1000
BARCAP_RACETRACK_DISTANCE = 20000
CAP_DURATION = 30 # minutes
ATTACK_CIRCLE_ALT = 1000
ATTACK_CIRCLE_DURATION = 15
CAS_ALTITUDE = 800
RTB_ALTITUDE = 800
RTB_DISTANCE = 5000
HELI_ALT = 500
TRANSPORT_LANDING_ALT = 2000
DEFENCE_ENGAGEMENT_MAX_DISTANCE = 60000
INTERCEPT_MAX_DISTANCE = 200000
class AircraftConflictGenerator:
escort_targets = [] # type: typing.List[typing.Tuple[FlyingGroup, int]]
@ -393,8 +377,8 @@ class AircraftConflictGenerator:
if not point.only_for_player or (point.only_for_player and flight.client_count > 0):
pt = group.add_waypoint(Point(point.x, point.y), point.alt)
if point.waypoint_type == FlightWaypointType.PATROL_TRACK:
action = OrbitAction(altitude=pt.alt, pattern=OrbitAction.OrbitPattern.RaceTrack)
pt.tasks.append(action)
action = ControlledTask(OrbitAction(altitude=pt.alt, pattern=OrbitAction.OrbitPattern.RaceTrack))
action.stop_after_duration(CAP_DURATION * 60)
#for tgt in point.targets:
# if hasattr(tgt, "position"):
# engagetgt = EngageTargetsInZone(tgt.position, radius=CAP_DEFAULT_ENGAGE_DISTANCE, targets=[Targets.All.Air])

19
gen/fleet/schnellboot.py Normal file
View File

@ -0,0 +1,19 @@
import random
from dcs.ships import Schnellboot_type_S130
from gen.sam.group_generator import GroupGenerator
class SchnellbootGroupGenerator(GroupGenerator):
def __init__(self, game, ground_object, faction):
super(SchnellbootGroupGenerator, self).__init__(game, ground_object)
self.faction = faction
def generate(self):
for i in range(random.randint(2, 4)):
self.add_unit(Schnellboot_type_S130, "Schnellboot" + str(i), self.position.x + i * random.randint(100, 250), self.position.y + (random.randint(100, 200)-100), self.heading)
self.get_generated_group().points[0].speed = 20

View File

@ -1,6 +1,35 @@
import random
from game import db
from gen.fleet.carrier_group import CarrierGroupGenerator
from gen.fleet.lha_group import LHAGroupGenerator
from dcs.ships import *
from gen.fleet.schnellboot import SchnellbootGroupGenerator
from gen.fleet.uboat import UBoatGroupGenerator
from gen.fleet.ww2lst import WW2LSTGroupGenerator
SHIP_MAP = {
Schnellboot_type_S130: SchnellbootGroupGenerator,
LS_Samuel_Chase: WW2LSTGroupGenerator,
Uboat_VIIC_U_flak: UBoatGroupGenerator
}
def generate_ship_group(game, ground_object, faction:str):
"""
This generate a ship group
:return: Nothing, but put the group reference inside the ground object
"""
faction = db.FACTIONS[faction]
if "boat" in faction.keys():
ships = faction["boat"]
if len(ships) > 0:
sam = random.choice(ships)
generator = SHIP_MAP[sam](game, ground_object, faction)
generator.generate()
return generator.get_generated_group()
return None
def generate_carrier_group(faction:str, game, ground_object):

19
gen/fleet/uboat.py Normal file
View File

@ -0,0 +1,19 @@
import random
from dcs.ships import Uboat_VIIC_U_flak
from gen.sam.group_generator import GroupGenerator
class UBoatGroupGenerator(GroupGenerator):
def __init__(self, game, ground_object, faction):
super(UBoatGroupGenerator, self).__init__(game, ground_object)
self.faction = faction
def generate(self):
for i in range(random.randint(2, 6)):
self.add_unit(Uboat_VIIC_U_flak, "Uboat" + str(i), self.position.x + i * random.randint(100, 250), self.position.y + (random.randint(100, 200)-100), self.heading)
self.get_generated_group().points[0].speed = 20

22
gen/fleet/ww2lst.py Normal file
View File

@ -0,0 +1,22 @@
import random
from dcs.ships import LS_Samuel_Chase, LST_Mk_II
from gen.sam.group_generator import GroupGenerator
class WW2LSTGroupGenerator(GroupGenerator):
def __init__(self, game, ground_object, faction):
super(WW2LSTGroupGenerator, self).__init__(game, ground_object)
self.faction = faction
def generate(self):
# Add LS Samuel Chase
self.add_unit(LS_Samuel_Chase, "SamuelChase", self.position.x, self.position.y, self.heading)
for i in range(random.randint(2, 4)):
self.add_unit(LST_Mk_II, "LST" + str(i), self.position.x + i * random.randint(400, 600), self.position.y, self.heading)
self.get_generated_group().points[0].speed = 20

View File

@ -3,46 +3,15 @@ import operator
import random
from game import db
from game.data.doctrine import MODERN_DOCTRINE
from game.data.radar_db import UNITS_WITH_RADAR
from game.utils import meter_to_feet, nm_to_meter
from gen import Conflict
from gen.flights.ai_flight_planner_db import INTERCEPT_CAPABLE, CAP_CAPABLE, CAS_CAPABLE, SEAD_CAPABLE, STRIKE_CAPABLE
from gen.flights.flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
def meter_to_feet(value_in_meter):
return int(3.28084 * value_in_meter)
def feet_to_meter(value_in_feet):
return int(float(value_in_feet)/3.048)
def meter_to_nm(value_in_meter):
return int(float(value_in_meter)*0.000539957)
def nm_to_meter(value_in_nm):
return int(float(value_in_nm)*1852)
# TODO : Ideally should be based on the aircraft type instead / Availability of fuel
STRIKE_MAX_RANGE = 1500000
SEAD_MAX_RANGE = 1500000
MAX_NUMBER_OF_INTERCEPTION_GROUP = 3
MISSION_DURATION = 120 # in minutes
CAP_EVERY_X_MINUTES = 20
CAS_EVERY_X_MINUTES = 30
SEAD_EVERY_X_MINUTES = 40
STRIKE_EVERY_X_MINUTES = 40
INGRESS_EGRESS_DISTANCE = nm_to_meter(45)
INGRESS_ALT = feet_to_meter(20000)
EGRESS_ALT = feet_to_meter(20000)
PATROL_ALT_RANGE = (feet_to_meter(15000), feet_to_meter(33000))
NAV_ALT = 9144
PATTERN_ALTITUDE = feet_to_meter(5000)
CAP_DEFAULT_ENGAGE_DISTANCE = nm_to_meter(40)
MISSION_DURATION = 120
class FlightPlanner:
@ -54,6 +23,17 @@ class FlightPlanner:
self.game = game
self.aircraft_inventory = {} # local copy of the airbase inventory
if from_cp.captured:
self.faction = self.game.player_faction
else:
self.faction = self.game.enemy_faction
if "doctrine" in self.faction.keys():
self.doctrine = self.faction["doctrine"]
else:
self.doctrine = MODERN_DOCTRINE
def reset(self):
"""
Reset the planned flights and available units
@ -111,7 +91,7 @@ class FlightPlanner:
"""
# At least try to generate one interceptor group
number_of_interceptor_groups = min(max(sum([v for k, v in self.aircraft_inventory.items()]) / 4, MAX_NUMBER_OF_INTERCEPTION_GROUP), 1)
number_of_interceptor_groups = min(max(sum([v for k, v in self.aircraft_inventory.items()]) / 4, self.doctrine["MAX_NUMBER_OF_INTERCEPTION_GROUP"]), 1)
possible_interceptors = [k for k in self.aircraft_inventory.keys() if k in INTERCEPT_CAPABLE]
if len(possible_interceptors) <= 0:
@ -144,7 +124,7 @@ class FlightPlanner:
inventory = dict({k: v for k, v in self.aircraft_inventory.items() if k in possible_aircraft})
offset = random.randint(0,5)
for i in range(int(MISSION_DURATION/CAP_EVERY_X_MINUTES)):
for i in range(int(MISSION_DURATION/self.doctrine["CAP_EVERY_X_MINUTES"])):
try:
unit = random.choice([k for k, v in inventory.items() if v >= 2])
@ -155,7 +135,7 @@ class FlightPlanner:
flight = Flight(unit, 2, self.from_cp, FlightType.CAP)
flight.points = []
flight.scheduled_in = offset + i*random.randint(CAP_EVERY_X_MINUTES-5, CAP_EVERY_X_MINUTES+5)
flight.scheduled_in = offset + i*random.randint(self.doctrine["CAP_EVERY_X_MINUTES"] - 5, self.doctrine["CAP_EVERY_X_MINUTES"] + 5)
if len(self._get_cas_locations()) > 0:
enemy_cp = random.choice(self._get_cas_locations())
@ -182,7 +162,7 @@ class FlightPlanner:
if len(cas_location) > 0:
offset = random.randint(0,5)
for i in range(int(MISSION_DURATION/CAS_EVERY_X_MINUTES)):
for i in range(int(MISSION_DURATION/self.doctrine["CAS_EVERY_X_MINUTES"])):
try:
unit = random.choice([k for k, v in inventory.items() if v >= 2])
@ -192,7 +172,7 @@ class FlightPlanner:
inventory[unit] = inventory[unit] - 2
flight = Flight(unit, 2, self.from_cp, FlightType.CAS)
flight.points = []
flight.scheduled_in = offset + i * random.randint(CAS_EVERY_X_MINUTES - 5, CAS_EVERY_X_MINUTES + 5)
flight.scheduled_in = offset + i * random.randint(self.doctrine["CAS_EVERY_X_MINUTES"] - 5, self.doctrine["CAS_EVERY_X_MINUTES"] + 5)
location = random.choice(cas_location)
self.generate_cas(flight, flight.from_cp, location)
@ -215,7 +195,7 @@ class FlightPlanner:
if len(self.potential_sead_targets) > 0:
offset = random.randint(0,5)
for i in range(int(MISSION_DURATION/SEAD_EVERY_X_MINUTES)):
for i in range(int(MISSION_DURATION/self.doctrine["SEAD_EVERY_X_MINUTES"])):
if len(self.potential_sead_targets) <= 0:
break
@ -229,7 +209,7 @@ class FlightPlanner:
flight = Flight(unit, 2, self.from_cp, random.choice([FlightType.SEAD, FlightType.DEAD]))
flight.points = []
flight.scheduled_in = offset + i*random.randint(SEAD_EVERY_X_MINUTES-5, SEAD_EVERY_X_MINUTES+5)
flight.scheduled_in = offset + i*random.randint(self.doctrine["SEAD_EVERY_X_MINUTES"] - 5, self.doctrine["SEAD_EVERY_X_MINUTES"] + 5)
location = self.potential_sead_targets[0][0]
self.potential_sead_targets.pop(0)
@ -254,7 +234,7 @@ class FlightPlanner:
if len(self.potential_strike_targets) > 0:
offset = random.randint(0,5)
for i in range(int(MISSION_DURATION/STRIKE_EVERY_X_MINUTES)):
for i in range(int(MISSION_DURATION/self.doctrine["STRIKE_EVERY_X_MINUTES"])):
if len(self.potential_strike_targets) <= 0:
break
@ -268,7 +248,7 @@ class FlightPlanner:
flight = Flight(unit, 2, self.from_cp, FlightType.STRIKE)
flight.points = []
flight.scheduled_in = offset + i*random.randint(SEAD_EVERY_X_MINUTES-5, SEAD_EVERY_X_MINUTES+5)
flight.scheduled_in = offset + i*random.randint(self.doctrine["STRIKE_EVERY_X_MINUTES"] - 5, self.doctrine["STRIKE_EVERY_X_MINUTES"] + 5)
location = self.potential_strike_targets[0][0]
self.potential_strike_targets.pop(0)
@ -306,7 +286,7 @@ class FlightPlanner:
distance = math.hypot(cp.position.x - self.from_cp.position.x,
cp.position.y - self.from_cp.position.y)
if distance > 2*STRIKE_MAX_RANGE:
if distance > 2*self.doctrine["STRIKE_MAX_RANGE"]:
# Then it's unlikely any child ground object is in range
return
@ -318,7 +298,7 @@ class FlightPlanner:
distance = math.hypot(cp.position.x - self.from_cp.position.x,
cp.position.y - self.from_cp.position.y)
if distance < SEAD_MAX_RANGE:
if distance < self.doctrine["SEAD_MAX_RANGE"]:
self.potential_strike_targets.append((g, distance))
added_group.append(g)
@ -339,7 +319,7 @@ class FlightPlanner:
cp.position.y - self.from_cp.position.y)
# Then it's unlikely any ground object is range
if distance > 2*SEAD_MAX_RANGE:
if distance > 2*self.doctrine["SEAD_MAX_RANGE"]:
return
for g in cp.ground_objects:
@ -347,8 +327,7 @@ class FlightPlanner:
if g.dcs_identifier == "AA":
# Check that there is at least one unit with a radar in the ground objects unit groups
number_of_units = sum([len([r for r in group.units if hasattr(db.unit_type_from_name(r.type), "detection_range")
and db.unit_type_from_name(r.type).detection_range > 1000]) for group in g.groups])
number_of_units = sum([len([r for r in group.units if db.unit_type_from_name(r.type) in UNITS_WITH_RADAR]) for group in g.groups])
if number_of_units <= 0:
continue
@ -356,7 +335,7 @@ class FlightPlanner:
distance = math.hypot(cp.position.x - self.from_cp.position.x,
cp.position.y - self.from_cp.position.y)
if distance < SEAD_MAX_RANGE:
if distance < self.doctrine["SEAD_MAX_RANGE"]:
self.potential_sead_targets.append((g, distance))
self.potential_sead_targets.sort(key=operator.itemgetter(1))
@ -385,8 +364,8 @@ class FlightPlanner:
ingress_heading = heading - 180 + 25
egress_heading = heading - 180 - 25
ingress_pos = location.position.point_from_heading(ingress_heading, INGRESS_EGRESS_DISTANCE)
ingress_point = FlightWaypoint(ingress_pos.x, ingress_pos.y, INGRESS_ALT)
ingress_pos = location.position.point_from_heading(ingress_heading, self.doctrine["INGRESS_EGRESS_DISTANCE"])
ingress_point = FlightWaypoint(ingress_pos.x, ingress_pos.y, self.doctrine["INGRESS_ALT"])
ingress_point.pretty_name = "INGRESS on " + location.obj_name
ingress_point.description = "INGRESS on " + location.obj_name
ingress_point.name = "INGRESS"
@ -428,8 +407,8 @@ class FlightPlanner:
ingress_point.targets.append(location)
flight.points.append(point)
egress_pos = location.position.point_from_heading(egress_heading, INGRESS_EGRESS_DISTANCE)
egress_point = FlightWaypoint(egress_pos.x, egress_pos.y, EGRESS_ALT)
egress_pos = location.position.point_from_heading(egress_heading, self.doctrine["INGRESS_EGRESS_DISTANCE"])
egress_point = FlightWaypoint(egress_pos.x, egress_pos.y, self.doctrine["EGRESS_ALT"])
egress_point.name = "EGRESS"
egress_point.pretty_name = "EGRESS from " + location.obj_name
egress_point.description = "EGRESS from " + location.obj_name
@ -447,22 +426,20 @@ class FlightPlanner:
Generate a barcap flight at a given location
:param flight: Flight to setup
:param for_cp: CP to protect
:param location: Location to protect in priority
:param location: Is the location to protect a frontline
"""
flight.flight_type = FlightType.BARCAP if for_cp.is_carrier else FlightType.CAP
patrol_alt = random.randint(PATROL_ALT_RANGE[0], PATROL_ALT_RANGE[1])
patrol_alt = random.randint(self.doctrine["PATROL_ALT_RANGE"][0], self.doctrine["PATROL_ALT_RANGE"][1])
if len(for_cp.ground_objects) > 0:
loc = random.choice(for_cp.ground_objects)
hdg = for_cp.position.heading_between_point(loc.position)
radius = nm_to_meter(random.randint(15, 40))
radius = random.randint(self.doctrine["CAP_PATTERN_LENGTH"][0], self.doctrine["CAP_PATTERN_LENGTH"][1])
orbit0p = loc.position.point_from_heading(hdg - 90, radius)
orbit1p = loc.position.point_from_heading(hdg + 90, radius)
else:
loc = for_cp.position.point_from_heading(random.randint(0, 360), random.randint(nm_to_meter(10), nm_to_meter(40)))
loc = for_cp.position.point_from_heading(random.randint(0, 360), random.randint(self.doctrine["CAP_DISTANCE_FROM_CP"][0], self.doctrine["CAP_DISTANCE_FROM_CP"][1]))
hdg = for_cp.position.heading_between_point(loc)
radius = nm_to_meter(random.randint(15, 40))
radius = random.randint(self.doctrine["CAP_PATTERN_LENGTH"][0], self.doctrine["CAP_PATTERN_LENGTH"][1])
orbit0p = loc.point_from_heading(hdg - 90, radius)
orbit1p = loc.point_from_heading(hdg + 90, radius)
@ -507,7 +484,7 @@ class FlightPlanner:
:param enemy_cp: Enemy connected cp
"""
flight.flight_type = FlightType.CAP
patrol_alt = random.randint(PATROL_ALT_RANGE[0], PATROL_ALT_RANGE[1])
patrol_alt = random.randint(self.doctrine["PATROL_ALT_RANGE"][0], self.doctrine["PATROL_ALT_RANGE"][1])
# Find targets waypoints
ingress, heading, distance = Conflict.frontline_vector(ally_cp, enemy_cp, self.game.theater)
@ -563,8 +540,8 @@ class FlightPlanner:
ingress_heading = heading - 180 + 25
egress_heading = heading - 180 - 25
ingress_pos = location.position.point_from_heading(ingress_heading, INGRESS_EGRESS_DISTANCE)
ingress_point = FlightWaypoint(ingress_pos.x, ingress_pos.y, INGRESS_ALT)
ingress_pos = location.position.point_from_heading(ingress_heading, self.doctrine["INGRESS_EGRESS_DISTANCE"])
ingress_point = FlightWaypoint(ingress_pos.x, ingress_pos.y, self.doctrine["INGRESS_ALT"])
ingress_point.name = "INGRESS"
ingress_point.pretty_name = "INGRESS on " + location.obj_name
ingress_point.description = "INGRESS on " + location.obj_name
@ -599,8 +576,8 @@ class FlightPlanner:
ingress_point.targets.append(location)
flight.points.append(point)
egress_pos = location.position.point_from_heading(egress_heading, INGRESS_EGRESS_DISTANCE)
egress_point = FlightWaypoint(egress_pos.x, egress_pos.y, EGRESS_ALT)
egress_pos = location.position.point_from_heading(egress_heading, self.doctrine["INGRESS_EGRESS_DISTANCE"])
egress_point = FlightWaypoint(egress_pos.x, egress_pos.y, self.doctrine["EGRESS_ALT"])
egress_point.name = "INGRESS"
egress_point.pretty_name = "EGRESS from " + location.obj_name
egress_point.description = "EGRESS from " + location.obj_name
@ -670,11 +647,11 @@ class FlightPlanner:
"""
ascend_heading = from_cp.heading
pos_ascend = from_cp.position.point_from_heading(ascend_heading, 10000)
ascend = FlightWaypoint(pos_ascend.x, pos_ascend.y, PATTERN_ALTITUDE)
ascend = FlightWaypoint(pos_ascend.x, pos_ascend.y, self.doctrine["PATTERN_ALTITUDE"])
ascend.name = "ASCEND"
ascend.alt_type = "RADIO"
ascend.description = "Ascend to alt [" + str(meter_to_feet(PATTERN_ALTITUDE)) + " ft AGL], then proceed to next waypoint"
ascend.pretty_name = "Ascend to alt [" + str(meter_to_feet(PATTERN_ALTITUDE)) + " ft AGL]"
ascend.description = "Ascend to alt [" + str(meter_to_feet(self.doctrine["PATTERN_ALTITUDE"])) + " ft AGL], then proceed to next waypoint"
ascend.pretty_name = "Ascend to alt [" + str(meter_to_feet(self.doctrine["PATTERN_ALTITUDE"])) + " ft AGL]"
ascend.waypoint_type = FlightWaypointType.ASCEND_POINT
return ascend
@ -687,11 +664,11 @@ class FlightPlanner:
"""
ascend_heading = from_cp.heading
descend = from_cp.position.point_from_heading(ascend_heading - 180, 10000)
descend = FlightWaypoint(descend.x, descend.y, PATTERN_ALTITUDE)
descend = FlightWaypoint(descend.x, descend.y, self.doctrine["PATTERN_ALTITUDE"])
descend.name = "DESCEND"
descend.alt_type = "RADIO"
descend.description = "Descend to pattern alt [" + str(meter_to_feet(PATTERN_ALTITUDE)) + " ft AGL], contact tower, and land"
descend.pretty_name = "Descend to pattern alt [" + str(meter_to_feet(PATTERN_ALTITUDE)) + " ft AGL]"
descend.description = "Descend to pattern alt [" + str(meter_to_feet(self.doctrine["PATTERN_ALTITUDE"])) + " ft AGL], contact tower, and land"
descend.pretty_name = "Descend to pattern alt [" + str(meter_to_feet(self.doctrine["PATTERN_ALTITUDE"])) + " ft AGL]"
descend.waypoint_type = FlightWaypointType.DESCENT_POINT
return descend

View File

@ -171,6 +171,7 @@ STRIKE_CAPABLE = [
P_51D,
P_47D_30,
A_20G,
B_17G,
SpitfireLFMkIXCW,
SpitfireLFMkIX,

View File

@ -66,21 +66,35 @@ class GroundObjectsGenerator:
utype = unit_type_from_name(g.units[0].type)
vg = self.m.vehicle_group(side, g.name, utype, position=g.position, heading=g.units[0].heading)
vg.units[0].name = self.m.string(g.units[0].name)
for i, u in enumerate(g.units):
if i > 0:
vehicle = Vehicle(self.m.next_unit_id(), self.m.string(u.name), u.type)
vehicle.position.x = u.position.x
vehicle.position.y = u.position.y
vehicle.heading = u.heading
vg.add_unit(vehicle)
if not ground_object.sea_object:
vg = self.m.vehicle_group(side, g.name, utype, position=g.position, heading=g.units[0].heading)
vg.units[0].name = self.m.string(g.units[0].name)
for i, u in enumerate(g.units):
if i > 0:
vehicle = Vehicle(self.m.next_unit_id(), self.m.string(u.name), u.type)
vehicle.position.x = u.position.x
vehicle.position.y = u.position.y
vehicle.heading = u.heading
vg.add_unit(vehicle)
else:
vg = self.m.ship_group(side, g.name, utype, position=g.position,
heading=g.units[0].heading)
vg.units[0].name = self.m.string(g.units[0].name)
for i, u in enumerate(g.units):
utype = unit_type_from_name(u.type)
if i > 0:
ship = Ship(self.m.next_unit_id(), self.m.string(u.name), utype)
ship.position.x = u.position.x
ship.position.y = u.position.y
ship.heading = u.heading
vg.add_unit(ship)
if self.game.settings.perf_red_alert_state:
vg.points[0].tasks.append(OptAlarmState(2))
else:
vg.points[0].tasks.append(OptAlarmState(1))
elif ground_object.dcs_identifier in ["CARRIER", "LHA"]:
for g in ground_object.groups:
if len(g.units) > 0:

View File

@ -15,7 +15,7 @@ class FlakGenerator(GroupGenerator):
grid_x = random.randint(2, 4)
grid_y = random.randint(2, 4)
spacing = random.randint(10,40)
spacing = random.randint(30,60)
index = 0
mixed = random.choice([True, False])
@ -25,8 +25,8 @@ class FlakGenerator(GroupGenerator):
for j in range(grid_y):
index = index+1
self.add_unit(unit_type, "AAA#" + str(index),
self.position.x + spacing*i,
self.position.y + spacing*j, self.heading)
self.position.x + spacing*i + random.randint(1,5),
self.position.y + spacing*j + random.randint(1,5), self.heading)
if(mixed):
unit_type = random.choice(GFLAK)
@ -35,5 +35,5 @@ class FlakGenerator(GroupGenerator):
for i in range(grid_x):
for j in range(grid_y):
self.add_unit(Unarmed.Blitz_3_6_6700A, "AAA#" + str(index),
self.position.x + 200 + 9*i,
self.position.y + 9*j, 90)
self.position.x + 200 + 15*i + random.randint(1,5),
self.position.y + 15*j + random.randint(1,5), 90)

View File

@ -43,6 +43,7 @@ class NewGameWizard(QtWidgets.QWizard):
isTerrainNormandy = self.field("isTerrainNormandy")
isTerrainNormandySmall = self.field("isTerrainNormandySmall")
isTerrainChannel = self.field("isTerrainChannel")
isTerrainChannelComplete = self.field("isTerrainChannelComplete")
isTerrainEmirates = self.field("isTerrainEmirates")
timePeriod = db.TIME_PERIODS[list(db.TIME_PERIODS.keys())[self.field("timePeriod")]]
midGame = self.field("midGame")
@ -71,6 +72,8 @@ class NewGameWizard(QtWidgets.QWizard):
conflicttheater = normandy.NormandySmall()
elif isTerrainChannel:
conflicttheater = thechannel.ChannelTheater()
elif isTerrainChannelComplete:
conflicttheater = thechannel.ChannelTheaterComplete()
else:
conflicttheater = caucasus.CaucasusTheater()
@ -248,8 +251,10 @@ class TheaterConfiguration(QtWidgets.QWizardPage):
terrainNormandy.setIcon(QtGui.QIcon(CONST.ICONS["Terrain_Normandy"]))
terrainNormandySmall = QtWidgets.QRadioButton("Normandy Small")
terrainNormandySmall.setIcon(QtGui.QIcon(CONST.ICONS["Terrain_Normandy"]))
terrainChannel = QtWidgets.QRadioButton("Channel")
terrainChannel = QtWidgets.QRadioButton("The Channel : Start in Dunkirk")
terrainChannel.setIcon(QtGui.QIcon(CONST.ICONS["Terrain_Channel"]))
terrainChannelComplete = QtWidgets.QRadioButton("The Channel : Battle of Britain")
terrainChannelComplete.setIcon(QtGui.QIcon(CONST.ICONS["Terrain_Channel"]))
terrainCaucasusSmall.setChecked(True)
# Time Period
@ -273,6 +278,7 @@ class TheaterConfiguration(QtWidgets.QWizardPage):
self.registerField('isTerrainNormandy', terrainNormandy)
self.registerField('isTerrainNormandySmall', terrainNormandySmall)
self.registerField('isTerrainChannel', terrainChannel)
self.registerField('isTerrainChannelComplete', terrainChannelComplete)
self.registerField('timePeriod', timePeriodSelect)
# Build layout
@ -287,6 +293,7 @@ class TheaterConfiguration(QtWidgets.QWizardPage):
terrainGroupLayout.addWidget(terrainNttr)
terrainGroupLayout.addWidget(terrainNormandy)
terrainGroupLayout.addWidget(terrainNormandySmall)
terrainGroupLayout.addWidget(terrainChannelComplete)
terrainGroupLayout.addWidget(terrainChannel)
terrainGroup.setLayout(terrainGroupLayout)

View File

@ -3,7 +3,7 @@ from PySide2.QtWidgets import QLabel, QHBoxLayout, QVBoxLayout, QGroupBox
from dcs import Point
from game import Game
from gen.flights.ai_flight_planner import meter_to_nm
from game.utils import meter_to_nm
from gen.flights.flight import Flight
from qt_ui.widgets.combos.QPredefinedWaypointSelectionComboBox import QPredefinedWaypointSelectionComboBox
from qt_ui.windows.mission.flight.generator.QAbstractMissionGenerator import QAbstractMissionGenerator

View File

@ -2,7 +2,7 @@ from PySide2.QtGui import Qt
from PySide2.QtWidgets import QLabel, QHBoxLayout, QVBoxLayout, QGroupBox
from game import Game
from gen.flights.ai_flight_planner import meter_to_nm
from game.utils import meter_to_nm
from gen.flights.flight import Flight
from qt_ui.widgets.combos.QSEADTargetSelectionComboBox import QSEADTargetSelectionComboBox
from qt_ui.widgets.views.QSeadTargetInfoView import QSeadTargetInfoView

View File

@ -2,7 +2,7 @@ from PySide2.QtGui import Qt
from PySide2.QtWidgets import QLabel, QHBoxLayout, QVBoxLayout, QGroupBox
from game import Game
from gen.flights.ai_flight_planner import meter_to_nm
from game.utils import meter_to_nm
from gen.flights.flight import Flight
from qt_ui.widgets.combos.QStrikeTargetSelectionComboBox import QStrikeTargetSelectionComboBox
from qt_ui.widgets.views.QStrikeTargetInfoView import QStrikeTargetInfoView

View File

@ -0,0 +1,69 @@
local unitPayloads = {
["name"] = "B-17G",
["payloads"] = {
[1] = {
["name"] = "STRIKE",
["pylons"] = {
[1] = {
["CLSID"] = "{12xM64}",
["num"] = 1,
},
},
["tasks"] = {
[1] = 32,
},
},
[2] = {
["name"] = "CAP",
["pylons"] = {
[1] = {
["CLSID"] = "{12xM64}",
["num"] = 1,
},
},
["tasks"] = {
[1] = 32,
},
},
[3] = {
["name"] = "SEAD",
["pylons"] = {
[1] = {
["CLSID"] = "{12xM64}",
["num"] = 1,
},
},
["tasks"] = {
[1] = 32,
},
},
[4] = {
["name"] = "ANTISHIP",
["pylons"] = {
[1] = {
["CLSID"] = "{12xM64}",
["num"] = 1,
},
},
["tasks"] = {
[1] = 32,
},
},
[5] = {
["name"] = "CAS",
["pylons"] = {
[1] = {
["CLSID"] = "{12xM64}",
["num"] = 1,
},
},
["tasks"] = {
[1] = 32,
},
},
},
["tasks"] = {
},
["unitType"] = "B-17G",
}
return unitPayloads

View File

@ -7,7 +7,7 @@ import logging
from game.data.building_data import DEFAULT_AVAILABLE_BUILDINGS
from gen import namegen
from gen.defenses.armor_group_generator import generate_armor_group
from gen.fleet.ship_group_generator import generate_carrier_group, generate_lha_group
from gen.fleet.ship_group_generator import generate_carrier_group, generate_lha_group, generate_ship_group
from gen.sam.sam_group_generator import generate_anti_air_group, generate_shorad_group
from theater import ControlPointType
from theater.base import *
@ -81,6 +81,7 @@ def generate_groundobjects(theater: ConflictTheater, game):
g.cp_id = cp.id
g.airbase_group = True
g.dcs_identifier = "CARRIER"
g.sea_object = True
g.obj_name = namegen.random_objective_name()
g.heading = 0
g.position = Point(cp.position.x, cp.position.y)
@ -101,6 +102,7 @@ def generate_groundobjects(theater: ConflictTheater, game):
g.cp_id = cp.id
g.airbase_group = True
g.dcs_identifier = "LHA"
g.sea_object = True
g.obj_name = namegen.random_objective_name()
g.heading = 0
g.position = Point(cp.position.x, cp.position.y)
@ -114,7 +116,7 @@ def generate_groundobjects(theater: ConflictTheater, game):
cp.name = random.choice(db.FACTIONS[faction]["lhanames"])
else:
for i in range(random.randint(2,6)):
for i in range(random.randint(3,6)):
print("GENERATE BASE DEFENSE")
point = find_location(True, cp.position, theater, 1000, 2800, [], True)
@ -132,6 +134,7 @@ def generate_groundobjects(theater: ConflictTheater, game):
g.cp_id = cp.id
g.airbase_group = True
g.dcs_identifier = "AA"
g.sea_object = False
g.obj_name = namegen.random_objective_name()
g.heading = 0
g.position = Point(point.x, point.y)
@ -144,6 +147,35 @@ def generate_groundobjects(theater: ConflictTheater, game):
for ground_object in cp.ground_objects:
print(ground_object.groups)
for i in range(random.randint(2, 3)):
print("GENERATE SHIPS")
point = find_location(False, cp.position, theater, 5000, 40000, [], False)
print(point)
if point is None:
print("Couldn't find point for {} ships".format(cp))
continue
group_id = group_id + 1
g = TheaterGroundObject("aa")
g.group_id = group_id
g.object_id = 0
g.cp_id = cp.id
g.airbase_group = False
g.dcs_identifier = "AA"
g.sea_object = True
g.obj_name = namegen.random_objective_name()
g.heading = 0
g.position = Point(point.x, point.y)
group = generate_ship_group(game, g, faction)
g.groups = []
if group is not None:
g.groups.append(group)
cp.ground_objects.append(g)
def generate_airbase_defense_group(airbase_defense_group_id, ground_obj:TheaterGroundObject, faction, game, cp):
@ -286,6 +318,7 @@ def generate_cp_ground_points(cp: ControlPoint, theater, game, group_id, templat
g.dcs_identifier = object["type"]
g.heading = object["heading"]
g.sea_object = False
g.position = Point(point.x + object["offset"].x, point.y + object["offset"].y)
if g.dcs_identifier == "AA":

View File

@ -69,6 +69,7 @@ class TheaterGroundObject:
position = None # type: Point
groups = []
obj_name = ""
sea_object = False
def __init__(self, category: str):
self.category = category

View File

@ -21,7 +21,54 @@ class ChannelTheater(ConflictTheater):
super(ChannelTheater, self).__init__()
self.abeville = ControlPoint.from_airport(thechannel.Abbeville_Drucat, LAND, SIZE_SMALL, IMPORTANCE_LOW)
self.detling = ControlPoint.from_airport(thechannel.Detling, LAND, SIZE_SMALL, IMPORTANCE_LOW)
#self.detling = ControlPoint.from_airport(thechannel.Detling, LAND, SIZE_SMALL, IMPORTANCE_LOW)
self.stomer = ControlPoint.from_airport(thechannel.Saint_Omer_Longuenesse, LAND, SIZE_SMALL, IMPORTANCE_LOW)
self.dunkirk = ControlPoint.from_airport(thechannel.Dunkirk_Mardyck, LAND, SIZE_SMALL, IMPORTANCE_LOW)
self.hawkinge = ControlPoint.from_airport(thechannel.Hawkinge, LAND, SIZE_SMALL, IMPORTANCE_LOW)
#self.highhalden = ControlPoint.from_airport(thechannel.High_Halden, LAND, SIZE_SMALL, IMPORTANCE_LOW)
self.lympne = ControlPoint.from_airport(thechannel.Lympne, LAND, SIZE_SMALL, IMPORTANCE_LOW)
self.manston = ControlPoint.from_airport(thechannel.Manston, LAND, SIZE_SMALL, IMPORTANCE_LOW)
self.merville = ControlPoint.from_airport(thechannel.Merville_Calonne, LAND, SIZE_SMALL, IMPORTANCE_LOW)
# England
self.add_controlpoint(self.hawkinge, connected_to=[self.lympne, self.manston])
self.add_controlpoint(self.lympne, connected_to=[self.hawkinge])
self.add_controlpoint(self.manston, connected_to=[self.hawkinge])
# France
self.add_controlpoint(self.dunkirk, connected_to=[self.stomer])
self.add_controlpoint(self.stomer, connected_to=[self.dunkirk, self.merville, self.abeville])
self.add_controlpoint(self.merville, connected_to=[self.stomer])
self.add_controlpoint(self.abeville, connected_to=[self.stomer])
#self.detling.captured = True
self.hawkinge.captured = True
self.dunkirk.captured = True
#self.highhalden.captured = True
self.lympne.captured = True
self.manston.captured = True
class ChannelTheaterComplete(ConflictTheater):
terrain = dcs.terrain.TheChannel()
overview_image = "thechannel.gif"
reference_points = {(thechannel.Abbeville_Drucat.position.x, thechannel.Abbeville_Drucat.position.y): (2400, 4100),
(thechannel.Detling.position.x, thechannel.Detling.position.y): (1100, 2000)}
landmap = load_landmap("resources\\channellandmap.p")
daytime_map = {
"dawn": (6, 8),
"day": (10, 17),
"dusk": (17, 18),
"night": (0, 5),
}
def __init__(self):
super(ChannelTheaterComplete, self).__init__()
self.abeville = ControlPoint.from_airport(thechannel.Abbeville_Drucat, LAND, SIZE_SMALL, IMPORTANCE_LOW)
#self.detling = ControlPoint.from_airport(thechannel.Detling, LAND, SIZE_SMALL, IMPORTANCE_LOW)
self.stomer = ControlPoint.from_airport(thechannel.Saint_Omer_Longuenesse, LAND, SIZE_SMALL, IMPORTANCE_LOW)
self.dunkirk = ControlPoint.from_airport(thechannel.Dunkirk_Mardyck, LAND, SIZE_SMALL, IMPORTANCE_LOW)
@ -31,13 +78,11 @@ class ChannelTheater(ConflictTheater):
self.manston = ControlPoint.from_airport(thechannel.Manston, LAND, SIZE_SMALL, IMPORTANCE_LOW)
self.merville = ControlPoint.from_airport(thechannel.Merville_Calonne, LAND, SIZE_SMALL, IMPORTANCE_LOW)
# England
self.add_controlpoint(self.detling, connected_to=[self.highhalden])
self.add_controlpoint(self.hawkinge, connected_to=[self.lympne, self.manston])
self.add_controlpoint(self.highhalden, connected_to=[self.detling, self.lympne])
self.add_controlpoint(self.lympne, connected_to=[self.highhalden, self.hawkinge])
self.add_controlpoint(self.lympne, connected_to=[self.hawkinge, self.highhalden])
self.add_controlpoint(self.manston, connected_to=[self.hawkinge])
self.add_controlpoint(self.highhalden, connected_to=[self.lympne])
# France
self.add_controlpoint(self.dunkirk, connected_to=[self.stomer])
@ -45,8 +90,9 @@ class ChannelTheater(ConflictTheater):
self.add_controlpoint(self.merville, connected_to=[self.stomer])
self.add_controlpoint(self.abeville, connected_to=[self.stomer])
self.detling.captured = True
#self.detling.captured = True
self.hawkinge.captured = True
#self.dunkirk.captured = True
self.highhalden.captured = True
self.lympne.captured = True
self.manston.captured = True