Merge pull request #130 from DanAlbert/callsigns

Handle callsigns for flights.
This commit is contained in:
C. Perreau 2020-09-11 20:53:44 +02:00 committed by GitHub
commit 70cd0e8c31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 86 additions and 58 deletions

View File

@ -200,7 +200,7 @@ class Operation:
) )
# Generate ground units on frontline everywhere # Generate ground units on frontline everywhere
self.game.jtacs = [] jtacs: List[JtacInfo] = []
for player_cp, enemy_cp in self.game.theater.conflicts(True): for player_cp, enemy_cp in self.game.theater.conflicts(True):
conflict = Conflict.frontline_cas_conflict(self.attacker_name, self.defender_name, conflict = Conflict.frontline_cas_conflict(self.attacker_name, self.defender_name,
self.current_mission.country(self.attacker_country), self.current_mission.country(self.attacker_country),
@ -211,6 +211,7 @@ class Operation:
enemy_gp = self.game.ground_planners[enemy_cp.id].units_per_cp[player_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 = GroundConflictGenerator(self.current_mission, conflict, self.game, player_gp, enemy_gp, player_cp.stances[enemy_cp.id])
groundConflictGen.generate() groundConflictGen.generate()
jtacs.extend(groundConflictGen.jtacs)
# Setup combined arms parameters # Setup combined arms parameters
self.current_mission.groundControl.pilot_can_control_vehicles = self.ca_slots > 0 self.current_mission.groundControl.pilot_can_control_vehicles = self.ca_slots > 0
@ -251,8 +252,8 @@ class Operation:
if not self.game.settings.jtac_smoke_on: if not self.game.settings.jtac_smoke_on:
smoke = "false" smoke = "false"
for jtac in self.game.jtacs: for jtac in jtacs:
script = script + "\n" + "JTACAutoLase('" + str(jtac[2]) + "', " + str(jtac[1]) + ", " + smoke + ", \"vehicle\")" + "\n" script += f"\nJTACAutoLase('{jtac.unit_name}', {jtac.code}, {smoke}, 'vehicle')\n"
load_autolase.add_action(DoScript(String(script))) load_autolase.add_action(DoScript(String(script)))
self.current_mission.triggerrules.triggers.append(load_autolase) self.current_mission.triggerrules.triggers.append(load_autolase)
@ -283,9 +284,7 @@ class Operation:
self.briefinggen.add_awacs(awacs) self.briefinggen.add_awacs(awacs)
kneeboard_generator.add_awacs(awacs) kneeboard_generator.add_awacs(awacs)
for region, code, name in self.game.jtacs: for jtac in jtacs:
# TODO: Radio info? Type?
jtac = JtacInfo(name, region, code)
self.briefinggen.add_jtac(jtac) self.briefinggen.add_jtac(jtac)
kneeboard_generator.add_jtac(jtac) kneeboard_generator.add_jtac(jtac)

View File

@ -202,9 +202,27 @@ class FlightData:
self.waypoints = waypoints self.waypoints = waypoints
self.intra_flight_channel = intra_flight_channel self.intra_flight_channel = intra_flight_channel
self.frequency_to_channel_map = {} self.frequency_to_channel_map = {}
self.callsign = self.create_group_callsign()
self.assign_intra_flight_channel() self.assign_intra_flight_channel()
def create_group_callsign(self) -> str:
lead = self.units[0]
raw_callsign = lead.callsign_as_str()
if not lead.callsign_is_western:
# Callsigns for non-Western countries are just a number per flight,
# similar to tail numbers.
return f"Flight {raw_callsign}"
# Callsign from pydcs is in the format `<name><group ID><unit ID>`,
# where unit ID is guaranteed to be a single digit but the group ID may
# be more.
match = re.search(r"^(\D+)(\d+)(\d)$", raw_callsign)
if match is None:
logging.error(f"Could not parse unit callsign: {raw_callsign}")
return f"Flight {raw_callsign}"
return f"{match.group(1)} {match.group(2)}"
@property @property
def client_units(self) -> List[FlyingUnit]: def client_units(self) -> List[FlyingUnit]:
"""List of playable units in the flight.""" """List of playable units in the flight."""
@ -254,6 +272,17 @@ class FlightData:
) )
def callsign_for_support_unit(group: FlyingGroup) -> str:
# Either something like Overlord11 for Western AWACS, or else just a number.
# Convert to either "Overlord" or "Flight 123".
lead = group.units[0]
raw_callsign = lead.callsign_as_str()
try:
return f"Flight {int(raw_callsign)}"
except ValueError:
return raw_callsign.rstrip("1234567890")
class AircraftConflictGenerator: class AircraftConflictGenerator:
escort_targets = [] # type: typing.List[typing.Tuple[FlyingGroup, int]] escort_targets = [] # type: typing.List[typing.Tuple[FlyingGroup, int]]

View File

@ -1,16 +1,11 @@
from typing import List
from dataclasses import dataclass, field from dataclasses import dataclass, field
from .aircraft import callsign_for_support_unit
from .conflictgen import * from .conflictgen import *
from .naming import * from .naming import *
from .radios import RadioFrequency, RadioRegistry from .radios import RadioFrequency, RadioRegistry
from .tacan import TacanBand, TacanChannel, TacanRegistry from .tacan import TacanBand, TacanChannel, TacanRegistry
from dcs.mission import *
from dcs.unitgroup import *
from dcs.unittype import *
from dcs.task import *
TANKER_DISTANCE = 15000 TANKER_DISTANCE = 15000
TANKER_ALT = 4572 TANKER_ALT = 4572
TANKER_HEADING_OFFSET = 45 TANKER_HEADING_OFFSET = 45
@ -18,27 +13,6 @@ TANKER_HEADING_OFFSET = 45
AWACS_DISTANCE = 150000 AWACS_DISTANCE = 150000
AWACS_ALT = 13000 AWACS_ALT = 13000
AWACS_CALLSIGNS = [
"Overlord",
"Magic",
"Wizard",
"Focus",
"Darkstar",
]
@dataclass
class TankerCallsign:
full: str
short: str
TANKER_CALLSIGNS = [
TankerCallsign("Texaco", "TEX"),
TankerCallsign("Arco", "ARC"),
TankerCallsign("Shell", "SHL"),
]
@dataclass @dataclass
class AwacsInfo: class AwacsInfo:
@ -81,8 +55,9 @@ class AirSupportConflictGenerator:
def generate(self, is_awacs_enabled): def generate(self, is_awacs_enabled):
player_cp = self.conflict.from_cp if self.conflict.from_cp.captured else self.conflict.to_cp player_cp = self.conflict.from_cp if self.conflict.from_cp.captured else self.conflict.to_cp
fallback_tanker_number = 0
for i, tanker_unit_type in enumerate(db.find_unittype(Refueling, self.conflict.attackers_side)): for i, tanker_unit_type in enumerate(db.find_unittype(Refueling, self.conflict.attackers_side)):
callsign = TANKER_CALLSIGNS[i]
variant = db.unit_type_name(tanker_unit_type) variant = db.unit_type_name(tanker_unit_type)
freq = self.radio_registry.alloc_uhf() freq = self.radio_registry.alloc_uhf()
tacan = self.tacan_registry.alloc_for_band(TacanBand.Y) tacan = self.tacan_registry.alloc_for_band(TacanBand.Y)
@ -102,19 +77,35 @@ class AirSupportConflictGenerator:
tacanchannel=str(tacan), tacanchannel=str(tacan),
) )
callsign = callsign_for_support_unit(tanker_group)
tacan_callsign = {
"Texaco": "TEX",
"Arco": "ARC",
"Shell": "SHL",
}.get(callsign)
if tacan_callsign is None:
# The dict above is all the callsigns currently in the game, but
# non-Western countries don't use the callsigns and instead just
# use numbers. It's possible that none of those nations have
# TACAN compatible refueling aircraft, but fallback just in
# case.
tacan_callsign = f"TK{fallback_tanker_number}"
fallback_tanker_number += 1
if tanker_unit_type != IL_78M: if tanker_unit_type != IL_78M:
tanker_group.points[0].tasks.pop() # Override PyDCS tacan channel # Override PyDCS tacan channel.
tanker_group.points[0].tasks.pop()
tanker_group.points[0].tasks.append(ActivateBeaconCommand( tanker_group.points[0].tasks.append(ActivateBeaconCommand(
tacan.number, tacan.band.value, callsign.short, True, tanker_group.units[0].id, True)) tacan.number, tacan.band.value, tacan_callsign, True,
tanker_group.units[0].id, True))
tanker_group.points[0].tasks.append(SetInvisibleCommand(True)) tanker_group.points[0].tasks.append(SetInvisibleCommand(True))
tanker_group.points[0].tasks.append(SetImmortalCommand(True)) tanker_group.points[0].tasks.append(SetImmortalCommand(True))
self.air_support.tankers.append(TankerInfo(callsign.full, variant, freq, tacan)) self.air_support.tankers.append(TankerInfo(callsign, variant, freq, tacan))
if is_awacs_enabled: if is_awacs_enabled:
try: try:
callsign = AWACS_CALLSIGNS[0]
freq = self.radio_registry.alloc_uhf() freq = self.radio_registry.alloc_uhf()
awacs_unit = db.find_unittype(AWACS, self.conflict.attackers_side)[0] awacs_unit = db.find_unittype(AWACS, self.conflict.attackers_side)[0]
awacs_flight = self.mission.awacs_flight( awacs_flight = self.mission.awacs_flight(
@ -129,7 +120,8 @@ class AirSupportConflictGenerator:
) )
awacs_flight.points[0].tasks.append(SetInvisibleCommand(True)) awacs_flight.points[0].tasks.append(SetInvisibleCommand(True))
awacs_flight.points[0].tasks.append(SetImmortalCommand(True)) awacs_flight.points[0].tasks.append(SetImmortalCommand(True))
self.air_support.awacs.append(AwacsInfo(callsign, freq))
except:
print("No AWACS for faction")
self.air_support.awacs.append(AwacsInfo(
callsign_for_support_unit(awacs_flight), freq))
except:
print("No AWACS for faction")

View File

@ -1,10 +1,12 @@
from dcs.action import AITaskPush, AITaskSet from dataclasses import dataclass
from dcs.action import AITaskPush
from dcs.condition import TimeAfter, UnitDamaged, Or, GroupLifeLess from dcs.condition import TimeAfter, UnitDamaged, Or, GroupLifeLess
from dcs.task import *
from dcs.triggers import TriggerOnce, Event from dcs.triggers import TriggerOnce, Event
from gen import namegen from gen import namegen
from gen.ground_forces.ai_ground_planner import CombatGroupRole, DISTANCE_FROM_FRONTLINE from gen.ground_forces.ai_ground_planner import CombatGroupRole, DISTANCE_FROM_FRONTLINE
from .aircraft import callsign_for_support_unit
from .conflictgen import * from .conflictgen import *
SPREAD_DISTANCE_FACTOR = 0.1, 0.3 SPREAD_DISTANCE_FACTOR = 0.1, 0.3
@ -22,6 +24,17 @@ FIGHT_DISTANCE = 3500
RANDOM_OFFSET_ATTACK = 250 RANDOM_OFFSET_ATTACK = 250
@dataclass(frozen=True)
class JtacInfo:
"""JTAC information."""
unit_name: str
callsign: str
region: str
code: str
# TODO: Radio info? Type?
class GroundConflictGenerator: class GroundConflictGenerator:
def __init__(self, mission: Mission, conflict: Conflict, game, player_planned_combat_groups, enemy_planned_combat_groups, player_stance): def __init__(self, mission: Mission, conflict: Conflict, game, player_planned_combat_groups, enemy_planned_combat_groups, player_stance):
@ -32,6 +45,7 @@ class GroundConflictGenerator:
self.player_stance = CombatStance(player_stance) self.player_stance = CombatStance(player_stance)
self.enemy_stance = random.choice([CombatStance.AGGRESIVE, CombatStance.AGGRESIVE, CombatStance.AGGRESIVE, 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.AGGRESIVE]) self.enemy_stance = random.choice([CombatStance.AGGRESIVE, CombatStance.AGGRESIVE, CombatStance.AGGRESIVE, 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.AGGRESIVE])
self.game = game self.game = game
self.jtacs: List[JtacInfo] = []
def _group_point(self, point) -> Point: def _group_point(self, point) -> Point:
distance = randint( distance = randint(
@ -100,7 +114,7 @@ class GroundConflictGenerator:
# Add JTAC # Add JTAC
if "has_jtac" in self.game.player_faction and self.game.player_faction["has_jtac"] and self.game.settings.include_jtac_if_available: if "has_jtac" in self.game.player_faction and self.game.player_faction["has_jtac"] and self.game.settings.include_jtac_if_available:
n = "JTAC" + str(self.conflict.from_cp.id) + str(self.conflict.to_cp.id) n = "JTAC" + str(self.conflict.from_cp.id) + str(self.conflict.to_cp.id)
code = 1688 - len(self.game.jtacs) code = 1688 - len(self.jtacs)
utype = MQ_9_Reaper utype = MQ_9_Reaper
if "jtac_unit" in self.game.player_faction: if "jtac_unit" in self.game.player_faction:
@ -115,7 +129,10 @@ class GroundConflictGenerator:
jtac.points[0].tasks.append(SetInvisibleCommand(True)) jtac.points[0].tasks.append(SetInvisibleCommand(True))
jtac.points[0].tasks.append(SetImmortalCommand(True)) jtac.points[0].tasks.append(SetImmortalCommand(True))
jtac.points[0].tasks.append(OrbitAction(5000, 300, OrbitAction.OrbitPattern.Circle)) jtac.points[0].tasks.append(OrbitAction(5000, 300, OrbitAction.OrbitPattern.Circle))
self.game.jtacs.append(("Frontline " + self.conflict.from_cp.name + "/" + self.conflict.to_cp.name, code, n)) frontline = f"Frontline {self.conflict.from_cp.name}/{self.conflict.to_cp.name}"
# Note: Will need to change if we ever add ground based JTAC.
callsign = callsign_for_support_unit(jtac)
self.jtacs.append(JtacInfo(n, callsign, frontline, str(code)))
def gen_infantry_group_for_group(self, group, is_player, side:Country, forward_heading): def gen_infantry_group_for_group(self, group, is_player, side:Country, forward_heading):

View File

@ -2,13 +2,14 @@ import os
from collections import defaultdict from collections import defaultdict
from dataclasses import dataclass from dataclasses import dataclass
import random import random
from typing import List, Tuple from typing import List
from game import db from game import db
from dcs.mission import Mission from dcs.mission import Mission
from .aircraft import FlightData from .aircraft import FlightData
from .airfields import RunwayData from .airfields import RunwayData
from .airsupportgen import AwacsInfo, TankerInfo from .airsupportgen import AwacsInfo, TankerInfo
from .armor import JtacInfo
from .conflictgen import Conflict from .conflictgen import Conflict
from .ground_forces.combat_stance import CombatStance from .ground_forces.combat_stance import CombatStance
from .radios import RadioFrequency from .radios import RadioFrequency
@ -21,14 +22,6 @@ class CommInfo:
freq: RadioFrequency freq: RadioFrequency
@dataclass
class JtacInfo:
"""JTAC information for the kneeboard."""
callsign: str
region: str
code: str
class MissionInfoGenerator: class MissionInfoGenerator:
"""Base type for generators of mission information for the player. """Base type for generators of mission information for the player.

View File

@ -159,9 +159,7 @@ class BriefingPage(KneeboardPage):
def write(self, path: Path) -> None: def write(self, path: Path) -> None:
writer = KneeboardPageWriter() writer = KneeboardPageWriter()
# TODO: Assign callsigns to flights and include that info. writer.title(f"{self.flight.callsign} Mission Info")
# https://github.com/Khopa/dcs_liberation/issues/113
writer.title(f"Mission Info")
# TODO: Handle carriers. # TODO: Handle carriers.
writer.heading("Airfield Info") writer.heading("Airfield Info")

2
pydcs

@ -1 +1 @@
Subproject commit 5c02bf8ea5e3ec5afccc0135e31a3dd15e21342b Subproject commit f0f9754b97cf08f71c2b7f5f9687b679b4f0d69d