mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Merge pull request #309 from DanAlbert/theater-refactor
Refactor game and ground object generation.
This commit is contained in:
commit
040db055fd
@ -1,2 +1,3 @@
|
|||||||
from .game import Game
|
from .game import Game
|
||||||
from . import db
|
from . import db
|
||||||
|
from .version import VERSION
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import inspect
|
import inspect
|
||||||
import dcs
|
import dcs
|
||||||
|
|
||||||
DEFAULT_AVAILABLE_BUILDINGS = ['fuel', 'ammo', 'comms', 'oil', 'ware', 'farp', 'fob', 'power', 'factory', 'derrick', 'aa']
|
DEFAULT_AVAILABLE_BUILDINGS = ['fuel', 'ammo', 'comms', 'oil', 'ware', 'farp', 'fob', 'power', 'factory', 'derrick']
|
||||||
|
|
||||||
WW2_FREE = ['fuel', 'factory', 'ware', 'aa']
|
WW2_FREE = ['fuel', 'factory', 'ware']
|
||||||
WW2_GERMANY_BUILDINGS = ['fuel', 'factory', 'ww2bunker', 'ww2bunker', 'ww2bunker', 'allycamp', 'allycamp', 'aa']
|
WW2_GERMANY_BUILDINGS = ['fuel', 'factory', 'ww2bunker', 'ww2bunker', 'ww2bunker', 'allycamp', 'allycamp']
|
||||||
WW2_ALLIES_BUILDINGS = ['fuel', 'factory', 'allycamp', 'allycamp', 'allycamp', 'allycamp', 'allycamp', 'aa']
|
WW2_ALLIES_BUILDINGS = ['fuel', 'factory', 'allycamp', 'allycamp', 'allycamp', 'allycamp', 'allycamp']
|
||||||
|
|
||||||
FORTIFICATION_BUILDINGS = ['Siegfried Line', 'Concertina wire', 'Concertina Wire', 'Czech hedgehogs 1', 'Czech hedgehogs 2',
|
FORTIFICATION_BUILDINGS = ['Siegfried Line', 'Concertina wire', 'Concertina Wire', 'Czech hedgehogs 1', 'Czech hedgehogs 2',
|
||||||
'Dragonteeth 1', 'Dragonteeth 2', 'Dragonteeth 3', 'Dragonteeth 4', 'Dragonteeth 5',
|
'Dragonteeth 1', 'Dragonteeth 2', 'Dragonteeth 3', 'Dragonteeth 4', 'Dragonteeth 5',
|
||||||
|
|||||||
@ -14,7 +14,6 @@ from game.infos.information import Information
|
|||||||
from game.operation.operation import Operation
|
from game.operation.operation import Operation
|
||||||
from gen.ground_forces.combat_stance import CombatStance
|
from gen.ground_forces.combat_stance import CombatStance
|
||||||
from theater import ControlPoint
|
from theater import ControlPoint
|
||||||
from theater.start_generator import generate_airbase_defense_group
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..game import Game
|
from ..game import Game
|
||||||
|
|||||||
8
game/version.py
Normal file
8
game/version.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
#: Current version of Liberation.
|
||||||
|
VERSION = "2.2.0-preview"
|
||||||
|
if Path("buildnumber").exists():
|
||||||
|
with open("buildnumber", "r") as file:
|
||||||
|
VERSION += f"-{file.readline()}"
|
||||||
@ -1,8 +1,18 @@
|
|||||||
|
"""Generators for creating the groups for ground objectives.
|
||||||
|
|
||||||
|
The classes in this file are responsible for creating the vehicle groups, ship
|
||||||
|
groups, statics, missile sites, and AA sites for the mission. Each of these
|
||||||
|
objectives is defined in the Theater by a TheaterGroundObject. These classes
|
||||||
|
create the pydcs groups and statics for those areas and add them to the mission.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
from typing import Dict, Iterator
|
from typing import Dict, Iterator, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
from dcs import Mission
|
from dcs import Mission
|
||||||
|
from dcs.country import Country
|
||||||
from dcs.statics import fortification_map, warehouse_map
|
from dcs.statics import fortification_map, warehouse_map
|
||||||
from dcs.task import (
|
from dcs.task import (
|
||||||
ActivateBeaconCommand,
|
ActivateBeaconCommand,
|
||||||
@ -10,22 +20,339 @@ from dcs.task import (
|
|||||||
EPLRS,
|
EPLRS,
|
||||||
OptAlarmState,
|
OptAlarmState,
|
||||||
)
|
)
|
||||||
from dcs.unit import Ship, Vehicle
|
from dcs.unit import Ship, Vehicle, Unit
|
||||||
from dcs.unitgroup import StaticGroup
|
from dcs.unitgroup import Group, ShipGroup, StaticGroup
|
||||||
|
from dcs.unittype import StaticType, UnitType
|
||||||
|
|
||||||
from game import db
|
from game import db
|
||||||
from game.data.building_data import FORTIFICATION_UNITS, FORTIFICATION_UNITS_ID
|
from game.data.building_data import FORTIFICATION_UNITS, FORTIFICATION_UNITS_ID
|
||||||
from game.db import unit_type_from_name
|
from game.db import unit_type_from_name
|
||||||
|
from theater import ControlPoint, TheaterGroundObject
|
||||||
|
from theater.theatergroundobject import (
|
||||||
|
BuildingGroundObject, CarrierGroundObject,
|
||||||
|
GenericCarrierGroundObject,
|
||||||
|
LhaGroundObject, ShipGroundObject,
|
||||||
|
)
|
||||||
from .conflictgen import Conflict
|
from .conflictgen import Conflict
|
||||||
from .radios import RadioRegistry
|
from .radios import RadioFrequency, RadioRegistry
|
||||||
from .runways import RunwayData
|
from .runways import RunwayData
|
||||||
from .tacan import TacanBand, TacanRegistry
|
from .tacan import TacanBand, TacanChannel, TacanRegistry
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from game import Game
|
||||||
|
|
||||||
|
|
||||||
FARP_FRONTLINE_DISTANCE = 10000
|
FARP_FRONTLINE_DISTANCE = 10000
|
||||||
AA_CP_MIN_DISTANCE = 40000
|
AA_CP_MIN_DISTANCE = 40000
|
||||||
|
|
||||||
|
|
||||||
|
class GenericGroundObjectGenerator:
|
||||||
|
"""An unspecialized ground object generator.
|
||||||
|
|
||||||
|
Currently used only for SAM and missile (V1/V2) sites.
|
||||||
|
"""
|
||||||
|
def __init__(self, ground_object: TheaterGroundObject, country: Country,
|
||||||
|
game: Game, mission: Mission) -> None:
|
||||||
|
self.ground_object = ground_object
|
||||||
|
self.country = country
|
||||||
|
self.game = game
|
||||||
|
self.m = mission
|
||||||
|
|
||||||
|
def generate(self) -> None:
|
||||||
|
if self.game.position_culled(self.ground_object.position):
|
||||||
|
return
|
||||||
|
|
||||||
|
for group in self.ground_object.groups:
|
||||||
|
if not group.units:
|
||||||
|
logging.warning(f"Found empty group in {self.ground_object}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
unit_type = unit_type_from_name(group.units[0].type)
|
||||||
|
if unit_type is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Unrecognized unit type: {group.units[0].type}")
|
||||||
|
|
||||||
|
vg = self.m.vehicle_group(self.country, group.name, unit_type,
|
||||||
|
position=group.position,
|
||||||
|
heading=group.units[0].heading)
|
||||||
|
vg.units[0].name = self.m.string(group.units[0].name)
|
||||||
|
vg.units[0].player_can_drive = True
|
||||||
|
for i, u in enumerate(group.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
|
||||||
|
vehicle.player_can_drive = True
|
||||||
|
vg.add_unit(vehicle)
|
||||||
|
|
||||||
|
self.enable_eplrs(vg, unit_type)
|
||||||
|
self.set_alarm_state(vg)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def enable_eplrs(group: Group, unit_type: UnitType) -> None:
|
||||||
|
if hasattr(unit_type, 'eplrs'):
|
||||||
|
if unit_type.eplrs:
|
||||||
|
group.points[0].tasks.append(EPLRS(group.id))
|
||||||
|
|
||||||
|
def set_alarm_state(self, group: Group) -> None:
|
||||||
|
if self.game.settings.perf_red_alert_state:
|
||||||
|
group.points[0].tasks.append(OptAlarmState(2))
|
||||||
|
else:
|
||||||
|
group.points[0].tasks.append(OptAlarmState(1))
|
||||||
|
|
||||||
|
|
||||||
|
class BuildingSiteGenerator(GenericGroundObjectGenerator):
|
||||||
|
"""Generator for building sites.
|
||||||
|
|
||||||
|
Building sites are the primary type of non-airbase objective locations that
|
||||||
|
appear on the map. They come in a handful of variants each with different
|
||||||
|
types of buildings and ground units.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def generate(self) -> None:
|
||||||
|
if self.game.position_culled(self.ground_object.position):
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.ground_object.dcs_identifier in warehouse_map:
|
||||||
|
static_type = warehouse_map[self.ground_object.dcs_identifier]
|
||||||
|
self.generate_static(static_type)
|
||||||
|
elif self.ground_object.dcs_identifier in fortification_map:
|
||||||
|
static_type = fortification_map[self.ground_object.dcs_identifier]
|
||||||
|
self.generate_static(static_type)
|
||||||
|
elif self.ground_object.dcs_identifier in FORTIFICATION_UNITS_ID:
|
||||||
|
for f in FORTIFICATION_UNITS:
|
||||||
|
if f.id == self.ground_object.dcs_identifier:
|
||||||
|
unit_type = f
|
||||||
|
self.generate_vehicle_group(unit_type)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
logging.error(
|
||||||
|
f"{self.ground_object.dcs_identifier} not found in static maps")
|
||||||
|
|
||||||
|
def generate_vehicle_group(self, unit_type: UnitType) -> None:
|
||||||
|
if not self.ground_object.is_dead:
|
||||||
|
self.m.vehicle_group(
|
||||||
|
country=self.country,
|
||||||
|
name=self.ground_object.string_identifier,
|
||||||
|
_type=unit_type,
|
||||||
|
position=self.ground_object.position,
|
||||||
|
heading=self.ground_object.heading,
|
||||||
|
)
|
||||||
|
|
||||||
|
def generate_static(self, static_type: StaticType) -> None:
|
||||||
|
self.m.static_group(
|
||||||
|
country=self.country,
|
||||||
|
name=self.ground_object.string_identifier,
|
||||||
|
_type=static_type,
|
||||||
|
position=self.ground_object.position,
|
||||||
|
heading=self.ground_object.heading,
|
||||||
|
dead=self.ground_object.is_dead,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GenericCarrierGenerator(GenericGroundObjectGenerator):
|
||||||
|
"""Base type for carrier group generation.
|
||||||
|
|
||||||
|
Used by both CV(N) groups and LHA groups.
|
||||||
|
"""
|
||||||
|
def __init__(self, ground_object: GenericCarrierGroundObject,
|
||||||
|
control_point: ControlPoint, country: Country, game: Game,
|
||||||
|
mission: Mission, radio_registry: RadioRegistry,
|
||||||
|
tacan_registry: TacanRegistry, icls_alloc: Iterator[int],
|
||||||
|
runways: Dict[str, RunwayData]) -> None:
|
||||||
|
super().__init__(ground_object, country, game, mission)
|
||||||
|
self.ground_object = ground_object
|
||||||
|
self.control_point = control_point
|
||||||
|
self.radio_registry = radio_registry
|
||||||
|
self.tacan_registry = tacan_registry
|
||||||
|
self.icls_alloc = icls_alloc
|
||||||
|
self.runways = runways
|
||||||
|
|
||||||
|
def generate(self) -> None:
|
||||||
|
# TODO: Require single group?
|
||||||
|
for group in self.ground_object.groups:
|
||||||
|
if not group.units:
|
||||||
|
logging.warning(
|
||||||
|
f"Found empty carrier group in {self.control_point}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
atc = self.radio_registry.alloc_uhf()
|
||||||
|
ship_group = self.configure_carrier(group, atc)
|
||||||
|
for unit in group.units[1:]:
|
||||||
|
ship_group.add_unit(self.create_ship(unit, atc))
|
||||||
|
|
||||||
|
tacan = self.tacan_registry.alloc_for_band(TacanBand.X)
|
||||||
|
tacan_callsign = self.tacan_callsign()
|
||||||
|
icls = next(self.icls_alloc)
|
||||||
|
|
||||||
|
brc = self.steam_into_wind(ship_group)
|
||||||
|
self.activate_beacons(ship_group, tacan, tacan_callsign, icls)
|
||||||
|
self.add_runway_data(brc or 0, atc, tacan, tacan_callsign, icls)
|
||||||
|
|
||||||
|
def get_carrier_type(self, group: Group) -> UnitType:
|
||||||
|
unit_type = unit_type_from_name(group.units[0].type)
|
||||||
|
if unit_type is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Unrecognized carrier name: {group.units[0].type}")
|
||||||
|
return unit_type
|
||||||
|
|
||||||
|
def configure_carrier(self, group: Group,
|
||||||
|
atc_channel: RadioFrequency) -> ShipGroup:
|
||||||
|
unit_type = self.get_carrier_type(group)
|
||||||
|
|
||||||
|
ship_group = self.m.ship_group(self.country, group.name, unit_type,
|
||||||
|
position=group.position,
|
||||||
|
heading=group.units[0].heading)
|
||||||
|
ship_group.set_frequency(atc_channel.hertz)
|
||||||
|
ship_group.units[0].name = self.m.string(group.units[0].name)
|
||||||
|
return ship_group
|
||||||
|
|
||||||
|
def create_ship(self, unit: Unit, atc_channel: RadioFrequency) -> Ship:
|
||||||
|
ship = Ship(self.m.next_unit_id(),
|
||||||
|
self.m.string(unit.name),
|
||||||
|
unit_type_from_name(unit.type))
|
||||||
|
ship.position.x = unit.position.x
|
||||||
|
ship.position.y = unit.position.y
|
||||||
|
ship.heading = unit.heading
|
||||||
|
# TODO: Verify.
|
||||||
|
ship.set_frequency(atc_channel.hertz)
|
||||||
|
return ship
|
||||||
|
|
||||||
|
def steam_into_wind(self, group: ShipGroup) -> Optional[int]:
|
||||||
|
brc = self.m.weather.wind_at_ground.direction + 180
|
||||||
|
for attempt in range(5):
|
||||||
|
point = group.points[0].position.point_from_heading(
|
||||||
|
brc, 100000 - attempt * 20000)
|
||||||
|
if self.game.theater.is_in_sea(point):
|
||||||
|
group.add_waypoint(point)
|
||||||
|
return brc
|
||||||
|
return None
|
||||||
|
|
||||||
|
def tacan_callsign(self) -> str:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def activate_beacons(group: ShipGroup, tacan: TacanChannel,
|
||||||
|
callsign: str, icls: int) -> None:
|
||||||
|
group.points[0].tasks.append(ActivateBeaconCommand(
|
||||||
|
channel=tacan.number,
|
||||||
|
modechannel=tacan.band.value,
|
||||||
|
callsign=callsign,
|
||||||
|
unit_id=group.units[0].id,
|
||||||
|
aa=False
|
||||||
|
))
|
||||||
|
group.points[0].tasks.append(ActivateICLSCommand(
|
||||||
|
icls, unit_id=group.units[0].id
|
||||||
|
))
|
||||||
|
|
||||||
|
def add_runway_data(self, brc: int, atc: RadioFrequency,
|
||||||
|
tacan: TacanChannel, callsign: str, icls: int) -> None:
|
||||||
|
# TODO: Make unit name usable.
|
||||||
|
# This relies on one control point mapping exactly
|
||||||
|
# to one LHA, carrier, or other usable "runway".
|
||||||
|
# This isn't wholly true, since the DD escorts of
|
||||||
|
# the carrier group are valid for helicopters, but
|
||||||
|
# they aren't exposed as such to the game. Should
|
||||||
|
# clean this up so that's possible. We can't use the
|
||||||
|
# unit name since it's an arbitrary ID.
|
||||||
|
self.runways[self.control_point.name] = RunwayData(
|
||||||
|
self.control_point.name,
|
||||||
|
brc,
|
||||||
|
"N/A",
|
||||||
|
atc=atc,
|
||||||
|
tacan=tacan,
|
||||||
|
tacan_callsign=callsign,
|
||||||
|
icls=icls,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CarrierGenerator(GenericCarrierGenerator):
|
||||||
|
"""Generator for CV(N) groups."""
|
||||||
|
|
||||||
|
def get_carrier_type(self, group: Group) -> UnitType:
|
||||||
|
unit_type = super().get_carrier_type(group)
|
||||||
|
if self.game.settings.supercarrier:
|
||||||
|
unit_type = db.upgrade_to_supercarrier(unit_type,
|
||||||
|
self.control_point.name)
|
||||||
|
return unit_type
|
||||||
|
|
||||||
|
def tacan_callsign(self) -> str:
|
||||||
|
# TODO: Assign these properly.
|
||||||
|
return random.choice([
|
||||||
|
"STE",
|
||||||
|
"CVN",
|
||||||
|
"CVH",
|
||||||
|
"CCV",
|
||||||
|
"ACC",
|
||||||
|
"ARC",
|
||||||
|
"GER",
|
||||||
|
"ABR",
|
||||||
|
"LIN",
|
||||||
|
"TRU",
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class LhaGenerator(GenericCarrierGenerator):
|
||||||
|
"""Generator for LHA groups."""
|
||||||
|
|
||||||
|
def tacan_callsign(self) -> str:
|
||||||
|
# TODO: Assign these properly.
|
||||||
|
return random.choice([
|
||||||
|
"LHD",
|
||||||
|
"LHA",
|
||||||
|
"LHB",
|
||||||
|
"LHC",
|
||||||
|
"LHD",
|
||||||
|
"LDS",
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class ShipObjectGenerator(GenericGroundObjectGenerator):
|
||||||
|
"""Generator for non-carrier naval groups."""
|
||||||
|
|
||||||
|
def generate(self) -> None:
|
||||||
|
if self.game.position_culled(self.ground_object.position):
|
||||||
|
return
|
||||||
|
|
||||||
|
for group in self.ground_object.groups:
|
||||||
|
if not group.units:
|
||||||
|
logging.warning(f"Found empty group in {self.ground_object}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
unit_type = unit_type_from_name(group.units[0].type)
|
||||||
|
if unit_type is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Unrecognized unit type: {group.units[0].type}")
|
||||||
|
|
||||||
|
self.generate_group(group, unit_type)
|
||||||
|
|
||||||
|
def generate_group(self, group_def: Group, unit_type: UnitType):
|
||||||
|
group = self.m.ship_group(self.country, group_def.name, unit_type,
|
||||||
|
position=group_def.position,
|
||||||
|
heading=group_def.units[0].heading)
|
||||||
|
group.units[0].name = self.m.string(group_def.units[0].name)
|
||||||
|
# TODO: Skipping the first unit looks like copy pasta from the carrier.
|
||||||
|
for unit in group_def.units[1:]:
|
||||||
|
unit_type = unit_type_from_name(unit.type)
|
||||||
|
ship = Ship(self.m.next_unit_id(),
|
||||||
|
self.m.string(unit.name), unit_type)
|
||||||
|
ship.position.x = unit.position.x
|
||||||
|
ship.position.y = unit.position.y
|
||||||
|
ship.heading = unit.heading
|
||||||
|
group.add_unit(ship)
|
||||||
|
self.set_alarm_state(group)
|
||||||
|
|
||||||
|
|
||||||
class GroundObjectsGenerator:
|
class GroundObjectsGenerator:
|
||||||
|
"""Creates DCS groups and statics for the theater during mission generation.
|
||||||
|
|
||||||
|
Most of the work of group/static generation is delegated to the other
|
||||||
|
generator classes. This class is responsible for finding each of the
|
||||||
|
locations for spawning ground objects, determining their types, and creating
|
||||||
|
the appropriate generators.
|
||||||
|
"""
|
||||||
FARP_CAPACITY = 4
|
FARP_CAPACITY = 4
|
||||||
|
|
||||||
def __init__(self, mission: Mission, conflict: Conflict, game,
|
def __init__(self, mission: Mission, conflict: Conflict, game,
|
||||||
@ -61,188 +388,34 @@ class GroundObjectsGenerator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
|
|
||||||
for cp in self.game.theater.controlpoints:
|
for cp in self.game.theater.controlpoints:
|
||||||
|
|
||||||
if cp.captured:
|
if cp.captured:
|
||||||
country = self.game.player_country
|
country_name = self.game.player_country
|
||||||
else:
|
else:
|
||||||
country = self.game.enemy_country
|
country_name = self.game.enemy_country
|
||||||
side = self.m.country(country)
|
country = self.m.country(country_name)
|
||||||
|
|
||||||
for ground_object in cp.ground_objects:
|
for ground_object in cp.ground_objects:
|
||||||
if ground_object.dcs_identifier == "AA":
|
if isinstance(ground_object, BuildingGroundObject):
|
||||||
|
generator = BuildingSiteGenerator(ground_object, country,
|
||||||
if self.game.position_culled(ground_object.position):
|
self.game, self.m)
|
||||||
continue
|
elif isinstance(ground_object, CarrierGroundObject):
|
||||||
|
generator = CarrierGenerator(ground_object, cp, country,
|
||||||
for g in ground_object.groups:
|
self.game, self.m,
|
||||||
if len(g.units) > 0:
|
self.radio_registry,
|
||||||
utype = unit_type_from_name(g.units[0].type)
|
self.tacan_registry,
|
||||||
|
self.icls_alloc, self.runways)
|
||||||
if not ground_object.sea_object:
|
elif isinstance(ground_object, LhaGroundObject):
|
||||||
vg = self.m.vehicle_group(side, g.name, utype, position=g.position, heading=g.units[0].heading)
|
generator = CarrierGenerator(ground_object, cp, country,
|
||||||
vg.units[0].name = self.m.string(g.units[0].name)
|
self.game, self.m,
|
||||||
vg.units[0].player_can_drive = True
|
self.radio_registry,
|
||||||
for i, u in enumerate(g.units):
|
self.tacan_registry,
|
||||||
if i > 0:
|
self.icls_alloc, self.runways)
|
||||||
vehicle = Vehicle(self.m.next_unit_id(), self.m.string(u.name), u.type)
|
elif isinstance(ground_object, ShipGroundObject):
|
||||||
vehicle.position.x = u.position.x
|
generator = ShipObjectGenerator(ground_object, country,
|
||||||
vehicle.position.y = u.position.y
|
self.game, self.m)
|
||||||
vehicle.heading = u.heading
|
|
||||||
vehicle.player_can_drive = True
|
|
||||||
vg.add_unit(vehicle)
|
|
||||||
|
|
||||||
if hasattr(utype, 'eplrs'):
|
|
||||||
if utype.eplrs:
|
|
||||||
vg.points[0].tasks.append(EPLRS(vg.id))
|
|
||||||
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:
|
|
||||||
|
|
||||||
utype = unit_type_from_name(g.units[0].type)
|
|
||||||
if ground_object.dcs_identifier == "CARRIER" and self.game.settings.supercarrier == True:
|
|
||||||
utype = db.upgrade_to_supercarrier(utype, cp.name)
|
|
||||||
|
|
||||||
sg = self.m.ship_group(side, g.name, utype, position=g.position, heading=g.units[0].heading)
|
|
||||||
atc_channel = self.radio_registry.alloc_uhf()
|
|
||||||
sg.set_frequency(atc_channel.hertz)
|
|
||||||
sg.units[0].name = self.m.string(g.units[0].name)
|
|
||||||
|
|
||||||
for i, u in enumerate(g.units):
|
|
||||||
if i > 0:
|
|
||||||
ship = Ship(self.m.next_unit_id(), self.m.string(u.name), unit_type_from_name(u.type))
|
|
||||||
ship.position.x = u.position.x
|
|
||||||
ship.position.y = u.position.y
|
|
||||||
ship.heading = u.heading
|
|
||||||
# TODO: Verify.
|
|
||||||
ship.set_frequency(atc_channel.hertz)
|
|
||||||
sg.add_unit(ship)
|
|
||||||
|
|
||||||
# Find carrier direction (In the wind)
|
|
||||||
found_carrier_destination = False
|
|
||||||
attempt = 0
|
|
||||||
brc = self.m.weather.wind_at_ground.direction + 180
|
|
||||||
while not found_carrier_destination and attempt < 5:
|
|
||||||
point = sg.points[0].position.point_from_heading(brc, 100000-attempt*20000)
|
|
||||||
if self.game.theater.is_in_sea(point):
|
|
||||||
found_carrier_destination = True
|
|
||||||
sg.add_waypoint(point)
|
|
||||||
else:
|
|
||||||
attempt = attempt + 1
|
|
||||||
|
|
||||||
# Set UP TACAN and ICLS
|
|
||||||
tacan = self.tacan_registry.alloc_for_band(TacanBand.X)
|
|
||||||
icls_channel = next(self.icls_alloc)
|
|
||||||
# TODO: Assign these properly.
|
|
||||||
if ground_object.dcs_identifier == "CARRIER":
|
|
||||||
tacan_callsign = random.choice([
|
|
||||||
"STE",
|
|
||||||
"CVN",
|
|
||||||
"CVH",
|
|
||||||
"CCV",
|
|
||||||
"ACC",
|
|
||||||
"ARC",
|
|
||||||
"GER",
|
|
||||||
"ABR",
|
|
||||||
"LIN",
|
|
||||||
"TRU",
|
|
||||||
])
|
|
||||||
else:
|
|
||||||
tacan_callsign = random.choice([
|
|
||||||
"LHD",
|
|
||||||
"LHA",
|
|
||||||
"LHB",
|
|
||||||
"LHC",
|
|
||||||
"LHD",
|
|
||||||
"LDS",
|
|
||||||
])
|
|
||||||
sg.points[0].tasks.append(ActivateBeaconCommand(
|
|
||||||
channel=tacan.number,
|
|
||||||
modechannel=tacan.band.value,
|
|
||||||
callsign=tacan_callsign,
|
|
||||||
unit_id=sg.units[0].id,
|
|
||||||
aa=False
|
|
||||||
))
|
|
||||||
sg.points[0].tasks.append(ActivateICLSCommand(
|
|
||||||
icls_channel,
|
|
||||||
unit_id=sg.units[0].id
|
|
||||||
))
|
|
||||||
# TODO: Make unit name usable.
|
|
||||||
# This relies on one control point mapping exactly
|
|
||||||
# to one LHA, carrier, or other usable "runway".
|
|
||||||
# This isn't wholly true, since the DD escorts of
|
|
||||||
# the carrier group are valid for helicopters, but
|
|
||||||
# they aren't exposed as such to the game. Should
|
|
||||||
# clean this up so that's possible. We can't use the
|
|
||||||
# unit name since it's an arbitrary ID.
|
|
||||||
self.runways[cp.name] = RunwayData(
|
|
||||||
cp.name,
|
|
||||||
brc,
|
|
||||||
"N/A",
|
|
||||||
atc=atc_channel,
|
|
||||||
tacan=tacan,
|
|
||||||
tacan_callsign=tacan_callsign,
|
|
||||||
icls=icls_channel,
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
generator = GenericGroundObjectGenerator(ground_object,
|
||||||
if self.game.position_culled(ground_object.position):
|
country, self.game,
|
||||||
continue
|
self.m)
|
||||||
|
generator.generate()
|
||||||
static_type = None
|
|
||||||
if ground_object.dcs_identifier in warehouse_map:
|
|
||||||
static_type = warehouse_map[ground_object.dcs_identifier]
|
|
||||||
elif ground_object.dcs_identifier in fortification_map:
|
|
||||||
static_type = fortification_map[ground_object.dcs_identifier]
|
|
||||||
elif ground_object.dcs_identifier in FORTIFICATION_UNITS_ID:
|
|
||||||
for f in FORTIFICATION_UNITS:
|
|
||||||
if f.id == ground_object.dcs_identifier:
|
|
||||||
unit_type = f
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
print("Didn't find {} in static _map(s)!".format(ground_object.dcs_identifier))
|
|
||||||
continue
|
|
||||||
|
|
||||||
if static_type is None:
|
|
||||||
if not ground_object.is_dead:
|
|
||||||
group = self.m.vehicle_group(
|
|
||||||
country=side,
|
|
||||||
name=ground_object.string_identifier,
|
|
||||||
_type=unit_type,
|
|
||||||
position=ground_object.position,
|
|
||||||
heading=ground_object.heading,
|
|
||||||
)
|
|
||||||
logging.info("generated {}object identifier {} with mission id {}".format(
|
|
||||||
"dead " if ground_object.is_dead else "", group.name, group.id))
|
|
||||||
else:
|
|
||||||
group = self.m.static_group(
|
|
||||||
country=side,
|
|
||||||
name=ground_object.string_identifier,
|
|
||||||
_type=static_type,
|
|
||||||
position=ground_object.position,
|
|
||||||
heading=ground_object.heading,
|
|
||||||
dead=ground_object.is_dead,
|
|
||||||
)
|
|
||||||
|
|
||||||
logging.info("generated {}object identifier {} with mission id {}".format("dead " if ground_object.is_dead else "", group.name, group.id))
|
|
||||||
|
|||||||
@ -15,7 +15,12 @@ if TYPE_CHECKING:
|
|||||||
from game.game import Game
|
from game.game import Game
|
||||||
|
|
||||||
|
|
||||||
class GroupGenerator():
|
# TODO: Generate a group description rather than a pydcs group.
|
||||||
|
# It appears that all of this work gets redone at miz generation time (see
|
||||||
|
# groundobjectsgen for an example). We can do less work and include the data we
|
||||||
|
# care about in the format we want if we just generate our own group description
|
||||||
|
# types rather than pydcs groups.
|
||||||
|
class GroupGenerator:
|
||||||
|
|
||||||
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Optional[Faction] = None): # faction is not mandatory because some subclasses do not use it
|
def __init__(self, game: Game, ground_object: TheaterGroundObject, faction: Optional[Faction] = None): # faction is not mandatory because some subclasses do not use it
|
||||||
self.game = game
|
self.game = game
|
||||||
@ -34,7 +39,7 @@ class GroupGenerator():
|
|||||||
def generate(self):
|
def generate(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def get_generated_group(self):
|
def get_generated_group(self) -> unitgroup.VehicleGroup:
|
||||||
return self.vg
|
return self.vg
|
||||||
|
|
||||||
def add_unit(self, unit_type: VehicleType, name: str, pos_x: float, pos_y: float, heading: int):
|
def add_unit(self, unit_type: VehicleType, name: str, pos_x: float, pos_y: float, heading: int):
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import random
|
import random
|
||||||
from typing import List, Type
|
from typing import List, Optional, Type
|
||||||
|
|
||||||
from dcs.vehicles import AirDefence
|
from dcs.vehicles import AirDefence
|
||||||
|
from dcs.unitgroup import VehicleGroup
|
||||||
|
|
||||||
from game import db
|
from game import Game, db
|
||||||
from gen.sam.aaa_bofors import BoforsGenerator
|
from gen.sam.aaa_bofors import BoforsGenerator
|
||||||
from gen.sam.aaa_flak import FlakGenerator
|
from gen.sam.aaa_flak import FlakGenerator
|
||||||
from gen.sam.aaa_flak18 import Flak18Generator
|
from gen.sam.aaa_flak18 import Flak18Generator
|
||||||
@ -34,6 +35,7 @@ from gen.sam.sam_zsu23 import ZSU23Generator
|
|||||||
from gen.sam.sam_zu23 import ZU23Generator
|
from gen.sam.sam_zu23 import ZU23Generator
|
||||||
from gen.sam.sam_zu23_ural import ZU23UralGenerator
|
from gen.sam.sam_zu23_ural import ZU23UralGenerator
|
||||||
from gen.sam.sam_zu23_ural_insurgent import ZU23UralInsurgentGenerator
|
from gen.sam.sam_zu23_ural_insurgent import ZU23UralInsurgentGenerator
|
||||||
|
from theater import TheaterGroundObject
|
||||||
|
|
||||||
SAM_MAP = {
|
SAM_MAP = {
|
||||||
"HawkGenerator": HawkGenerator,
|
"HawkGenerator": HawkGenerator,
|
||||||
@ -111,13 +113,14 @@ def get_faction_possible_sams_generator(faction: str) -> List[Type[GroupGenerato
|
|||||||
return [SAM_MAP[s] for s in db.FACTIONS[faction].sams if s in SAM_MAP.keys()]
|
return [SAM_MAP[s] for s in db.FACTIONS[faction].sams if s in SAM_MAP.keys()]
|
||||||
|
|
||||||
|
|
||||||
def generate_anti_air_group(game, parent_cp, ground_object, faction: str):
|
def generate_anti_air_group(game: Game, ground_object: TheaterGroundObject,
|
||||||
|
faction: str) -> Optional[VehicleGroup]:
|
||||||
"""
|
"""
|
||||||
This generate a SAM group
|
This generate a SAM group
|
||||||
:param parentCp: The parent control point
|
:param game: The Game.
|
||||||
:param ground_object: The ground object which will own the sam group
|
:param ground_object: The ground object which will own the sam group.
|
||||||
:param country: Owner country
|
:param faction: Owner faction.
|
||||||
:return: Nothing, but put the group reference inside the ground object
|
:return: The generated group, or None if one could not be generated.
|
||||||
"""
|
"""
|
||||||
possible_sams_generators = get_faction_possible_sams_generator(faction)
|
possible_sams_generators = get_faction_possible_sams_generator(faction)
|
||||||
if len(possible_sams_generators) > 0:
|
if len(possible_sams_generators) > 0:
|
||||||
@ -128,7 +131,8 @@ def generate_anti_air_group(game, parent_cp, ground_object, faction: str):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def generate_shorad_group(game, parent_cp, ground_object, faction_name: str):
|
def generate_shorad_group(game: Game, ground_object: TheaterGroundObject,
|
||||||
|
faction_name: str) -> Optional[VehicleGroup]:
|
||||||
faction = db.FACTIONS[faction_name]
|
faction = db.FACTIONS[faction_name]
|
||||||
|
|
||||||
if len(faction.shorads) > 0:
|
if len(faction.shorads) > 0:
|
||||||
@ -137,4 +141,4 @@ def generate_shorad_group(game, parent_cp, ground_object, faction_name: str):
|
|||||||
generator.generate()
|
generator.generate()
|
||||||
return generator.get_generated_group()
|
return generator.get_generated_group()
|
||||||
else:
|
else:
|
||||||
return generate_anti_air_group(game, parent_cp, ground_object, faction_name)
|
return generate_anti_air_group(game, ground_object, faction_name)
|
||||||
|
|||||||
@ -7,7 +7,7 @@ from PySide2 import QtWidgets
|
|||||||
from PySide2.QtGui import QPixmap
|
from PySide2.QtGui import QPixmap
|
||||||
from PySide2.QtWidgets import QApplication, QSplashScreen
|
from PySide2.QtWidgets import QApplication, QSplashScreen
|
||||||
|
|
||||||
from game import db, persistency
|
from game import db, persistency, VERSION
|
||||||
from qt_ui import (
|
from qt_ui import (
|
||||||
liberation_install,
|
liberation_install,
|
||||||
liberation_theme,
|
liberation_theme,
|
||||||
@ -20,7 +20,7 @@ from qt_ui.windows.preferences.QLiberationFirstStartWindow import \
|
|||||||
QLiberationFirstStartWindow
|
QLiberationFirstStartWindow
|
||||||
|
|
||||||
# Logging setup
|
# Logging setup
|
||||||
logging_config.init_logging(uiconstants.VERSION_STRING)
|
logging_config.init_logging(VERSION)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Load eagerly to catch errors early.
|
# Load eagerly to catch errors early.
|
||||||
|
|||||||
@ -7,10 +7,6 @@ from PySide2.QtGui import QColor, QFont, QPixmap
|
|||||||
from theater.theatergroundobject import CATEGORY_MAP
|
from theater.theatergroundobject import CATEGORY_MAP
|
||||||
from .liberation_theme import get_theme_icons
|
from .liberation_theme import get_theme_icons
|
||||||
|
|
||||||
VERSION_STRING = "2.2.0-preview"
|
|
||||||
if Path("buildnumber").exists():
|
|
||||||
with open("buildnumber", "r") as file:
|
|
||||||
VERSION_STRING += f"-{file.readline()}"
|
|
||||||
|
|
||||||
URLS : Dict[str, str] = {
|
URLS : Dict[str, str] = {
|
||||||
"Manual": "https://github.com/khopa/dcs_liberation/wiki",
|
"Manual": "https://github.com/khopa/dcs_liberation/wiki",
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import sys
|
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from typing import Optional, Union
|
from typing import Optional
|
||||||
|
|
||||||
from PySide2.QtCore import Qt
|
from PySide2.QtCore import Qt
|
||||||
from PySide2.QtGui import QCloseEvent, QIcon
|
from PySide2.QtGui import QCloseEvent, QIcon
|
||||||
@ -10,14 +9,14 @@ from PySide2.QtWidgets import (
|
|||||||
QActionGroup, QDesktopWidget,
|
QActionGroup, QDesktopWidget,
|
||||||
QFileDialog,
|
QFileDialog,
|
||||||
QMainWindow,
|
QMainWindow,
|
||||||
QMenu, QMessageBox,
|
QMessageBox,
|
||||||
QSplitter,
|
QSplitter,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
QWidget,
|
QWidget,
|
||||||
)
|
)
|
||||||
|
|
||||||
import qt_ui.uiconstants as CONST
|
import qt_ui.uiconstants as CONST
|
||||||
from game import Game, persistency
|
from game import Game, persistency, VERSION
|
||||||
from qt_ui.dialogs import Dialog
|
from qt_ui.dialogs import Dialog
|
||||||
from qt_ui.displayoptions import DisplayGroup, DisplayOptions, DisplayRule
|
from qt_ui.displayoptions import DisplayGroup, DisplayOptions, DisplayRule
|
||||||
from qt_ui.models import GameModel
|
from qt_ui.models import GameModel
|
||||||
@ -47,7 +46,7 @@ class QLiberationWindow(QMainWindow):
|
|||||||
self.setGame(persistency.restore_game())
|
self.setGame(persistency.restore_game())
|
||||||
|
|
||||||
self.setGeometry(300, 100, 270, 100)
|
self.setGeometry(300, 100, 270, 100)
|
||||||
self.setWindowTitle("DCS Liberation - v" + CONST.VERSION_STRING)
|
self.setWindowTitle(f"DCS Liberation - v{VERSION}")
|
||||||
self.setWindowIcon(QIcon("./resources/icon.png"))
|
self.setWindowIcon(QIcon("./resources/icon.png"))
|
||||||
self.statusBar().showMessage('Ready')
|
self.statusBar().showMessage('Ready')
|
||||||
|
|
||||||
@ -228,7 +227,7 @@ class QLiberationWindow(QMainWindow):
|
|||||||
self.liberation_map.setGame(game)
|
self.liberation_map.setGame(game)
|
||||||
|
|
||||||
def showAboutDialog(self):
|
def showAboutDialog(self):
|
||||||
text = "<h3>DCS Liberation " + CONST.VERSION_STRING + "</h3>" + \
|
text = "<h3>DCS Liberation " + VERSION + "</h3>" + \
|
||||||
"<b>Source code :</b> https://github.com/khopa/dcs_liberation" + \
|
"<b>Source code :</b> https://github.com/khopa/dcs_liberation" + \
|
||||||
"<h4>Authors</h4>" + \
|
"<h4>Authors</h4>" + \
|
||||||
"<p>DCS Liberation was originally developed by <b>shdwp</b>, DCS Liberation 2.0 is a partial rewrite based on this work by <b>Khopa</b>." \
|
"<p>DCS Liberation was originally developed by <b>shdwp</b>, DCS Liberation 2.0 is a partial rewrite based on this work by <b>Khopa</b>." \
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import datetime
|
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
@ -10,15 +9,14 @@ from PySide2.QtWidgets import QVBoxLayout
|
|||||||
from dcs.task import CAP, CAS
|
from dcs.task import CAP, CAS
|
||||||
|
|
||||||
import qt_ui.uiconstants as CONST
|
import qt_ui.uiconstants as CONST
|
||||||
from game import Game, db
|
from game import db
|
||||||
from game.settings import Settings
|
from game.settings import Settings
|
||||||
from gen import namegen
|
|
||||||
from qt_ui.windows.newgame.QCampaignList import (
|
from qt_ui.windows.newgame.QCampaignList import (
|
||||||
Campaign,
|
Campaign,
|
||||||
QCampaignList,
|
QCampaignList,
|
||||||
load_campaigns,
|
load_campaigns,
|
||||||
)
|
)
|
||||||
from theater import ConflictTheater, start_generator
|
from theater.start_generator import GameGenerator
|
||||||
|
|
||||||
|
|
||||||
class NewGameWizard(QtWidgets.QWizard):
|
class NewGameWizard(QtWidgets.QWizard):
|
||||||
@ -76,39 +74,13 @@ class NewGameWizard(QtWidgets.QWizard):
|
|||||||
settings.do_not_generate_player_navy = no_player_navy
|
settings.do_not_generate_player_navy = no_player_navy
|
||||||
settings.do_not_generate_enemy_navy = no_enemy_navy
|
settings.do_not_generate_enemy_navy = no_enemy_navy
|
||||||
|
|
||||||
self.generatedGame = self.start_new_game(player_name, enemy_name, conflictTheater, midGame, multiplier,
|
generator = GameGenerator(player_name, enemy_name, conflictTheater,
|
||||||
timePeriod, settings, starting_money)
|
settings, timePeriod, starting_money,
|
||||||
|
multiplier, midGame)
|
||||||
|
self.generatedGame = generator.generate()
|
||||||
|
|
||||||
super(NewGameWizard, self).accept()
|
super(NewGameWizard, self).accept()
|
||||||
|
|
||||||
def start_new_game(self, player_name: str, enemy_name: str, conflictTheater: ConflictTheater,
|
|
||||||
midgame: bool, multiplier: float, period: datetime, settings:Settings, starting_money: int):
|
|
||||||
|
|
||||||
# Reset name generator
|
|
||||||
namegen.reset()
|
|
||||||
start_generator.prepare_theater(conflictTheater, settings, midgame)
|
|
||||||
|
|
||||||
print("-- Starting New Game Generator")
|
|
||||||
print("Enemy name : " + enemy_name)
|
|
||||||
print("Player name : " + player_name)
|
|
||||||
print("Midgame : " + str(midgame))
|
|
||||||
start_generator.generate_initial_units(conflictTheater, enemy_name, True, multiplier)
|
|
||||||
|
|
||||||
print("-- Initial units generated")
|
|
||||||
game = Game(player_name=player_name,
|
|
||||||
enemy_name=enemy_name,
|
|
||||||
theater=conflictTheater,
|
|
||||||
start_date=period,
|
|
||||||
settings=settings)
|
|
||||||
|
|
||||||
print("-- Game Object generated")
|
|
||||||
start_generator.generate_groundobjects(conflictTheater, game)
|
|
||||||
game.budget = starting_money
|
|
||||||
game.settings.multiplier = multiplier
|
|
||||||
game.settings.sams = True
|
|
||||||
game.settings.version = CONST.VERSION_STRING
|
|
||||||
return game
|
|
||||||
|
|
||||||
|
|
||||||
class IntroPage(QtWidgets.QWizardPage):
|
class IntroPage(QtWidgets.QWizardPage):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
|
|||||||
@ -35,7 +35,6 @@ class ControlPoint(MissionTarget):
|
|||||||
|
|
||||||
position = None # type: Point
|
position = None # type: Point
|
||||||
name = None # type: str
|
name = None # type: str
|
||||||
allow_sea_units = True
|
|
||||||
|
|
||||||
captured = False
|
captured = False
|
||||||
has_frontline = True
|
has_frontline = True
|
||||||
@ -47,10 +46,9 @@ class ControlPoint(MissionTarget):
|
|||||||
at: db.StartingPosition, radials: List[int], size: int,
|
at: db.StartingPosition, radials: List[int], size: int,
|
||||||
importance: float, has_frontline=True,
|
importance: float, has_frontline=True,
|
||||||
cptype=ControlPointType.AIRBASE):
|
cptype=ControlPointType.AIRBASE):
|
||||||
|
super().__init__(" ".join(re.split(r" |-", name)[:2]), position)
|
||||||
self.id = id
|
self.id = id
|
||||||
self.name = " ".join(re.split(r" |-", name)[:2])
|
|
||||||
self.full_name = name
|
self.full_name = name
|
||||||
self.position: Point = position
|
|
||||||
self.at = at
|
self.at = at
|
||||||
self.ground_objects: List[TheaterGroundObject] = []
|
self.ground_objects: List[TheaterGroundObject] = []
|
||||||
|
|
||||||
@ -228,10 +226,8 @@ class ControlPoint(MissionTarget):
|
|||||||
|
|
||||||
# Handle cyclic dependency.
|
# Handle cyclic dependency.
|
||||||
from .start_generator import generate_airbase_defense_group
|
from .start_generator import generate_airbase_defense_group
|
||||||
airbase_def_id = 0
|
for idx, ground_object in enumerate(self.ground_objects):
|
||||||
for ground_object in self.ground_objects:
|
|
||||||
ground_object.groups = []
|
ground_object.groups = []
|
||||||
if ground_object.airbase_group and faction_name != "":
|
if ground_object.airbase_group and faction_name != "":
|
||||||
generate_airbase_defense_group(airbase_def_id, ground_object,
|
generate_airbase_defense_group(idx, ground_object,
|
||||||
faction_name, game, self)
|
faction_name, game)
|
||||||
airbase_def_id = airbase_def_id + 1
|
|
||||||
|
|||||||
@ -4,11 +4,27 @@ from typing import Tuple
|
|||||||
from dcs.mapping import Point
|
from dcs.mapping import Point
|
||||||
from . import ControlPoint, MissionTarget
|
from . import ControlPoint, MissionTarget
|
||||||
|
|
||||||
|
|
||||||
# TODO: Dedup by moving everything to using this class.
|
# TODO: Dedup by moving everything to using this class.
|
||||||
FRONTLINE_MIN_CP_DISTANCE = 5000
|
FRONTLINE_MIN_CP_DISTANCE = 5000
|
||||||
|
|
||||||
|
|
||||||
|
def compute_position(control_point_a: ControlPoint,
|
||||||
|
control_point_b: ControlPoint) -> Point:
|
||||||
|
a = control_point_a.position
|
||||||
|
b = control_point_b.position
|
||||||
|
attack_heading = a.heading_between_point(b)
|
||||||
|
attack_distance = a.distance_to_point(b)
|
||||||
|
middle_point = a.point_from_heading(attack_heading, attack_distance / 2)
|
||||||
|
|
||||||
|
strength_delta = float(control_point_a.base.strength -
|
||||||
|
control_point_b.base.strength)
|
||||||
|
position = middle_point.point_from_heading(attack_heading,
|
||||||
|
strength_delta *
|
||||||
|
attack_distance / 2 -
|
||||||
|
FRONTLINE_MIN_CP_DISTANCE)
|
||||||
|
return position
|
||||||
|
|
||||||
|
|
||||||
class FrontLine(MissionTarget):
|
class FrontLine(MissionTarget):
|
||||||
"""Defines a front line location between two control points.
|
"""Defines a front line location between two control points.
|
||||||
|
|
||||||
@ -17,6 +33,8 @@ class FrontLine(MissionTarget):
|
|||||||
|
|
||||||
def __init__(self, control_point_a: ControlPoint,
|
def __init__(self, control_point_a: ControlPoint,
|
||||||
control_point_b: ControlPoint) -> None:
|
control_point_b: ControlPoint) -> None:
|
||||||
|
super().__init__(f"Front line {control_point_a}/{control_point_b}",
|
||||||
|
compute_position(control_point_a, control_point_b))
|
||||||
self.control_point_a = control_point_a
|
self.control_point_a = control_point_a
|
||||||
self.control_point_b = control_point_b
|
self.control_point_b = control_point_b
|
||||||
|
|
||||||
@ -24,22 +42,3 @@ class FrontLine(MissionTarget):
|
|||||||
def control_points(self) -> Tuple[ControlPoint, ControlPoint]:
|
def control_points(self) -> Tuple[ControlPoint, ControlPoint]:
|
||||||
"""Returns a tuple of the two control points."""
|
"""Returns a tuple of the two control points."""
|
||||||
return self.control_point_a, self.control_point_b
|
return self.control_point_a, self.control_point_b
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self) -> str:
|
|
||||||
a = self.control_point_a.name
|
|
||||||
b = self.control_point_b.name
|
|
||||||
return f"Front line {a}/{b}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def position(self) -> Point:
|
|
||||||
a = self.control_point_a.position
|
|
||||||
b = self.control_point_b.position
|
|
||||||
attack_heading = a.heading_between_point(b)
|
|
||||||
attack_distance = a.distance_to_point(b)
|
|
||||||
middle_point = a.point_from_heading(attack_heading, attack_distance / 2)
|
|
||||||
|
|
||||||
strength_delta = (self.control_point_a.base.strength - self.control_point_b.base.strength) / 1.0
|
|
||||||
position = middle_point.point_from_heading(attack_heading,
|
|
||||||
strength_delta * attack_distance / 2 - FRONTLINE_MIN_CP_DISTANCE)
|
|
||||||
return position
|
|
||||||
|
|||||||
@ -1,22 +1,18 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
|
||||||
from dcs.mapping import Point
|
from dcs.mapping import Point
|
||||||
|
|
||||||
|
|
||||||
class MissionTarget(ABC):
|
class MissionTarget:
|
||||||
# TODO: These should just be required objects to the constructor
|
def __init__(self, name: str, position: Point) -> None:
|
||||||
# The TheatherGroundObject class is difficult to modify because it's
|
"""Initializes a mission target.
|
||||||
# generated data that's pickled ahead of time.
|
|
||||||
@property
|
|
||||||
@abstractmethod
|
|
||||||
def name(self) -> str:
|
|
||||||
"""The name of the mission target."""
|
|
||||||
|
|
||||||
@property
|
Args:
|
||||||
@abstractmethod
|
name: The name of the mission target.
|
||||||
def position(self) -> Point:
|
position: The location of the mission target.
|
||||||
"""The location of the mission target."""
|
"""
|
||||||
|
self.name = name
|
||||||
|
self.position = position
|
||||||
|
|
||||||
def distance_to(self, other: MissionTarget) -> int:
|
def distance_to(self, other: MissionTarget) -> int:
|
||||||
"""Computes the distance to the given mission target."""
|
"""Computes the distance to the given mission target."""
|
||||||
|
|||||||
@ -1,16 +1,19 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
import pickle
|
import pickle
|
||||||
import random
|
import random
|
||||||
import typing
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from dcs.mapping import Point
|
from dcs.mapping import Point
|
||||||
from dcs.task import CAP, CAS, PinpointStrike
|
from dcs.task import CAP, CAS, PinpointStrike
|
||||||
from dcs.vehicles import AirDefence
|
from dcs.vehicles import AirDefence
|
||||||
|
|
||||||
from game import db
|
from game import Game, db
|
||||||
from game.data.building_data import DEFAULT_AVAILABLE_BUILDINGS
|
from game.factions.faction import Faction
|
||||||
from game.settings import Settings
|
from game.settings import Settings
|
||||||
|
from game.version import VERSION
|
||||||
from gen import namegen
|
from gen import namegen
|
||||||
from gen.defenses.armor_group_generator import generate_armor_group
|
from gen.defenses.armor_group_generator import generate_armor_group
|
||||||
from gen.fleet.ship_group_generator import (
|
from gen.fleet.ship_group_generator import (
|
||||||
@ -30,6 +33,13 @@ from theater import (
|
|||||||
TheaterGroundObject,
|
TheaterGroundObject,
|
||||||
)
|
)
|
||||||
from theater.conflicttheater import IMPORTANCE_HIGH, IMPORTANCE_LOW
|
from theater.conflicttheater import IMPORTANCE_HIGH, IMPORTANCE_LOW
|
||||||
|
from theater.theatergroundobject import (
|
||||||
|
SamGroundObject, BuildingGroundObject, CarrierGroundObject,
|
||||||
|
LhaGroundObject,
|
||||||
|
MissileSiteGroundObject, ShipGroundObject,
|
||||||
|
)
|
||||||
|
|
||||||
|
GroundObjectTemplates = Dict[str, Dict[str, Any]]
|
||||||
|
|
||||||
UNIT_VARIETY = 6
|
UNIT_VARIETY = 6
|
||||||
UNIT_AMOUNT_FACTOR = 16
|
UNIT_AMOUNT_FACTOR = 16
|
||||||
@ -43,215 +53,387 @@ COUNT_BY_TASK = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def generate_initial_units(theater: ConflictTheater, enemy_country: str, sams: bool, multiplier: float):
|
class GameGenerator:
|
||||||
for cp in theater.enemy_points():
|
def __init__(self, player: str, enemy: str, theater: ConflictTheater,
|
||||||
if cp.captured:
|
settings: Settings, start_date, starting_budget: int,
|
||||||
continue
|
multiplier: float, midgame: bool) -> None:
|
||||||
|
self.player = player
|
||||||
|
self.enemy = enemy
|
||||||
|
self.theater = theater
|
||||||
|
self.settings = settings
|
||||||
|
self.start_date = start_date
|
||||||
|
self.starting_budget = starting_budget
|
||||||
|
self.multiplier = multiplier
|
||||||
|
self.midgame = midgame
|
||||||
|
|
||||||
|
def generate(self) -> Game:
|
||||||
|
# Reset name generator
|
||||||
|
namegen.reset()
|
||||||
|
self.prepare_theater()
|
||||||
|
self.populate_red_airbases()
|
||||||
|
|
||||||
|
game = Game(player_name=self.player,
|
||||||
|
enemy_name=self.enemy,
|
||||||
|
theater=self.theater,
|
||||||
|
start_date=self.start_date,
|
||||||
|
settings=self.settings)
|
||||||
|
|
||||||
|
GroundObjectGenerator(game).generate()
|
||||||
|
game.budget = self.starting_budget
|
||||||
|
game.settings.multiplier = self.multiplier
|
||||||
|
game.settings.sams = True
|
||||||
|
game.settings.version = VERSION
|
||||||
|
return game
|
||||||
|
|
||||||
|
def prepare_theater(self) -> None:
|
||||||
|
to_remove = []
|
||||||
|
|
||||||
|
# Auto-capture half the bases if midgame.
|
||||||
|
if self.midgame:
|
||||||
|
control_points = self.theater.controlpoints
|
||||||
|
for control_point in control_points[:len(control_points) // 2]:
|
||||||
|
control_point.captured = True
|
||||||
|
|
||||||
|
# Remove carrier and lha, invert situation if needed
|
||||||
|
for cp in self.theater.controlpoints:
|
||||||
|
no_carrier = self.settings.do_not_generate_carrier
|
||||||
|
no_lha = self.settings.do_not_generate_lha
|
||||||
|
if cp.cptype is ControlPointType.AIRCRAFT_CARRIER_GROUP and \
|
||||||
|
no_carrier:
|
||||||
|
to_remove.append(cp)
|
||||||
|
elif cp.cptype is ControlPointType.LHA_GROUP and no_lha:
|
||||||
|
to_remove.append(cp)
|
||||||
|
|
||||||
|
if self.settings.inverted:
|
||||||
|
cp.captured = cp.captured_invert
|
||||||
|
|
||||||
|
# do remove
|
||||||
|
for cp in to_remove:
|
||||||
|
self.theater.controlpoints.remove(cp)
|
||||||
|
|
||||||
|
# TODO: Fix this. This captures all bases for blue.
|
||||||
|
# reapply midgame inverted if needed
|
||||||
|
if self.midgame and self.settings.inverted:
|
||||||
|
for i, cp in enumerate(reversed(self.theater.controlpoints)):
|
||||||
|
if i > len(self.theater.controlpoints):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
cp.captured = True
|
||||||
|
|
||||||
|
def populate_red_airbases(self) -> None:
|
||||||
|
for control_point in self.theater.enemy_points():
|
||||||
|
if control_point.captured:
|
||||||
|
continue
|
||||||
|
self.populate_red_airbase(control_point)
|
||||||
|
|
||||||
|
def populate_red_airbase(self, control_point: ControlPoint) -> None:
|
||||||
# Force reset cp on generation
|
# Force reset cp on generation
|
||||||
cp.base.aircraft = {}
|
control_point.base.aircraft = {}
|
||||||
cp.base.armor = {}
|
control_point.base.armor = {}
|
||||||
cp.base.aa = {}
|
control_point.base.aa = {}
|
||||||
cp.base.commision_points = {}
|
control_point.base.commision_points = {}
|
||||||
cp.base.strength = 1
|
control_point.base.strength = 1
|
||||||
|
|
||||||
for task in [PinpointStrike, CAP, CAS, AirDefence]:
|
for task in [PinpointStrike, CAP, CAS, AirDefence]:
|
||||||
assert cp.importance <= IMPORTANCE_HIGH, "invalid importance {}".format(cp.importance)
|
if IMPORTANCE_HIGH <= control_point.importance <= IMPORTANCE_LOW:
|
||||||
assert cp.importance >= IMPORTANCE_LOW, "invalid importance {}".format(cp.importance)
|
raise ValueError(
|
||||||
|
f"CP importance must be between {IMPORTANCE_LOW} and "
|
||||||
|
f"{IMPORTANCE_HIGH}, is {control_point.importance}")
|
||||||
|
|
||||||
importance_factor = (cp.importance - IMPORTANCE_LOW) / (IMPORTANCE_HIGH - IMPORTANCE_LOW)
|
importance_factor = ((control_point.importance - IMPORTANCE_LOW) /
|
||||||
variety = int(UNIT_VARIETY)
|
(IMPORTANCE_HIGH - IMPORTANCE_LOW))
|
||||||
unittypes = db.choose_units(task, importance_factor, variety, enemy_country)
|
# noinspection PyTypeChecker
|
||||||
|
unit_types = db.choose_units(task, importance_factor, UNIT_VARIETY,
|
||||||
if not sams and task == AirDefence:
|
self.enemy)
|
||||||
unittypes = [x for x in db.find_unittype(AirDefence, enemy_country) if x not in db.SAM_BAN]
|
if not unit_types:
|
||||||
|
|
||||||
count_log = math.log(cp.importance + 0.01, UNIT_COUNT_IMPORTANCE_LOG)
|
|
||||||
count = max(COUNT_BY_TASK[task] * multiplier * (1+count_log), 1)
|
|
||||||
|
|
||||||
if len(unittypes) > 0:
|
|
||||||
count_per_type = max(int(float(count) / len(unittypes)), 1)
|
|
||||||
for unit_type in unittypes:
|
|
||||||
logging.info("{} - {} {}".format(cp.name, db.unit_type_name(unit_type), count_per_type))
|
|
||||||
cp.base.commision_units({unit_type: count_per_type})
|
|
||||||
|
|
||||||
|
|
||||||
def generate_groundobjects(theater: ConflictTheater, game):
|
|
||||||
with open("resources/groundobject_templates.p", "rb") as f:
|
|
||||||
tpls = pickle.load(f)
|
|
||||||
|
|
||||||
group_id = 0
|
|
||||||
cp_to_remove = []
|
|
||||||
for cp in theater.controlpoints:
|
|
||||||
group_id = generate_cp_ground_points(cp, theater, game, group_id, tpls)
|
|
||||||
|
|
||||||
# CP
|
|
||||||
if cp.captured:
|
|
||||||
faction_name = game.player_name
|
|
||||||
else:
|
|
||||||
faction_name = game.enemy_name
|
|
||||||
faction = db.FACTIONS[faction_name]
|
|
||||||
|
|
||||||
if cp.cptype == ControlPointType.AIRCRAFT_CARRIER_GROUP:
|
|
||||||
# Create ground object group
|
|
||||||
group_id = game.next_group_id()
|
|
||||||
g = TheaterGroundObject("CARRIER")
|
|
||||||
g.group_id = group_id
|
|
||||||
g.object_id = 0
|
|
||||||
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)
|
|
||||||
group = generate_carrier_group(faction_name, game, g)
|
|
||||||
g.groups = []
|
|
||||||
if group is not None:
|
|
||||||
g.groups.append(group)
|
|
||||||
cp.ground_objects.append(g)
|
|
||||||
# Set new name :
|
|
||||||
if len(faction.carrier_names) > 0:
|
|
||||||
cp.name = random.choice(faction.carrier_names)
|
|
||||||
else:
|
|
||||||
cp_to_remove.append(cp)
|
|
||||||
elif cp.cptype == ControlPointType.LHA_GROUP:
|
|
||||||
# Create ground object group
|
|
||||||
group_id = game.next_group_id()
|
|
||||||
g = TheaterGroundObject("LHA")
|
|
||||||
g.group_id = group_id
|
|
||||||
g.object_id = 0
|
|
||||||
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)
|
|
||||||
group = generate_lha_group(faction_name, game, g)
|
|
||||||
g.groups = []
|
|
||||||
if group is not None:
|
|
||||||
g.groups.append(group)
|
|
||||||
cp.ground_objects.append(g)
|
|
||||||
# Set new name :
|
|
||||||
if len(faction.helicopter_carrier_names) > 0:
|
|
||||||
cp.name = random.choice(faction.helicopter_carrier_names)
|
|
||||||
else:
|
|
||||||
cp_to_remove.append(cp)
|
|
||||||
else:
|
|
||||||
|
|
||||||
for i in range(random.randint(3, 6)):
|
|
||||||
|
|
||||||
logging.info("GENERATE BASE DEFENSE")
|
|
||||||
point = find_location(True, cp.position, theater, 800, 3200, [], True)
|
|
||||||
logging.info(point)
|
|
||||||
|
|
||||||
if point is None:
|
|
||||||
logging.info("Couldn't find point for {} base defense".format(cp))
|
|
||||||
continue
|
|
||||||
|
|
||||||
group_id = game.next_group_id()
|
|
||||||
|
|
||||||
g = TheaterGroundObject("aa")
|
|
||||||
g.group_id = group_id
|
|
||||||
g.object_id = 0
|
|
||||||
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)
|
|
||||||
|
|
||||||
generate_airbase_defense_group(i, g, faction_name, game, cp)
|
|
||||||
cp.ground_objects.append(g)
|
|
||||||
|
|
||||||
logging.info("---------------------------")
|
|
||||||
logging.info("CP Generation : " + cp.name)
|
|
||||||
for ground_object in cp.ground_objects:
|
|
||||||
logging.info(ground_object.groups)
|
|
||||||
|
|
||||||
# Generate navy groups
|
|
||||||
if len(faction.navy_generators) > 0 and cp.allow_sea_units:
|
|
||||||
|
|
||||||
if cp.captured and game.settings.do_not_generate_player_navy:
|
|
||||||
continue
|
|
||||||
if not cp.captured and game.settings.do_not_generate_enemy_navy:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for i in range(faction.navy_group_count):
|
count_log = math.log(control_point.importance + 0.01,
|
||||||
|
UNIT_COUNT_IMPORTANCE_LOG)
|
||||||
|
count = max(
|
||||||
|
COUNT_BY_TASK[task] * self.multiplier * (1 + count_log), 1
|
||||||
|
)
|
||||||
|
|
||||||
point = find_location(False, cp.position, theater, 5000, 40000, [], False)
|
count_per_type = max(int(float(count) / len(unit_types)), 1)
|
||||||
|
for unit_type in unit_types:
|
||||||
if point is None:
|
control_point.base.commision_units({unit_type: count_per_type})
|
||||||
logging.info("Couldn't find point for {} ships".format(cp))
|
|
||||||
continue
|
|
||||||
|
|
||||||
group_id = game.next_group_id()
|
|
||||||
|
|
||||||
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_name)
|
|
||||||
g.groups = []
|
|
||||||
if group is not None:
|
|
||||||
g.groups.append(group)
|
|
||||||
cp.ground_objects.append(g)
|
|
||||||
|
|
||||||
if len(faction.missiles) > 0:
|
|
||||||
|
|
||||||
for i in range(faction.missiles_group_count):
|
|
||||||
|
|
||||||
point = find_location(True, cp.position, theater, 2500, 40000, [], False)
|
|
||||||
|
|
||||||
if point is None:
|
|
||||||
logging.info("Couldn't find point for {} missiles".format(cp))
|
|
||||||
continue
|
|
||||||
|
|
||||||
group_id = game.next_group_id()
|
|
||||||
|
|
||||||
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 = False
|
|
||||||
g.obj_name = namegen.random_objective_name()
|
|
||||||
g.heading = 0
|
|
||||||
g.position = Point(point.x, point.y)
|
|
||||||
|
|
||||||
group = generate_missile_group(game, g, faction_name)
|
|
||||||
g.groups = []
|
|
||||||
if group is not None:
|
|
||||||
g.groups.append(group)
|
|
||||||
cp.ground_objects.append(g)
|
|
||||||
|
|
||||||
for cp in cp_to_remove:
|
|
||||||
theater.controlpoints.remove(cp)
|
|
||||||
|
|
||||||
|
|
||||||
|
class ControlPointGroundObjectGenerator:
|
||||||
|
def __init__(self, game: Game, control_point: ControlPoint,
|
||||||
|
templates: GroundObjectTemplates) -> None:
|
||||||
|
self.game = game
|
||||||
|
self.control_point = control_point
|
||||||
|
self.templates = templates
|
||||||
|
|
||||||
def generate_airbase_defense_group(airbase_defense_group_id, ground_obj:TheaterGroundObject, faction, game, cp):
|
@property
|
||||||
|
def faction_name(self) -> str:
|
||||||
|
if self.control_point.captured:
|
||||||
|
return self.game.player_name
|
||||||
|
else:
|
||||||
|
return self.game.enemy_name
|
||||||
|
|
||||||
logging.info("GENERATE AIR DEFENSE GROUP")
|
@property
|
||||||
logging.info(faction)
|
def faction(self) -> Faction:
|
||||||
logging.info(airbase_defense_group_id)
|
return db.FACTIONS[self.faction_name]
|
||||||
|
|
||||||
|
def generate(self) -> bool:
|
||||||
|
self.control_point.ground_objects = []
|
||||||
|
self.generate_ground_points()
|
||||||
|
if self.faction.navy_generators:
|
||||||
|
# Even airbases can generate navies if they are close enough to the
|
||||||
|
# water. This is not controlled by the control point definition, but
|
||||||
|
# rather by whether or not the generator can find a valid position
|
||||||
|
# for the ship.
|
||||||
|
self.generate_navy()
|
||||||
|
|
||||||
|
if self.faction.missiles:
|
||||||
|
# TODO: Presumably only for airbases?
|
||||||
|
self.generate_missile_sites()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def generate_ground_points(self) -> None:
|
||||||
|
"""Generate ground objects and AA sites for the control point."""
|
||||||
|
|
||||||
|
if self.control_point.is_global:
|
||||||
|
return
|
||||||
|
|
||||||
|
# TODO: Should probably perform this check later.
|
||||||
|
# Just because we don't have factories for the faction doesn't mean we
|
||||||
|
# shouldn't generate AA.
|
||||||
|
available_categories = self.faction.building_set
|
||||||
|
if not available_categories:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Always generate at least one AA point.
|
||||||
|
self.generate_aa_site()
|
||||||
|
|
||||||
|
# And between 2 and 7 other objectives.
|
||||||
|
amount = random.randrange(2, 7)
|
||||||
|
for i in range(amount):
|
||||||
|
# 1 in 4 additional objectives are AA.
|
||||||
|
if random.randint(0, 3) == 0:
|
||||||
|
self.generate_aa_site()
|
||||||
|
else:
|
||||||
|
category = random.choice(available_categories)
|
||||||
|
self.generate_ground_point(category)
|
||||||
|
|
||||||
|
def generate_ground_point(self, category: str) -> None:
|
||||||
|
obj_name = namegen.random_objective_name()
|
||||||
|
template = random.choice(list(self.templates[category].values()))
|
||||||
|
point = find_location(category != "oil",
|
||||||
|
self.control_point.position,
|
||||||
|
self.game.theater, 10000, 40000,
|
||||||
|
self.control_point.ground_objects)
|
||||||
|
|
||||||
|
if point is None:
|
||||||
|
logging.error(
|
||||||
|
f"Could not find point for {obj_name} at {self.control_point}")
|
||||||
|
return
|
||||||
|
|
||||||
|
object_id = 0
|
||||||
|
group_id = self.game.next_group_id()
|
||||||
|
|
||||||
|
# TODO: Create only one TGO per objective, each with multiple units.
|
||||||
|
for unit in template:
|
||||||
|
object_id += 1
|
||||||
|
|
||||||
|
template_point = Point(unit["offset"].x, unit["offset"].y)
|
||||||
|
g = BuildingGroundObject(
|
||||||
|
obj_name, category, group_id, object_id, point + template_point,
|
||||||
|
unit["heading"], self.control_point, unit["type"])
|
||||||
|
|
||||||
|
self.control_point.ground_objects.append(g)
|
||||||
|
|
||||||
|
def generate_aa_site(self) -> None:
|
||||||
|
obj_name = namegen.random_objective_name()
|
||||||
|
position = find_location(True, self.control_point.position,
|
||||||
|
self.game.theater, 10000, 40000,
|
||||||
|
self.control_point.ground_objects)
|
||||||
|
|
||||||
|
if position is None:
|
||||||
|
logging.error(
|
||||||
|
f"Could not find point for {obj_name} at {self.control_point}")
|
||||||
|
return
|
||||||
|
|
||||||
|
group_id = self.game.next_group_id()
|
||||||
|
|
||||||
|
g = SamGroundObject(namegen.random_objective_name(), group_id,
|
||||||
|
position, self.control_point, for_airbase=False)
|
||||||
|
group = generate_anti_air_group(self.game, g, self.faction_name)
|
||||||
|
if group is not None:
|
||||||
|
g.groups = [group]
|
||||||
|
self.control_point.ground_objects.append(g)
|
||||||
|
|
||||||
|
def generate_navy(self) -> None:
|
||||||
|
skip_player_navy = self.game.settings.do_not_generate_player_navy
|
||||||
|
if self.control_point.captured and skip_player_navy:
|
||||||
|
return
|
||||||
|
|
||||||
|
skip_enemy_navy = self.game.settings.do_not_generate_enemy_navy
|
||||||
|
if not self.control_point.captured and skip_enemy_navy:
|
||||||
|
return
|
||||||
|
|
||||||
|
for _ in range(self.faction.navy_group_count):
|
||||||
|
self.generate_ship()
|
||||||
|
|
||||||
|
def generate_ship(self) -> None:
|
||||||
|
point = find_location(False, self.control_point.position,
|
||||||
|
self.game.theater, 5000, 40000, [], False)
|
||||||
|
if point is None:
|
||||||
|
logging.error(
|
||||||
|
f"Could not find point for {self.control_point}'s navy")
|
||||||
|
return
|
||||||
|
|
||||||
|
group_id = self.game.next_group_id()
|
||||||
|
|
||||||
|
g = ShipGroundObject(namegen.random_objective_name(), group_id, point,
|
||||||
|
self.control_point)
|
||||||
|
|
||||||
|
group = generate_ship_group(self.game, g, self.faction_name)
|
||||||
|
g.groups = []
|
||||||
|
if group is not None:
|
||||||
|
g.groups.append(group)
|
||||||
|
self.control_point.ground_objects.append(g)
|
||||||
|
|
||||||
|
def generate_missile_sites(self) -> None:
|
||||||
|
for i in range(self.faction.missiles_group_count):
|
||||||
|
self.generate_missile_site()
|
||||||
|
|
||||||
|
def generate_missile_site(self) -> None:
|
||||||
|
point = find_location(True, self.control_point.position,
|
||||||
|
self.game.theater, 2500, 40000, [], False)
|
||||||
|
if point is None:
|
||||||
|
logging.info(
|
||||||
|
f"Could not find point for {self.control_point} missile site")
|
||||||
|
return
|
||||||
|
|
||||||
|
group_id = self.game.next_group_id()
|
||||||
|
|
||||||
|
g = MissileSiteGroundObject(namegen.random_objective_name(), group_id,
|
||||||
|
point, self.control_point)
|
||||||
|
group = generate_missile_group(self.game, g, self.faction_name)
|
||||||
|
g.groups = []
|
||||||
|
if group is not None:
|
||||||
|
g.groups.append(group)
|
||||||
|
self.control_point.ground_objects.append(g)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
class CarrierGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||||
|
def generate(self) -> bool:
|
||||||
|
if not super().generate():
|
||||||
|
return False
|
||||||
|
|
||||||
|
carrier_names = self.faction.carrier_names
|
||||||
|
if not carrier_names:
|
||||||
|
logging.info(
|
||||||
|
f"Skipping generation of {self.control_point.name} because "
|
||||||
|
f"{self.faction_name} has no carriers")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Create ground object group
|
||||||
|
group_id = self.game.next_group_id()
|
||||||
|
g = CarrierGroundObject(namegen.random_objective_name(), group_id,
|
||||||
|
self.control_point)
|
||||||
|
group = generate_carrier_group(self.faction_name, self.game, g)
|
||||||
|
g.groups = []
|
||||||
|
if group is not None:
|
||||||
|
g.groups.append(group)
|
||||||
|
self.control_point.ground_objects.append(g)
|
||||||
|
self.control_point.name = random.choice(carrier_names)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class LhaGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||||
|
def generate(self) -> bool:
|
||||||
|
if not super().generate():
|
||||||
|
return False
|
||||||
|
|
||||||
|
lha_names = self.faction.helicopter_carrier_names
|
||||||
|
if not lha_names:
|
||||||
|
logging.info(
|
||||||
|
f"Skipping generation of {self.control_point.name} because "
|
||||||
|
f"{self.faction_name} has no LHAs")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Create ground object group
|
||||||
|
group_id = self.game.next_group_id()
|
||||||
|
g = LhaGroundObject(namegen.random_objective_name(), group_id,
|
||||||
|
self.control_point)
|
||||||
|
group = generate_lha_group(self.faction_name, self.game, g)
|
||||||
|
g.groups = []
|
||||||
|
if group is not None:
|
||||||
|
g.groups.append(group)
|
||||||
|
self.control_point.ground_objects.append(g)
|
||||||
|
self.control_point.name = random.choice(lha_names)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||||
|
def generate(self) -> bool:
|
||||||
|
if not super().generate():
|
||||||
|
return False
|
||||||
|
|
||||||
|
for i in range(random.randint(3, 6)):
|
||||||
|
self.generate_sam(i)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def generate_sam(self, index: int) -> None:
|
||||||
|
position = find_location(True, self.control_point.position,
|
||||||
|
self.game.theater, 800, 3200, [], True)
|
||||||
|
if position is None:
|
||||||
|
logging.error("Could not find position for "
|
||||||
|
f"{self.control_point} base defense")
|
||||||
|
return
|
||||||
|
|
||||||
|
group_id = self.game.next_group_id()
|
||||||
|
|
||||||
|
g = SamGroundObject(namegen.random_objective_name(), group_id,
|
||||||
|
position, self.control_point, for_airbase=True)
|
||||||
|
|
||||||
|
generate_airbase_defense_group(index, g, self.faction_name, self.game)
|
||||||
|
self.control_point.ground_objects.append(g)
|
||||||
|
|
||||||
|
|
||||||
|
class GroundObjectGenerator:
|
||||||
|
def __init__(self, game: Game) -> None:
|
||||||
|
self.game = game
|
||||||
|
with open("resources/groundobject_templates.p", "rb") as f:
|
||||||
|
self.templates: GroundObjectTemplates = pickle.load(f)
|
||||||
|
|
||||||
|
def generate(self) -> None:
|
||||||
|
# Copied so we can remove items from the original list without breaking
|
||||||
|
# the iterator.
|
||||||
|
control_points = list(self.game.theater.controlpoints)
|
||||||
|
for control_point in control_points:
|
||||||
|
if not self.generate_for_control_point(control_point):
|
||||||
|
self.game.theater.controlpoints.remove(control_point)
|
||||||
|
|
||||||
|
def generate_for_control_point(self, control_point: ControlPoint) -> bool:
|
||||||
|
generator: ControlPointGroundObjectGenerator
|
||||||
|
if control_point.cptype == ControlPointType.AIRCRAFT_CARRIER_GROUP:
|
||||||
|
generator = CarrierGroundObjectGenerator(self.game, control_point,
|
||||||
|
self.templates)
|
||||||
|
elif control_point.cptype == ControlPointType.LHA_GROUP:
|
||||||
|
generator = LhaGroundObjectGenerator(self.game, control_point,
|
||||||
|
self.templates)
|
||||||
|
else:
|
||||||
|
generator = AirbaseGroundObjectGenerator(self.game, control_point,
|
||||||
|
self.templates)
|
||||||
|
return generator.generate()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_airbase_defense_group(airbase_defense_group_id: int,
|
||||||
|
ground_obj: TheaterGroundObject,
|
||||||
|
faction: str, game: Game) -> None:
|
||||||
if airbase_defense_group_id == 0:
|
if airbase_defense_group_id == 0:
|
||||||
group = generate_armor_group(faction, game, ground_obj)
|
group = generate_armor_group(faction, game, ground_obj)
|
||||||
elif airbase_defense_group_id == 1 and random.randint(0, 1) == 0:
|
elif airbase_defense_group_id == 1 and random.randint(0, 1) == 0:
|
||||||
group = generate_anti_air_group(game, cp, ground_obj, faction)
|
group = generate_anti_air_group(game, ground_obj, faction)
|
||||||
elif random.randint(0, 2) == 1:
|
elif random.randint(0, 2) == 1:
|
||||||
group = generate_shorad_group(game, cp, ground_obj, faction)
|
group = generate_shorad_group(game, ground_obj, faction)
|
||||||
else:
|
else:
|
||||||
group = generate_armor_group(faction, game, ground_obj)
|
group = generate_armor_group(faction, game, ground_obj)
|
||||||
|
|
||||||
@ -260,22 +442,31 @@ def generate_airbase_defense_group(airbase_defense_group_id, ground_obj:TheaterG
|
|||||||
ground_obj.groups.append(group)
|
ground_obj.groups.append(group)
|
||||||
|
|
||||||
|
|
||||||
def find_location(on_ground, near, theater, min, max, others, is_base_defense=False) -> typing.Optional[Point]:
|
# TODO: https://stackoverflow.com/a/19482012/632035
|
||||||
|
# A lot of the time spent on mission generation is spent in this function since
|
||||||
|
# just randomly guess up to 1800 times and often fail. This is particularly
|
||||||
|
# problematic while trying to find placement for navies in Nevada.
|
||||||
|
def find_location(on_ground: bool, near: Point, theater: ConflictTheater,
|
||||||
|
min_range: int, max_range: int,
|
||||||
|
others: List[TheaterGroundObject],
|
||||||
|
is_base_defense: bool = False) -> Optional[Point]:
|
||||||
"""
|
"""
|
||||||
Find a valid ground object location
|
Find a valid ground object location
|
||||||
:param on_ground: Whether it should be on ground or on sea (True = on ground)
|
:param on_ground: Whether it should be on ground or on sea (True = on
|
||||||
|
ground)
|
||||||
:param near: Point
|
:param near: Point
|
||||||
:param theater: Theater object
|
:param theater: Theater object
|
||||||
:param min: Minimal range from point
|
:param min_range: Minimal range from point
|
||||||
:param max: Max range from point
|
:param max_range: Max range from point
|
||||||
:param others: Other already existing ground objects
|
:param others: Other already existing ground objects
|
||||||
|
:param is_base_defense: True if the location is for base defense.
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
point = None
|
point = None
|
||||||
for _ in range(300):
|
for _ in range(300):
|
||||||
|
|
||||||
# Check if on land or sea
|
# Check if on land or sea
|
||||||
p = near.random_point_within(max, min)
|
p = near.random_point_within(max_range, min_range)
|
||||||
if on_ground and theater.is_on_land(p):
|
if on_ground and theater.is_on_land(p):
|
||||||
point = p
|
point = p
|
||||||
elif not on_ground and theater.is_in_sea(p):
|
elif not on_ground and theater.is_in_sea(p):
|
||||||
@ -297,15 +488,16 @@ def find_location(on_ground, near, theater, min, max, others, is_base_defense=Fa
|
|||||||
break
|
break
|
||||||
|
|
||||||
if point:
|
if point:
|
||||||
for other in theater.controlpoints:
|
for control_point in theater.controlpoints:
|
||||||
if is_base_defense: break
|
if is_base_defense:
|
||||||
if other.position != near:
|
break
|
||||||
|
if control_point.position != near:
|
||||||
if point is None:
|
if point is None:
|
||||||
break
|
break
|
||||||
if other.position.distance_to_point(point) < 30000:
|
if control_point.position.distance_to_point(point) < 30000:
|
||||||
point = None
|
point = None
|
||||||
break
|
break
|
||||||
for ground_obj in other.ground_objects:
|
for ground_obj in control_point.ground_objects:
|
||||||
if ground_obj.position.distance_to_point(point) < 10000:
|
if ground_obj.position.distance_to_point(point) < 10000:
|
||||||
point = None
|
point = None
|
||||||
break
|
break
|
||||||
@ -313,114 +505,3 @@ def find_location(on_ground, near, theater, min, max, others, is_base_defense=Fa
|
|||||||
if point:
|
if point:
|
||||||
return point
|
return point
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def generate_cp_ground_points(cp: ControlPoint, theater, game, group_id, templates):
|
|
||||||
"""
|
|
||||||
Generate inital ground objects and AA site for given control point
|
|
||||||
:param cp: Control point to initialize
|
|
||||||
:param theater: Theater
|
|
||||||
:param game: Game object
|
|
||||||
:param group_id: Group id
|
|
||||||
:param templates: Ground object templates
|
|
||||||
:return: True if something was generated
|
|
||||||
"""
|
|
||||||
# Reset cp ground objects
|
|
||||||
cp.ground_objects = []
|
|
||||||
|
|
||||||
if cp.is_global:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if cp.captured:
|
|
||||||
faction = game.player_name
|
|
||||||
else:
|
|
||||||
faction = game.enemy_name
|
|
||||||
faction_data = db.FACTIONS[faction]
|
|
||||||
|
|
||||||
available_categories = faction_data.building_set
|
|
||||||
|
|
||||||
if len(available_categories) == 0:
|
|
||||||
return False
|
|
||||||
|
|
||||||
amount = random.randrange(3, 8)
|
|
||||||
for i in range(0, amount):
|
|
||||||
|
|
||||||
obj_name = namegen.random_objective_name()
|
|
||||||
|
|
||||||
if i >= amount - 1:
|
|
||||||
tpl_category = "aa"
|
|
||||||
else:
|
|
||||||
if random.randint(0, 3) == 0:
|
|
||||||
tpl_category = "aa"
|
|
||||||
else:
|
|
||||||
tpl_category = random.choice(available_categories)
|
|
||||||
|
|
||||||
tpl = random.choice(list(templates[tpl_category].values()))
|
|
||||||
point = find_location(tpl_category != "oil", cp.position, theater, 10000, 40000, cp.ground_objects)
|
|
||||||
|
|
||||||
if point is None:
|
|
||||||
logging.info("Couldn't find point for {}".format(cp))
|
|
||||||
continue
|
|
||||||
|
|
||||||
object_id = 0
|
|
||||||
group_id = game.next_group_id()
|
|
||||||
|
|
||||||
logging.info("generated {} for {}".format(tpl_category, cp))
|
|
||||||
|
|
||||||
for object in tpl:
|
|
||||||
object_id += 1
|
|
||||||
|
|
||||||
g = TheaterGroundObject(tpl_category)
|
|
||||||
g.group_id = group_id
|
|
||||||
g.object_id = object_id
|
|
||||||
g.cp_id = cp.id
|
|
||||||
g.airbase_group = False
|
|
||||||
g.obj_name = obj_name
|
|
||||||
|
|
||||||
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":
|
|
||||||
g.groups = []
|
|
||||||
group = generate_anti_air_group(game, cp, g, faction)
|
|
||||||
if group is not None:
|
|
||||||
g.groups.append(group)
|
|
||||||
|
|
||||||
cp.ground_objects.append(g)
|
|
||||||
return group_id
|
|
||||||
|
|
||||||
|
|
||||||
def prepare_theater(theater: ConflictTheater, settings:Settings, midgame):
|
|
||||||
|
|
||||||
to_remove = []
|
|
||||||
|
|
||||||
# autocapture half the base if midgame
|
|
||||||
if midgame:
|
|
||||||
for i in range(0, int(len(theater.controlpoints) / 2)):
|
|
||||||
theater.controlpoints[i].captured = True
|
|
||||||
|
|
||||||
# Remove carrier and lha, invert situation if needed
|
|
||||||
for cp in theater.controlpoints:
|
|
||||||
if cp.cptype is ControlPointType.AIRCRAFT_CARRIER_GROUP and settings.do_not_generate_carrier:
|
|
||||||
to_remove.append(cp)
|
|
||||||
elif cp.cptype is ControlPointType.LHA_GROUP and settings.do_not_generate_lha:
|
|
||||||
to_remove.append(cp)
|
|
||||||
|
|
||||||
if settings.inverted:
|
|
||||||
cp.captured = cp.captured_invert
|
|
||||||
|
|
||||||
# do remove
|
|
||||||
for cp in to_remove:
|
|
||||||
theater.controlpoints.remove(cp)
|
|
||||||
|
|
||||||
# reapply midgame inverted if needed
|
|
||||||
if midgame and settings.inverted:
|
|
||||||
for i, cp in enumerate(reversed(theater.controlpoints)):
|
|
||||||
if i > len(theater.controlpoints):
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
cp.captured = True
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import uuid
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import List, TYPE_CHECKING
|
from typing import List, TYPE_CHECKING
|
||||||
|
|
||||||
from dcs.mapping import Point
|
from dcs.mapping import Point
|
||||||
@ -68,21 +69,21 @@ CATEGORY_MAP = {
|
|||||||
|
|
||||||
|
|
||||||
class TheaterGroundObject(MissionTarget):
|
class TheaterGroundObject(MissionTarget):
|
||||||
cp_id = 0
|
|
||||||
group_id = 0
|
|
||||||
object_id = 0
|
|
||||||
dcs_identifier = None # type: str
|
|
||||||
is_dead = False
|
|
||||||
airbase_group = False
|
|
||||||
heading = 0
|
|
||||||
position = None # type: Point
|
|
||||||
groups: List[Group] = []
|
|
||||||
obj_name = ""
|
|
||||||
sea_object = False
|
|
||||||
uuid = uuid.uuid1()
|
|
||||||
|
|
||||||
def __init__(self, category: str):
|
def __init__(self, name: str, category: str, group_id: int, object_id: int,
|
||||||
|
position: Point, heading: int, cp_id: int, dcs_identifier: str,
|
||||||
|
airbase_group: bool, sea_object: bool) -> None:
|
||||||
|
super().__init__(name, position)
|
||||||
self.category = category
|
self.category = category
|
||||||
|
self.group_id = group_id
|
||||||
|
self.object_id = object_id
|
||||||
|
self.heading = heading
|
||||||
|
self.cp_id = cp_id
|
||||||
|
self.dcs_identifier = dcs_identifier
|
||||||
|
self.airbase_group = airbase_group
|
||||||
|
self.sea_object = sea_object
|
||||||
|
self.is_dead = False
|
||||||
|
self.groups: List[Group] = []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def string_identifier(self):
|
def string_identifier(self):
|
||||||
@ -96,20 +97,128 @@ class TheaterGroundObject(MissionTarget):
|
|||||||
def name_abbrev(self) -> str:
|
def name_abbrev(self) -> str:
|
||||||
return ABBREV_NAME[self.category]
|
return ABBREV_NAME[self.category]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return NAME_BY_CATEGORY[self.category]
|
return NAME_BY_CATEGORY[self.category]
|
||||||
|
|
||||||
def matches_string_identifier(self, id):
|
def matches_string_identifier(self, identifier):
|
||||||
return self.string_identifier == id
|
return self.string_identifier == identifier
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def obj_name(self) -> str:
|
||||||
return self.obj_name
|
return self.name
|
||||||
|
|
||||||
def parent_control_point(
|
def parent_control_point(self, theater: ConflictTheater) -> ControlPoint:
|
||||||
self, theater: "ConflictTheater") -> "ControlPoint":
|
|
||||||
"""Searches the theater for the parent control point."""
|
"""Searches the theater for the parent control point."""
|
||||||
for cp in theater.controlpoints:
|
for cp in theater.controlpoints:
|
||||||
if cp.id == self.cp_id:
|
if cp.id == self.cp_id:
|
||||||
return cp
|
return cp
|
||||||
raise RuntimeError("Could not find matching control point in theater")
|
raise RuntimeError("Could not find matching control point in theater")
|
||||||
|
|
||||||
|
|
||||||
|
class BuildingGroundObject(TheaterGroundObject):
|
||||||
|
def __init__(self, name: str, category: str, group_id: int, object_id: int,
|
||||||
|
position: Point, heading: int, control_point: ControlPoint,
|
||||||
|
dcs_identifier: str) -> None:
|
||||||
|
super().__init__(
|
||||||
|
name=name,
|
||||||
|
category=category,
|
||||||
|
group_id=group_id,
|
||||||
|
object_id=object_id,
|
||||||
|
position=position,
|
||||||
|
heading=heading,
|
||||||
|
cp_id=control_point.id,
|
||||||
|
dcs_identifier=dcs_identifier,
|
||||||
|
airbase_group=False,
|
||||||
|
sea_object=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GenericCarrierGroundObject(TheaterGroundObject):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Why is this both a CP and a TGO?
|
||||||
|
class CarrierGroundObject(GenericCarrierGroundObject):
|
||||||
|
def __init__(self, name: str, group_id: int,
|
||||||
|
control_point: ControlPoint) -> None:
|
||||||
|
super().__init__(
|
||||||
|
name=name,
|
||||||
|
category="CARRIER",
|
||||||
|
group_id=group_id,
|
||||||
|
object_id=0,
|
||||||
|
position=control_point.position,
|
||||||
|
heading=0,
|
||||||
|
cp_id=control_point.id,
|
||||||
|
dcs_identifier="CARRIER",
|
||||||
|
airbase_group=True,
|
||||||
|
sea_object=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Why is this both a CP and a TGO?
|
||||||
|
class LhaGroundObject(GenericCarrierGroundObject):
|
||||||
|
def __init__(self, name: str, group_id: int,
|
||||||
|
control_point: ControlPoint) -> None:
|
||||||
|
super().__init__(
|
||||||
|
name=name,
|
||||||
|
category="LHA",
|
||||||
|
group_id=group_id,
|
||||||
|
object_id=0,
|
||||||
|
position=control_point.position,
|
||||||
|
heading=0,
|
||||||
|
cp_id=control_point.id,
|
||||||
|
dcs_identifier="LHA",
|
||||||
|
airbase_group=True,
|
||||||
|
sea_object=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MissileSiteGroundObject(TheaterGroundObject):
|
||||||
|
def __init__(self, name: str, group_id: int, position: Point,
|
||||||
|
control_point: ControlPoint) -> None:
|
||||||
|
super().__init__(
|
||||||
|
name=name,
|
||||||
|
category="aa",
|
||||||
|
group_id=group_id,
|
||||||
|
object_id=0,
|
||||||
|
position=position,
|
||||||
|
heading=0,
|
||||||
|
cp_id=control_point.id,
|
||||||
|
dcs_identifier="AA",
|
||||||
|
airbase_group=False,
|
||||||
|
sea_object=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SamGroundObject(TheaterGroundObject):
|
||||||
|
def __init__(self, name: str, group_id: int, position: Point,
|
||||||
|
control_point: ControlPoint, for_airbase: bool) -> None:
|
||||||
|
super().__init__(
|
||||||
|
name=name,
|
||||||
|
category="aa",
|
||||||
|
group_id=group_id,
|
||||||
|
object_id=0,
|
||||||
|
position=position,
|
||||||
|
heading=0,
|
||||||
|
cp_id=control_point.id,
|
||||||
|
dcs_identifier="AA",
|
||||||
|
airbase_group=for_airbase,
|
||||||
|
sea_object=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ShipGroundObject(TheaterGroundObject):
|
||||||
|
def __init__(self, name: str, group_id: int, position: Point,
|
||||||
|
control_point: ControlPoint) -> None:
|
||||||
|
super().__init__(
|
||||||
|
name=name,
|
||||||
|
category="aa",
|
||||||
|
group_id=group_id,
|
||||||
|
object_id=0,
|
||||||
|
position=position,
|
||||||
|
heading=0,
|
||||||
|
cp_id=control_point.id,
|
||||||
|
dcs_identifier="AA",
|
||||||
|
airbase_group=False,
|
||||||
|
sea_object=True
|
||||||
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user