dcs-retribution/game/pretense/pretensemissiongenerator.py
Raffson d1b484f560
Support for auto-fog
implemented as a setting...
2024-12-26 02:51:58 +01:00

287 lines
12 KiB
Python

from __future__ import annotations
import logging
import pickle
from datetime import datetime
from pathlib import Path
from typing import TYPE_CHECKING
import dcs.lua
from dcs import Point
from dcs.coalition import Coalition
from dcs.countries import (
country_dict,
CombinedJointTaskForcesBlue,
CombinedJointTaskForcesRed,
)
from dcs.task import AFAC, FAC, SetInvisibleCommand, SetImmortalCommand, OrbitAction
from game.missiongenerator.convoygenerator import ConvoyGenerator
from game.missiongenerator.environmentgenerator import EnvironmentGenerator
from game.missiongenerator.forcedoptionsgenerator import ForcedOptionsGenerator
from game.missiongenerator.frontlineconflictdescription import (
FrontLineConflictDescription,
)
from game.missiongenerator.missiondata import JtacInfo
from game.missiongenerator.tgogenerator import TgoGenerator
from game.missiongenerator.visualsgenerator import VisualsGenerator
from game.naming import namegen
from game.persistency import pre_pretense_backups_dir
from game.pretense.pretenseaircraftgenerator import PretenseAircraftGenerator
from game.theater.bullseye import Bullseye
from game.unitmap import UnitMap
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from .pretenseluagenerator import PretenseLuaGenerator
from .pretensetgogenerator import PretenseTgoGenerator
from .pretensetriggergenerator import PretenseTriggerGenerator
from ..ato.airtaaskingorder import AirTaskingOrder
from ..callsigns import callsign_for_support_unit
from ..dcs.aircrafttype import AircraftType
from ..lasercodes import LaserCodeRegistry
from ..missiongenerator import MissionGenerator
from ..theater import Airfield
if TYPE_CHECKING:
from game import Game
class PretenseMissionGenerator(MissionGenerator):
def __init__(self, game: Game, time: datetime) -> None:
super().__init__(game, time)
self.laser_code_registry = LaserCodeRegistry()
with open("resources/default_options.lua", "r", encoding="utf-8") as f:
options = dcs.lua.loads(f.read())["options"]
ext_view = game.settings.external_views_allowed
options["miscellaneous"]["f11_free_camera"] = ext_view
options["difficulty"]["spectatorExternalViews"] = ext_view
self.mission.options.load_from_dict(options)
def generate_miz(self, output: Path) -> UnitMap:
game_backup_pickle = pickle.dumps(self.game)
path = pre_pretense_backups_dir()
path /= f".pre-pretense-backup.retribution"
try:
with open(path, "wb") as f:
pickle.dump(self.game, f)
except:
logging.error(f"Unable to save Pretense pre-generation backup to {path}")
if self.generation_started:
raise RuntimeError(
"Mission has already begun generating. To reset, create a new "
"MissionSimulation."
)
self.generation_started = True
self.game.pretense_ground_supply = {1: {}, 2: {}}
self.game.pretense_ground_assault = {1: {}, 2: {}}
self.game.pretense_air = {1: {}, 2: {}}
self.setup_mission_coalitions()
self.add_airfields_to_unit_map()
self.initialize_registries()
auto_fog = self.game.settings.use_auto_fog
EnvironmentGenerator(
self.mission, self.game.conditions, self.time, auto_fog
).generate()
tgo_generator = PretenseTgoGenerator(
self.mission,
self.game,
self.radio_registry,
self.tacan_registry,
self.unit_map,
self.mission_data,
)
tgo_generator.generate()
ConvoyGenerator(self.mission, self.game, self.unit_map).generate()
# Generate ground conflicts first so the JTACs get the first laser code (1688)
# rather than the first player flight with a TGP.
self.generate_ground_conflicts()
self.generate_air_units(tgo_generator)
for cp in self.game.theater.controlpoints:
if (
self.game.settings.ground_start_airbase_statics_farps_remove
and isinstance(cp, Airfield)
):
while len(tgo_generator.ground_spawns[cp]) > 0:
ground_spawn = tgo_generator.ground_spawns[cp].pop()
# Remove invisible FARPs from airfields because they are unnecessary
neutral_country = self.mission.country(
cp.coalition.game.neutral_country.name
)
neutral_country.remove_static_group(ground_spawn[0])
while len(tgo_generator.ground_spawns_roadbase[cp]) > 0:
ground_spawn = tgo_generator.ground_spawns_roadbase[cp].pop()
# Remove invisible FARPs from airfields because they are unnecessary
neutral_country = self.mission.country(
cp.coalition.game.neutral_country.name
)
neutral_country.remove_static_group(ground_spawn[0])
self.mission.triggerrules.triggers.clear()
PretenseTriggerGenerator(self.mission, self.game).generate()
ForcedOptionsGenerator(self.mission, self.game).generate()
VisualsGenerator(self.mission, self.game).generate()
PretenseLuaGenerator(self.game, self.mission, self.mission_data).generate()
self.setup_combined_arms()
self.notify_info_generators()
# TODO: Shouldn't this be first?
namegen.reset_numbers()
self.generate_warehouses()
self.mission.save(output)
print(
f"Loading pre-pretense save, number of BLUFOR squadrons: {len(self.game.blue.air_wing.squadrons)}"
)
self.game = pickle.loads(game_backup_pickle)
print(
f"Loaded pre-pretense save, number of BLUFOR squadrons: {len(self.game.blue.air_wing.squadrons)}"
)
GameUpdateSignal.get_instance().game_loaded.emit(self.game)
return self.unit_map
def setup_mission_coalitions(self) -> None:
self.mission.coalition["blue"] = Coalition(
"blue", bullseye=self.game.blue.bullseye.to_pydcs()
)
self.mission.coalition["red"] = Coalition(
"red", bullseye=self.game.red.bullseye.to_pydcs()
)
self.mission.coalition["neutrals"] = Coalition(
"neutrals", bullseye=Bullseye(Point(0, 0, self.mission.terrain)).to_pydcs()
)
self.mission.coalition["blue"].add_country(self.p_country)
self.mission.coalition["red"].add_country(self.e_country)
# Add CJTF factions to the coalitions, if they're not being used in the campaign
if CombinedJointTaskForcesBlue.id not in {self.p_country.id, self.e_country.id}:
self.mission.coalition["blue"].add_country(CombinedJointTaskForcesBlue())
if CombinedJointTaskForcesRed.id not in {self.p_country.id, self.e_country.id}:
self.mission.coalition["red"].add_country(CombinedJointTaskForcesRed())
belligerents = {self.p_country.id, self.e_country.id}
for country_id in country_dict.keys():
if country_id not in belligerents:
c = country_dict[country_id]()
self.mission.coalition["neutrals"].add_country(c)
def generate_ground_conflicts(self) -> None:
"""Generate FLOTs and JTACs for each active front line."""
for front_line in self.game.theater.conflicts():
player_cp = front_line.blue_cp
enemy_cp = front_line.red_cp
# Add JTAC
if self.game.blue.faction.has_jtac:
freq = self.radio_registry.alloc_uhf()
# If the option fc3LaserCode is enabled, force all JTAC
# laser codes to 1113 to allow lasing for Su-25 Frogfoots and A-10A Warthogs.
# Otherwise use 1688 for the first JTAC, 1687 for the second etc.
if self.game.settings.plugins.get("ctld.fc3LaserCode"):
code = self.game.laser_code_registry.fc3_code
else:
code = front_line.laser_code
utype = self.game.blue.faction.jtac_unit
if utype is None:
utype = AircraftType.named("MQ-9 Reaper")
country = self.mission.country(self.game.blue.faction.country.name)
position = FrontLineConflictDescription.frontline_position(
front_line, self.game.theater, self.game.settings
)
jtac = self.mission.flight_group(
country=country,
name=namegen.next_jtac_name(),
aircraft_type=utype.dcs_unit_type,
position=position[0],
airport=None,
altitude=5000,
maintask=AFAC,
)
jtac.points[0].tasks.append(
FAC(
callsign=len(self.mission_data.jtacs) + 1,
frequency=int(freq.mhz),
modulation=freq.modulation,
)
)
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)
)
frontline = f"Frontline {player_cp.name}/{enemy_cp.name}"
# Note: Will need to change if we ever add ground based JTAC.
callsign = callsign_for_support_unit(jtac)
self.mission_data.jtacs.append(
JtacInfo(
group_name=jtac.name,
unit_name=jtac.units[0].name,
callsign=callsign,
region=frontline,
code=str(code),
blue=True,
freq=freq,
)
)
def generate_air_units(self, tgo_generator: TgoGenerator) -> None:
"""Generate the air units for the Operation"""
# Generate Aircraft Activity on the map
aircraft_generator = PretenseAircraftGenerator(
self.mission,
self.game.settings,
self.game,
self.time,
self.radio_registry,
self.tacan_registry,
self.datalink_registry,
self.laser_code_registry,
self.unit_map,
mission_data=self.mission_data,
helipads=tgo_generator.helipads,
ground_spawns_roadbase=tgo_generator.ground_spawns_roadbase,
ground_spawns_large=tgo_generator.ground_spawns_large,
ground_spawns=tgo_generator.ground_spawns,
)
# Clear parking slots and ATOs
aircraft_generator.clear_parking_slots()
self.game.blue.ato.clear()
self.game.red.ato.clear()
for cp in self.game.theater.controlpoints:
for country in (self.p_country, self.e_country):
ato = AirTaskingOrder()
aircraft_generator.generate_flights(
country,
cp,
ato,
)
aircraft_generator.generate_packages(
country,
ato,
tgo_generator.runways,
)
self.mission_data.flights = aircraft_generator.flights
for flight in aircraft_generator.flights:
if not flight.client_units:
continue
flight.aircraft_type.assign_channels_for_flight(flight, self.mission_data)