mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Flight Planner first version (just print planned flight in log but does not generate them yet)
This commit is contained in:
parent
fa99df3ce7
commit
b7ee98dcd6
11
game/game.py
11
game/game.py
@ -8,6 +8,7 @@ from dcs.vehicles import *
|
||||
|
||||
from game.game_stats import GameStats
|
||||
from gen.conflictgen import Conflict
|
||||
from gen.flights.ai_flight_planner import FlightPlanner
|
||||
from userdata.debriefing import Debriefing
|
||||
from theater import *
|
||||
|
||||
@ -111,6 +112,7 @@ class Game:
|
||||
self.date = datetime(start_date.year, start_date.month, start_date.day)
|
||||
self.game_stats = GameStats()
|
||||
self.game_stats.update(self)
|
||||
self.planners = {}
|
||||
|
||||
def _roll(self, prob, mult):
|
||||
if self.settings.version == "dev":
|
||||
@ -310,6 +312,15 @@ class Game:
|
||||
# Update statistics
|
||||
self.game_stats.update(self)
|
||||
|
||||
# Plan flights for next turn
|
||||
self.planners = {}
|
||||
for cp in self.theater.controlpoints:
|
||||
planner = FlightPlanner(cp, self)
|
||||
planner.plan_flights()
|
||||
self.planners[cp.id] = planner
|
||||
print(planner)
|
||||
|
||||
|
||||
@property
|
||||
def current_turn_daytime(self):
|
||||
return ["dawn", "day", "dusk", "night"][self.turn % 4]
|
||||
|
||||
@ -135,10 +135,9 @@ class Operation:
|
||||
# Generate ground object first
|
||||
self.groundobjectgen.generate()
|
||||
|
||||
# air support
|
||||
# Air Support (Tanker & Awacs)
|
||||
self.airsupportgen.generate(self.is_awacs_enabled)
|
||||
|
||||
|
||||
# Generate Activity on the map
|
||||
for cp in self.game.theater.controlpoints:
|
||||
side = cp.captured
|
||||
@ -157,20 +156,15 @@ class Operation:
|
||||
self.airgen.generate_dead_sead(cp, country)
|
||||
|
||||
|
||||
for i, tanker_type in enumerate(self.airsupportgen.generated_tankers):
|
||||
self.briefinggen.append_frequency("Tanker {} ({})".format(TANKER_CALLSIGNS[i], tanker_type), "{}X/{} MHz AM".format(97+i, 130+i))
|
||||
|
||||
if self.is_awacs_enabled:
|
||||
self.briefinggen.append_frequency("AWACS", "133 MHz AM")
|
||||
|
||||
# combined arms
|
||||
#Setup combined arms parameters
|
||||
self.current_mission.groundControl.pilot_can_control_vehicles = self.ca_slots > 0
|
||||
if self.game.player_country in [country.name for country in self.current_mission.coalition["blue"].countries.values()]:
|
||||
self.current_mission.groundControl.blue_tactical_commander = self.ca_slots
|
||||
else:
|
||||
self.current_mission.groundControl.red_tactical_commander = self.ca_slots
|
||||
|
||||
#self.extra_aagen.generate()
|
||||
|
||||
# triggers
|
||||
if self.game.is_player_attack(self.conflict.attackers_country):
|
||||
@ -192,18 +186,10 @@ class Operation:
|
||||
# options
|
||||
self.forcedoptionsgen.generate()
|
||||
|
||||
# main frequencies
|
||||
self.briefinggen.append_frequency("Flight", "251 MHz AM")
|
||||
if self.departure_cp.is_global or self.conflict.to_cp.is_global:
|
||||
self.briefinggen.append_frequency("Carrier", "20X/ICLS CHAN1")
|
||||
|
||||
# briefing
|
||||
self.briefinggen.generate()
|
||||
|
||||
# visuals
|
||||
# Generate Visuals Smoke Effects
|
||||
self.visualgen.generate()
|
||||
|
||||
# Scripts
|
||||
# Inject Lua Scripts
|
||||
load_mist = TriggerStart(comment="Load Mist Lua Framework")
|
||||
with open(os.path.abspath("./resources/scripts/mist_4_3_74.lua")) as f:
|
||||
load_mist.add_action(DoScript(String(f.read())))
|
||||
@ -219,5 +205,19 @@ class Operation:
|
||||
load_dcs_libe.add_action(DoScript(String(script)))
|
||||
self.current_mission.triggerrules.triggers.append(load_dcs_libe)
|
||||
|
||||
# Briefing Generation
|
||||
for i, tanker_type in enumerate(self.airsupportgen.generated_tankers):
|
||||
self.briefinggen.append_frequency("Tanker {} ({})".format(TANKER_CALLSIGNS[i], tanker_type), "{}X/{} MHz AM".format(97+i, 130+i))
|
||||
|
||||
if self.is_awacs_enabled:
|
||||
self.briefinggen.append_frequency("AWACS", "133 MHz AM")
|
||||
|
||||
self.briefinggen.append_frequency("Flight", "251 MHz AM")
|
||||
if self.departure_cp.is_global or self.conflict.to_cp.is_global:
|
||||
self.briefinggen.append_frequency("Carrier", "20X/ICLS CHAN1")
|
||||
|
||||
# Generate the briefing
|
||||
self.briefinggen.generate()
|
||||
|
||||
|
||||
|
||||
|
||||
209
gen/flights/ai_flight_planner.py
Normal file
209
gen/flights/ai_flight_planner.py
Normal file
@ -0,0 +1,209 @@
|
||||
import math
|
||||
import operator
|
||||
import typing
|
||||
import random
|
||||
|
||||
from gen.flights.ai_flight_planner_db import INTERCEPT_CAPABLE, CAP_CAPABLE, CAS_CAPABLE
|
||||
from gen.flights.flight import Flight, FlightType
|
||||
|
||||
|
||||
# TODO : Ideally should be based on the aircraft type instead / Availability of fuel
|
||||
STRIKE_MAX_RANGE = 30000
|
||||
SEAD_MAX_RANGE = 30000
|
||||
|
||||
MAX_NUMBER_OF_INTERCEPTION_GROUP = 3
|
||||
MISSION_DURATION = 120 # in minutes
|
||||
CAP_EVERY_X_MINUTES = 20
|
||||
|
||||
|
||||
class FlightPlanner:
|
||||
|
||||
from_cp = None
|
||||
game = None
|
||||
|
||||
interceptor_flights = []
|
||||
cap_flights = []
|
||||
cas_flights = []
|
||||
strike_flights = []
|
||||
sead_flights = []
|
||||
flights = []
|
||||
|
||||
def __init__(self, from_cp, game):
|
||||
# TODO : have the flight planner depend on a 'stance' setting : [Defensive, Aggresive... etc] and faction doctrine
|
||||
self.from_cp = from_cp
|
||||
self.game = game
|
||||
self.aircraft_inventory = {} # local copy of the airbase inventory
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Reset the planned flights and avaialble units
|
||||
"""
|
||||
self.aircraft_inventory = dict({k: v for k, v in self.from_cp.base.aircraft.items()})
|
||||
self.interceptor_flights = []
|
||||
self.cap_flights = []
|
||||
self.cas_flights = []
|
||||
self.strike_flights = []
|
||||
self.sead_flights = []
|
||||
self.flights = []
|
||||
|
||||
def plan_flights(self):
|
||||
|
||||
self.reset()
|
||||
|
||||
# The priority is to assign air-superiority fighter or interceptor to interception roles, so they can scramble if there is an attacker
|
||||
self.commision_interceptors()
|
||||
|
||||
# Then some CAP patrol for the next 2 hours
|
||||
self.commision_barcap()
|
||||
|
||||
# TODO : commision CAS / BAI
|
||||
|
||||
# TODO : commision SEAD
|
||||
|
||||
# TODO : commision STRIKE / ANTISHIP
|
||||
|
||||
def commision_interceptors(self):
|
||||
"""
|
||||
Pick some aircraft to assign them to interception roles
|
||||
"""
|
||||
|
||||
# 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)
|
||||
possible_interceptors = [k for k in self.aircraft_inventory.keys() if k in INTERCEPT_CAPABLE]
|
||||
|
||||
if len(possible_interceptors) <= 0:
|
||||
possible_interceptors = [k for k,v in self.aircraft_inventory.items() if k in CAP_CAPABLE and v >= 2]
|
||||
|
||||
if number_of_interceptor_groups > 0:
|
||||
inventory = dict({k: v for k, v in self.aircraft_inventory.items() if k in possible_interceptors})
|
||||
for i in range(number_of_interceptor_groups):
|
||||
try:
|
||||
unit = random.choice([k for k,v in inventory.items() if v >= 2])
|
||||
except IndexError:
|
||||
break
|
||||
inventory[unit] = inventory[unit] - 2
|
||||
flight = Flight(unit, 2, self.from_cp, FlightType.INTERCEPTION)
|
||||
flight.points = []
|
||||
|
||||
self.interceptor_flights.append(flight)
|
||||
self.flights.append(flight)
|
||||
|
||||
# Update inventory
|
||||
for k, v in inventory.items():
|
||||
self.aircraft_inventory[k] = v
|
||||
|
||||
def commision_barcap(self):
|
||||
"""
|
||||
Pick some aircraft to assign them to defensive CAP roles (BARCAP)
|
||||
"""
|
||||
|
||||
possible_aircraft = [k for k, v in self.aircraft_inventory.items() if k in CAP_CAPABLE and v >= 2]
|
||||
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)):
|
||||
|
||||
try:
|
||||
unit = random.choice([k for k, v in inventory.items() if v >= 2])
|
||||
except IndexError:
|
||||
break
|
||||
|
||||
inventory[unit] = inventory[unit] - 2
|
||||
flight = Flight(unit, 2, self.from_cp, FlightType.BARCAP)
|
||||
|
||||
# Flight path : fly over each ground object (TODO : improve)
|
||||
flight.points = []
|
||||
flight.scheduled_in = offset + i*random.randint(CAP_EVERY_X_MINUTES-5, CAP_EVERY_X_MINUTES+5)
|
||||
|
||||
patrol_alt = random.randint(3600, 7000)
|
||||
|
||||
patrolled = []
|
||||
for ground_object in self.from_cp.ground_objects:
|
||||
if ground_object.group_id not in patrolled and not ground_object.airbase_group:
|
||||
flight.points.append([ground_object.position.x, ground_object.position.y, patrol_alt])
|
||||
patrolled.append(ground_object.group_id)
|
||||
|
||||
self.cap_flights.append(flight)
|
||||
self.flights.append(flight)
|
||||
|
||||
# Update inventory
|
||||
for k, v in inventory.items():
|
||||
self.aircraft_inventory[k] = v
|
||||
|
||||
def _get_strike_targets_in_range(self):
|
||||
"""
|
||||
@return a list of potential strike targets in range
|
||||
"""
|
||||
|
||||
# target, distance
|
||||
potential_targets = []
|
||||
|
||||
for cp in [c for c in self.game.theater.controlpoints if c.captured != self.from_cp.captured]:
|
||||
|
||||
# Compute distance to current cp
|
||||
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:
|
||||
# Then it's unlikely any child ground object is in range
|
||||
return
|
||||
|
||||
added_group = []
|
||||
for g in cp.ground_objects:
|
||||
if g.group_id in added_group: continue
|
||||
|
||||
# Compute distance to current cp
|
||||
distance = math.hypot(cp.position.x - self.from_cp.position.x,
|
||||
cp.position.y - self.from_cp.position.y)
|
||||
|
||||
if distance < SEAD_MAX_RANGE:
|
||||
potential_targets.append((g, distance))
|
||||
added_group.append(g)
|
||||
|
||||
return potential_targets.sort(key=operator.itemgetter(1))
|
||||
|
||||
def _get_sead_targets_in_range(self):
|
||||
"""
|
||||
@return a list of potential sead targets in range
|
||||
"""
|
||||
|
||||
# target, distance
|
||||
potential_targets = []
|
||||
|
||||
for cp in [c for c in self.game.theater.controlpoints if c.captured != self.from_cp.captured]:
|
||||
|
||||
# Compute distance to current cp
|
||||
distance = math.hypot(cp.position.x - self.from_cp.position.x,
|
||||
cp.position.y - self.from_cp.position.y)
|
||||
|
||||
# Then it's unlikely any ground object is range
|
||||
if distance > 2*SEAD_MAX_RANGE:
|
||||
return
|
||||
|
||||
for g in cp.ground_objects:
|
||||
|
||||
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(r, "detection_range") and r.detection_range > 1000]) for group in g.groups])
|
||||
if number_of_units <= 0:
|
||||
continue
|
||||
|
||||
# Compute distance to current cp
|
||||
distance = math.hypot(cp.position.x - self.from_cp.position.x,
|
||||
cp.position.y - self.from_cp.position.y)
|
||||
|
||||
if distance < SEAD_MAX_RANGE:
|
||||
potential_targets.append((g, distance))
|
||||
|
||||
return potential_targets.sort(key=operator.itemgetter(1))
|
||||
|
||||
def __repr__(self):
|
||||
return "-"*40 + "\n" + self.from_cp.name + " planned flights :\n"\
|
||||
+ "-"*40 + "\n" + "\n".join([repr(f) for f in self.flights]) + "\n" + "-"*40
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
177
gen/flights/ai_flight_planner_db.py
Normal file
177
gen/flights/ai_flight_planner_db.py
Normal file
@ -0,0 +1,177 @@
|
||||
from dcs.planes import *
|
||||
from dcs.helicopters import *
|
||||
|
||||
# Interceptor are the aircraft prioritized for interception tasks
|
||||
# If none is available, the AI will use regular CAP-capable aircraft instead
|
||||
INTERCEPT_CAPABLE = [
|
||||
MiG_21Bis,
|
||||
MiG_25PD,
|
||||
MiG_31,
|
||||
|
||||
M_2000C,
|
||||
Mirage_2000_5,
|
||||
|
||||
F_14B,
|
||||
F_15C,
|
||||
|
||||
]
|
||||
|
||||
# Used for CAP, Escort, and intercept if there is not a specialised aircraft available
|
||||
CAP_CAPABLE = [
|
||||
MiG_15bis,
|
||||
MiG_19P,
|
||||
MiG_21Bis,
|
||||
MiG_23MLD,
|
||||
MiG_29A,
|
||||
MiG_29G,
|
||||
MiG_29S,
|
||||
|
||||
Su_27,
|
||||
J_11A,
|
||||
Su_30,
|
||||
Su_33,
|
||||
|
||||
M_2000C,
|
||||
Mirage_2000_5,
|
||||
|
||||
F_86F_Sabre,
|
||||
F_4E,
|
||||
F_5E_3,
|
||||
F_14B,
|
||||
F_15C,
|
||||
F_16C_50,
|
||||
FA_18C_hornet,
|
||||
|
||||
C_101CC,
|
||||
L_39ZA,
|
||||
|
||||
P_51D_30_NA,
|
||||
P_51D,
|
||||
|
||||
SpitfireLFMkIXCW,
|
||||
SpitfireLFMkIX,
|
||||
|
||||
Bf_109K_4,
|
||||
FW_190D9,
|
||||
FW_190A8,
|
||||
]
|
||||
|
||||
# USed for CAS (Close air support) and BAI (Battlefield Interdiction)
|
||||
CAS_CAPABLE = [
|
||||
|
||||
MiG_15bis,
|
||||
MiG_29A,
|
||||
MiG_27K,
|
||||
MiG_29S,
|
||||
|
||||
Su_17M4,
|
||||
Su_24M,
|
||||
Su_24MR,
|
||||
Su_25,
|
||||
Su_25T,
|
||||
Su_34,
|
||||
|
||||
M_2000C,
|
||||
|
||||
A_10A,
|
||||
A_10C,
|
||||
AV8BNA,
|
||||
|
||||
F_86F_Sabre,
|
||||
F_5E_3,
|
||||
F_14B,
|
||||
F_16C_50,
|
||||
FA_18C_hornet,
|
||||
|
||||
C_101CC,
|
||||
L_39ZA,
|
||||
AJS37,
|
||||
|
||||
SA342M,
|
||||
SA342L,
|
||||
|
||||
AH_64A,
|
||||
AH_64D,
|
||||
|
||||
UH_1H,
|
||||
|
||||
Mi_8MT,
|
||||
Mi_28N,
|
||||
Mi_24V,
|
||||
Ka_50,
|
||||
|
||||
P_51D_30_NA,
|
||||
P_51D,
|
||||
|
||||
SpitfireLFMkIXCW,
|
||||
SpitfireLFMkIX,
|
||||
|
||||
Bf_109K_4,
|
||||
FW_190D9,
|
||||
FW_190A8,
|
||||
]
|
||||
|
||||
# Aircraft used for SEAD / DEAD tasks
|
||||
SEAD_CAPABLE = [
|
||||
F_4E,
|
||||
FA_18C_hornet,
|
||||
F_16C_50,
|
||||
AV8BNA,
|
||||
|
||||
Su_24M,
|
||||
Su_25T,
|
||||
Su_25TM,
|
||||
Su_17M4,
|
||||
Su_30,
|
||||
Su_34,
|
||||
MiG_27K,
|
||||
]
|
||||
|
||||
# Aircraft used for Strike mission
|
||||
STRIKE_CAPABLE = [
|
||||
MiG_15bis,
|
||||
MiG_29A,
|
||||
MiG_27K,
|
||||
MiG_29S,
|
||||
|
||||
Su_17M4,
|
||||
Su_24M,
|
||||
Su_24MR,
|
||||
Su_25,
|
||||
Su_25T,
|
||||
Su_34,
|
||||
|
||||
M_2000C,
|
||||
|
||||
A_10A,
|
||||
A_10C,
|
||||
AV8BNA,
|
||||
|
||||
F_86F_Sabre,
|
||||
F_5E_3,
|
||||
F_14B,
|
||||
F_16C_50,
|
||||
FA_18C_hornet,
|
||||
|
||||
C_101CC,
|
||||
L_39ZA,
|
||||
AJS37,
|
||||
|
||||
M_2000C,
|
||||
|
||||
P_51D_30_NA,
|
||||
P_51D,
|
||||
|
||||
SpitfireLFMkIXCW,
|
||||
SpitfireLFMkIX,
|
||||
|
||||
Bf_109K_4,
|
||||
FW_190D9,
|
||||
FW_190A8,
|
||||
]
|
||||
|
||||
ANTISHIP_CAPABLE = [
|
||||
Su_24M,
|
||||
F_A_18C,
|
||||
AV8BNA,
|
||||
]
|
||||
56
gen/flights/flight.py
Normal file
56
gen/flights/flight.py
Normal file
@ -0,0 +1,56 @@
|
||||
from dcs.unittype import UnitType
|
||||
from enum import Enum
|
||||
from game import db
|
||||
|
||||
|
||||
class FlightType(Enum):
|
||||
CAP = 0
|
||||
TARCAP = 1
|
||||
BARCAP = 2
|
||||
CAS = 3
|
||||
INTERCEPTION = 4
|
||||
STRIKE = 5
|
||||
ANTISHIP = 6
|
||||
SEAD = 7
|
||||
DEAD = 8
|
||||
ESCORT = 9
|
||||
BAI = 10
|
||||
|
||||
# Helos
|
||||
TROOP_TRANSPORT = 11
|
||||
LOGISTICS = 12
|
||||
EVAC = 13
|
||||
|
||||
|
||||
class Flight:
|
||||
|
||||
unit_type: UnitType
|
||||
from_cp = None
|
||||
points = []
|
||||
type = ""
|
||||
count = 0
|
||||
client_count = 0
|
||||
|
||||
# How long before this flight should take off
|
||||
scheduled_in = 0
|
||||
|
||||
def __init__(self, unit_type: UnitType, count: int, from_cp, flight_type: FlightType):
|
||||
self.unit_type = unit_type
|
||||
self.count = count
|
||||
self.from_cp = from_cp
|
||||
self.flight_type = flight_type
|
||||
|
||||
def __repr__(self):
|
||||
return self.flight_type.name + " | " + str(self.count) + "x" + db.unit_type_name(self.unit_type)\
|
||||
+ " in " + str(self.scheduled_in) + " minutes (" + str(len(self.points)) + " wpt)"
|
||||
|
||||
# Test
|
||||
if __name__ == '__main__':
|
||||
|
||||
from dcs.planes import A_10C
|
||||
from theater import ControlPoint, Point
|
||||
|
||||
from_cp = ControlPoint(0, "AA", Point(0,0), None, [], 0, 0)
|
||||
f = Flight(A_10C, 4, from_cp, FlightType.CAS)
|
||||
f.scheduled_in = 50
|
||||
print(f)
|
||||
BIN
resources/normandy.gif
Normal file
BIN
resources/normandy.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 369 KiB |
@ -1,8 +1,7 @@
|
||||
import typing
|
||||
import re
|
||||
import typing
|
||||
|
||||
from dcs.mapping import *
|
||||
from dcs.country import *
|
||||
from dcs.terrain import Airport
|
||||
|
||||
from .theatergroundobject import TheaterGroundObject
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user