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 . import db
|
||||
from .version import VERSION
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import inspect
|
||||
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_GERMANY_BUILDINGS = ['fuel', 'factory', 'ww2bunker', 'ww2bunker', 'ww2bunker', 'allycamp', 'allycamp', 'aa']
|
||||
WW2_ALLIES_BUILDINGS = ['fuel', 'factory', 'allycamp', 'allycamp', 'allycamp', 'allycamp', 'allycamp', 'aa']
|
||||
WW2_FREE = ['fuel', 'factory', 'ware']
|
||||
WW2_GERMANY_BUILDINGS = ['fuel', 'factory', 'ww2bunker', 'ww2bunker', 'ww2bunker', 'allycamp', 'allycamp']
|
||||
WW2_ALLIES_BUILDINGS = ['fuel', 'factory', 'allycamp', 'allycamp', 'allycamp', 'allycamp', 'allycamp']
|
||||
|
||||
FORTIFICATION_BUILDINGS = ['Siegfried Line', 'Concertina wire', 'Concertina Wire', 'Czech hedgehogs 1', 'Czech hedgehogs 2',
|
||||
'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 gen.ground_forces.combat_stance import CombatStance
|
||||
from theater import ControlPoint
|
||||
from theater.start_generator import generate_airbase_defense_group
|
||||
|
||||
if TYPE_CHECKING:
|
||||
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 random
|
||||
from typing import Dict, Iterator
|
||||
from typing import Dict, Iterator, Optional, TYPE_CHECKING
|
||||
|
||||
from dcs import Mission
|
||||
from dcs.country import Country
|
||||
from dcs.statics import fortification_map, warehouse_map
|
||||
from dcs.task import (
|
||||
ActivateBeaconCommand,
|
||||
@ -10,22 +20,339 @@ from dcs.task import (
|
||||
EPLRS,
|
||||
OptAlarmState,
|
||||
)
|
||||
from dcs.unit import Ship, Vehicle
|
||||
from dcs.unitgroup import StaticGroup
|
||||
from dcs.unit import Ship, Vehicle, Unit
|
||||
from dcs.unitgroup import Group, ShipGroup, StaticGroup
|
||||
from dcs.unittype import StaticType, UnitType
|
||||
|
||||
from game import db
|
||||
from game.data.building_data import FORTIFICATION_UNITS, FORTIFICATION_UNITS_ID
|
||||
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 .radios import RadioRegistry
|
||||
from .radios import RadioFrequency, RadioRegistry
|
||||
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
|
||||
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:
|
||||
"""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
|
||||
|
||||
def __init__(self, mission: Mission, conflict: Conflict, game,
|
||||
@ -61,188 +388,34 @@ class GroundObjectsGenerator:
|
||||
)
|
||||
|
||||
def generate(self):
|
||||
|
||||
for cp in self.game.theater.controlpoints:
|
||||
|
||||
if cp.captured:
|
||||
country = self.game.player_country
|
||||
country_name = self.game.player_country
|
||||
else:
|
||||
country = self.game.enemy_country
|
||||
side = self.m.country(country)
|
||||
country_name = self.game.enemy_country
|
||||
country = self.m.country(country_name)
|
||||
|
||||
for ground_object in cp.ground_objects:
|
||||
if ground_object.dcs_identifier == "AA":
|
||||
|
||||
if self.game.position_culled(ground_object.position):
|
||||
continue
|
||||
|
||||
for g in ground_object.groups:
|
||||
if len(g.units) > 0:
|
||||
utype = unit_type_from_name(g.units[0].type)
|
||||
|
||||
if not ground_object.sea_object:
|
||||
vg = self.m.vehicle_group(side, g.name, utype, position=g.position, heading=g.units[0].heading)
|
||||
vg.units[0].name = self.m.string(g.units[0].name)
|
||||
vg.units[0].player_can_drive = True
|
||||
for i, u in enumerate(g.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)
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
if isinstance(ground_object, BuildingGroundObject):
|
||||
generator = BuildingSiteGenerator(ground_object, country,
|
||||
self.game, self.m)
|
||||
elif isinstance(ground_object, CarrierGroundObject):
|
||||
generator = CarrierGenerator(ground_object, cp, country,
|
||||
self.game, self.m,
|
||||
self.radio_registry,
|
||||
self.tacan_registry,
|
||||
self.icls_alloc, self.runways)
|
||||
elif isinstance(ground_object, LhaGroundObject):
|
||||
generator = CarrierGenerator(ground_object, cp, country,
|
||||
self.game, self.m,
|
||||
self.radio_registry,
|
||||
self.tacan_registry,
|
||||
self.icls_alloc, self.runways)
|
||||
elif isinstance(ground_object, ShipGroundObject):
|
||||
generator = ShipObjectGenerator(ground_object, country,
|
||||
self.game, self.m)
|
||||
else:
|
||||
|
||||
if self.game.position_culled(ground_object.position):
|
||||
continue
|
||||
|
||||
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))
|
||||
generator = GenericGroundObjectGenerator(ground_object,
|
||||
country, self.game,
|
||||
self.m)
|
||||
generator.generate()
|
||||
|
||||
@ -15,7 +15,12 @@ if TYPE_CHECKING:
|
||||
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
|
||||
self.game = game
|
||||
@ -34,7 +39,7 @@ class GroupGenerator():
|
||||
def generate(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_generated_group(self):
|
||||
def get_generated_group(self) -> unitgroup.VehicleGroup:
|
||||
return self.vg
|
||||
|
||||
def add_unit(self, unit_type: VehicleType, name: str, pos_x: float, pos_y: float, heading: int):
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import random
|
||||
from typing import List, Type
|
||||
from typing import List, Optional, Type
|
||||
|
||||
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_flak import FlakGenerator
|
||||
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_ural import ZU23UralGenerator
|
||||
from gen.sam.sam_zu23_ural_insurgent import ZU23UralInsurgentGenerator
|
||||
from theater import TheaterGroundObject
|
||||
|
||||
SAM_MAP = {
|
||||
"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()]
|
||||
|
||||
|
||||
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
|
||||
:param parentCp: The parent control point
|
||||
:param ground_object: The ground object which will own the sam group
|
||||
:param country: Owner country
|
||||
:return: Nothing, but put the group reference inside the ground object
|
||||
:param game: The Game.
|
||||
:param ground_object: The ground object which will own the sam group.
|
||||
:param faction: Owner faction.
|
||||
:return: The generated group, or None if one could not be generated.
|
||||
"""
|
||||
possible_sams_generators = get_faction_possible_sams_generator(faction)
|
||||
if len(possible_sams_generators) > 0:
|
||||
@ -128,7 +131,8 @@ def generate_anti_air_group(game, parent_cp, ground_object, faction: str):
|
||||
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]
|
||||
|
||||
if len(faction.shorads) > 0:
|
||||
@ -137,4 +141,4 @@ def generate_shorad_group(game, parent_cp, ground_object, faction_name: str):
|
||||
generator.generate()
|
||||
return generator.get_generated_group()
|
||||
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.QtWidgets import QApplication, QSplashScreen
|
||||
|
||||
from game import db, persistency
|
||||
from game import db, persistency, VERSION
|
||||
from qt_ui import (
|
||||
liberation_install,
|
||||
liberation_theme,
|
||||
@ -20,7 +20,7 @@ from qt_ui.windows.preferences.QLiberationFirstStartWindow import \
|
||||
QLiberationFirstStartWindow
|
||||
|
||||
# Logging setup
|
||||
logging_config.init_logging(uiconstants.VERSION_STRING)
|
||||
logging_config.init_logging(VERSION)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Load eagerly to catch errors early.
|
||||
|
||||
@ -7,10 +7,6 @@ from PySide2.QtGui import QColor, QFont, QPixmap
|
||||
from theater.theatergroundobject import CATEGORY_MAP
|
||||
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] = {
|
||||
"Manual": "https://github.com/khopa/dcs_liberation/wiki",
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import logging
|
||||
import sys
|
||||
import webbrowser
|
||||
from typing import Optional, Union
|
||||
from typing import Optional
|
||||
|
||||
from PySide2.QtCore import Qt
|
||||
from PySide2.QtGui import QCloseEvent, QIcon
|
||||
@ -10,14 +9,14 @@ from PySide2.QtWidgets import (
|
||||
QActionGroup, QDesktopWidget,
|
||||
QFileDialog,
|
||||
QMainWindow,
|
||||
QMenu, QMessageBox,
|
||||
QMessageBox,
|
||||
QSplitter,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
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.displayoptions import DisplayGroup, DisplayOptions, DisplayRule
|
||||
from qt_ui.models import GameModel
|
||||
@ -47,7 +46,7 @@ class QLiberationWindow(QMainWindow):
|
||||
self.setGame(persistency.restore_game())
|
||||
|
||||
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.statusBar().showMessage('Ready')
|
||||
|
||||
@ -228,7 +227,7 @@ class QLiberationWindow(QMainWindow):
|
||||
self.liberation_map.setGame(game)
|
||||
|
||||
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" + \
|
||||
"<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>." \
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
|
||||
@ -10,15 +9,14 @@ from PySide2.QtWidgets import QVBoxLayout
|
||||
from dcs.task import CAP, CAS
|
||||
|
||||
import qt_ui.uiconstants as CONST
|
||||
from game import Game, db
|
||||
from game import db
|
||||
from game.settings import Settings
|
||||
from gen import namegen
|
||||
from qt_ui.windows.newgame.QCampaignList import (
|
||||
Campaign,
|
||||
QCampaignList,
|
||||
load_campaigns,
|
||||
)
|
||||
from theater import ConflictTheater, start_generator
|
||||
from theater.start_generator import GameGenerator
|
||||
|
||||
|
||||
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_enemy_navy = no_enemy_navy
|
||||
|
||||
self.generatedGame = self.start_new_game(player_name, enemy_name, conflictTheater, midGame, multiplier,
|
||||
timePeriod, settings, starting_money)
|
||||
generator = GameGenerator(player_name, enemy_name, conflictTheater,
|
||||
settings, timePeriod, starting_money,
|
||||
multiplier, midGame)
|
||||
self.generatedGame = generator.generate()
|
||||
|
||||
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):
|
||||
def __init__(self, parent=None):
|
||||
|
||||
@ -35,7 +35,6 @@ class ControlPoint(MissionTarget):
|
||||
|
||||
position = None # type: Point
|
||||
name = None # type: str
|
||||
allow_sea_units = True
|
||||
|
||||
captured = False
|
||||
has_frontline = True
|
||||
@ -47,10 +46,9 @@ class ControlPoint(MissionTarget):
|
||||
at: db.StartingPosition, radials: List[int], size: int,
|
||||
importance: float, has_frontline=True,
|
||||
cptype=ControlPointType.AIRBASE):
|
||||
super().__init__(" ".join(re.split(r" |-", name)[:2]), position)
|
||||
self.id = id
|
||||
self.name = " ".join(re.split(r" |-", name)[:2])
|
||||
self.full_name = name
|
||||
self.position: Point = position
|
||||
self.at = at
|
||||
self.ground_objects: List[TheaterGroundObject] = []
|
||||
|
||||
@ -228,10 +226,8 @@ class ControlPoint(MissionTarget):
|
||||
|
||||
# Handle cyclic dependency.
|
||||
from .start_generator import generate_airbase_defense_group
|
||||
airbase_def_id = 0
|
||||
for ground_object in self.ground_objects:
|
||||
for idx, ground_object in enumerate(self.ground_objects):
|
||||
ground_object.groups = []
|
||||
if ground_object.airbase_group and faction_name != "":
|
||||
generate_airbase_defense_group(airbase_def_id, ground_object,
|
||||
faction_name, game, self)
|
||||
airbase_def_id = airbase_def_id + 1
|
||||
generate_airbase_defense_group(idx, ground_object,
|
||||
faction_name, game)
|
||||
|
||||
@ -4,11 +4,27 @@ from typing import Tuple
|
||||
from dcs.mapping import Point
|
||||
from . import ControlPoint, MissionTarget
|
||||
|
||||
|
||||
# TODO: Dedup by moving everything to using this class.
|
||||
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):
|
||||
"""Defines a front line location between two control points.
|
||||
|
||||
@ -17,6 +33,8 @@ class FrontLine(MissionTarget):
|
||||
|
||||
def __init__(self, control_point_a: ControlPoint,
|
||||
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_b = control_point_b
|
||||
|
||||
@ -24,22 +42,3 @@ class FrontLine(MissionTarget):
|
||||
def control_points(self) -> Tuple[ControlPoint, ControlPoint]:
|
||||
"""Returns a tuple of the two control points."""
|
||||
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 abc import ABC, abstractmethod
|
||||
from dcs.mapping import Point
|
||||
|
||||
|
||||
class MissionTarget(ABC):
|
||||
# TODO: These should just be required objects to the constructor
|
||||
# The TheatherGroundObject class is difficult to modify because it's
|
||||
# generated data that's pickled ahead of time.
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> str:
|
||||
"""The name of the mission target."""
|
||||
class MissionTarget:
|
||||
def __init__(self, name: str, position: Point) -> None:
|
||||
"""Initializes a mission target.
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def position(self) -> Point:
|
||||
"""The location of the mission target."""
|
||||
Args:
|
||||
name: The name of the mission target.
|
||||
position: The location of the mission target.
|
||||
"""
|
||||
self.name = name
|
||||
self.position = position
|
||||
|
||||
def distance_to(self, other: MissionTarget) -> int:
|
||||
"""Computes the distance to the given mission target."""
|
||||
|
||||
@ -1,16 +1,19 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import math
|
||||
import pickle
|
||||
import random
|
||||
import typing
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from dcs.mapping import Point
|
||||
from dcs.task import CAP, CAS, PinpointStrike
|
||||
from dcs.vehicles import AirDefence
|
||||
|
||||
from game import db
|
||||
from game.data.building_data import DEFAULT_AVAILABLE_BUILDINGS
|
||||
from game import Game, db
|
||||
from game.factions.faction import Faction
|
||||
from game.settings import Settings
|
||||
from game.version import VERSION
|
||||
from gen import namegen
|
||||
from gen.defenses.armor_group_generator import generate_armor_group
|
||||
from gen.fleet.ship_group_generator import (
|
||||
@ -30,6 +33,13 @@ from theater import (
|
||||
TheaterGroundObject,
|
||||
)
|
||||
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_AMOUNT_FACTOR = 16
|
||||
@ -43,215 +53,387 @@ COUNT_BY_TASK = {
|
||||
}
|
||||
|
||||
|
||||
def generate_initial_units(theater: ConflictTheater, enemy_country: str, sams: bool, multiplier: float):
|
||||
for cp in theater.enemy_points():
|
||||
if cp.captured:
|
||||
continue
|
||||
class GameGenerator:
|
||||
def __init__(self, player: str, enemy: str, theater: ConflictTheater,
|
||||
settings: Settings, start_date, starting_budget: int,
|
||||
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
|
||||
cp.base.aircraft = {}
|
||||
cp.base.armor = {}
|
||||
cp.base.aa = {}
|
||||
cp.base.commision_points = {}
|
||||
cp.base.strength = 1
|
||||
control_point.base.aircraft = {}
|
||||
control_point.base.armor = {}
|
||||
control_point.base.aa = {}
|
||||
control_point.base.commision_points = {}
|
||||
control_point.base.strength = 1
|
||||
|
||||
for task in [PinpointStrike, CAP, CAS, AirDefence]:
|
||||
assert cp.importance <= IMPORTANCE_HIGH, "invalid importance {}".format(cp.importance)
|
||||
assert cp.importance >= IMPORTANCE_LOW, "invalid importance {}".format(cp.importance)
|
||||
if IMPORTANCE_HIGH <= control_point.importance <= IMPORTANCE_LOW:
|
||||
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)
|
||||
variety = int(UNIT_VARIETY)
|
||||
unittypes = db.choose_units(task, importance_factor, variety, enemy_country)
|
||||
|
||||
if not sams and task == AirDefence:
|
||||
unittypes = [x for x in db.find_unittype(AirDefence, enemy_country) if x not in db.SAM_BAN]
|
||||
|
||||
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:
|
||||
importance_factor = ((control_point.importance - IMPORTANCE_LOW) /
|
||||
(IMPORTANCE_HIGH - IMPORTANCE_LOW))
|
||||
# noinspection PyTypeChecker
|
||||
unit_types = db.choose_units(task, importance_factor, UNIT_VARIETY,
|
||||
self.enemy)
|
||||
if not unit_types:
|
||||
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)
|
||||
|
||||
if point is None:
|
||||
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)
|
||||
count_per_type = max(int(float(count) / len(unit_types)), 1)
|
||||
for unit_type in unit_types:
|
||||
control_point.base.commision_units({unit_type: count_per_type})
|
||||
|
||||
|
||||
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")
|
||||
logging.info(faction)
|
||||
logging.info(airbase_defense_group_id)
|
||||
@property
|
||||
def faction(self) -> Faction:
|
||||
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:
|
||||
group = generate_armor_group(faction, game, ground_obj)
|
||||
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:
|
||||
group = generate_shorad_group(game, cp, ground_obj, faction)
|
||||
group = generate_shorad_group(game, ground_obj, faction)
|
||||
else:
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
: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 theater: Theater object
|
||||
:param min: Minimal range from point
|
||||
:param max: Max range from point
|
||||
:param min_range: Minimal range from point
|
||||
:param max_range: Max range from point
|
||||
:param others: Other already existing ground objects
|
||||
:param is_base_defense: True if the location is for base defense.
|
||||
:return:
|
||||
"""
|
||||
point = None
|
||||
for _ in range(300):
|
||||
|
||||
# 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):
|
||||
point = 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
|
||||
|
||||
if point:
|
||||
for other in theater.controlpoints:
|
||||
if is_base_defense: break
|
||||
if other.position != near:
|
||||
for control_point in theater.controlpoints:
|
||||
if is_base_defense:
|
||||
break
|
||||
if control_point.position != near:
|
||||
if point is None:
|
||||
break
|
||||
if other.position.distance_to_point(point) < 30000:
|
||||
if control_point.position.distance_to_point(point) < 30000:
|
||||
point = None
|
||||
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:
|
||||
point = None
|
||||
break
|
||||
@ -313,114 +505,3 @@ def find_location(on_ground, near, theater, min, max, others, is_base_defense=Fa
|
||||
if point:
|
||||
return point
|
||||
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 dcs.mapping import Point
|
||||
@ -68,21 +69,21 @@ CATEGORY_MAP = {
|
||||
|
||||
|
||||
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.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
|
||||
def string_identifier(self):
|
||||
@ -96,20 +97,128 @@ class TheaterGroundObject(MissionTarget):
|
||||
def name_abbrev(self) -> str:
|
||||
return ABBREV_NAME[self.category]
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return NAME_BY_CATEGORY[self.category]
|
||||
|
||||
def matches_string_identifier(self, id):
|
||||
return self.string_identifier == id
|
||||
def matches_string_identifier(self, identifier):
|
||||
return self.string_identifier == identifier
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self.obj_name
|
||||
def obj_name(self) -> str:
|
||||
return self.name
|
||||
|
||||
def parent_control_point(
|
||||
self, theater: "ConflictTheater") -> "ControlPoint":
|
||||
def parent_control_point(self, theater: ConflictTheater) -> ControlPoint:
|
||||
"""Searches the theater for the parent control point."""
|
||||
for cp in theater.controlpoints:
|
||||
if cp.id == self.cp_id:
|
||||
return cp
|
||||
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