Handle callsigns for flights.

We don't configure the callsigns that pydcs uses, so instead read
those from pydcs and use them where appropriate instead of just
guessing.

Fixes https://github.com/Khopa/dcs_liberation/issues/113.
This commit is contained in:
Dan Albert 2020-09-10 16:59:24 -07:00
parent 51bfc9a59b
commit 993bf50012
6 changed files with 85 additions and 57 deletions

View File

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

View File

@ -202,9 +202,27 @@ class FlightData:
self.waypoints = waypoints
self.intra_flight_channel = intra_flight_channel
self.frequency_to_channel_map = {}
self.callsign = self.create_group_callsign()
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
def client_units(self) -> List[FlyingUnit]:
"""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:
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 .aircraft import callsign_for_support_unit
from .conflictgen import *
from .naming import *
from .radios import RadioFrequency, RadioRegistry
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_ALT = 4572
TANKER_HEADING_OFFSET = 45
@ -18,27 +13,6 @@ TANKER_HEADING_OFFSET = 45
AWACS_DISTANCE = 150000
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
class AwacsInfo:
@ -81,8 +55,9 @@ class AirSupportConflictGenerator:
def generate(self, is_awacs_enabled):
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)):
callsign = TANKER_CALLSIGNS[i]
variant = db.unit_type_name(tanker_unit_type)
freq = self.radio_registry.alloc_uhf()
tacan = self.tacan_registry.alloc_for_band(TacanBand.Y)
@ -102,19 +77,35 @@ class AirSupportConflictGenerator:
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:
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(
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(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:
try:
callsign = AWACS_CALLSIGNS[0]
freq = self.radio_registry.alloc_uhf()
awacs_unit = db.find_unittype(AWACS, self.conflict.attackers_side)[0]
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(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.task import *
from dcs.triggers import TriggerOnce, Event
from gen import namegen
from gen.ground_forces.ai_ground_planner import CombatGroupRole, DISTANCE_FROM_FRONTLINE
from .aircraft import callsign_for_support_unit
from .conflictgen import *
SPREAD_DISTANCE_FACTOR = 0.1, 0.3
@ -22,6 +24,17 @@ FIGHT_DISTANCE = 3500
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:
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.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.jtacs: List[JtacInfo] = []
def _group_point(self, point) -> Point:
distance = randint(
@ -100,7 +114,7 @@ class GroundConflictGenerator:
# 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:
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
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(SetImmortalCommand(True))
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):

View File

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

View File

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