mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Merge branch 'theater-refactor' into develop
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
import re
|
||||
from typing import Dict, List, TYPE_CHECKING
|
||||
from enum import Enum
|
||||
@@ -17,7 +18,7 @@ from game import db
|
||||
from gen.ground_forces.combat_stance import CombatStance
|
||||
from .base import Base
|
||||
from .missiontarget import MissionTarget
|
||||
from .theatergroundobject import TheaterGroundObject
|
||||
from .theatergroundobject import SamGroundObject, TheaterGroundObject
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
@@ -50,7 +51,8 @@ class ControlPoint(MissionTarget):
|
||||
self.id = id
|
||||
self.full_name = name
|
||||
self.at = at
|
||||
self.ground_objects: List[TheaterGroundObject] = []
|
||||
self.connected_objectives: List[TheaterGroundObject] = []
|
||||
self.base_defenses: List[SamGroundObject] = []
|
||||
|
||||
self.size = size
|
||||
self.importance = importance
|
||||
@@ -64,6 +66,11 @@ class ControlPoint(MissionTarget):
|
||||
self.stances: Dict[int, CombatStance] = {}
|
||||
self.airport = None
|
||||
|
||||
@property
|
||||
def ground_objects(self) -> List[TheaterGroundObject]:
|
||||
return list(
|
||||
itertools.chain(self.connected_objectives, self.base_defenses))
|
||||
|
||||
@classmethod
|
||||
def from_airport(cls, airport: Airport, radials: List[int], size: int, importance: float, has_frontline=True):
|
||||
assert airport
|
||||
@@ -225,9 +232,6 @@ class ControlPoint(MissionTarget):
|
||||
self.base.armor = {}
|
||||
|
||||
# Handle cyclic dependency.
|
||||
from .start_generator import generate_airbase_defense_group
|
||||
for idx, ground_object in enumerate(self.ground_objects):
|
||||
ground_object.groups = []
|
||||
if ground_object.airbase_group and faction_name != "":
|
||||
generate_airbase_defense_group(idx, ground_object,
|
||||
faction_name, game)
|
||||
from .start_generator import BaseDefenseGenerator
|
||||
self.base_defenses = []
|
||||
BaseDefenseGenerator(game, self, faction_name).generate()
|
||||
|
||||
@@ -160,11 +160,9 @@ class GameGenerator:
|
||||
|
||||
|
||||
class ControlPointGroundObjectGenerator:
|
||||
def __init__(self, game: Game, control_point: ControlPoint,
|
||||
templates: GroundObjectTemplates) -> None:
|
||||
def __init__(self, game: Game, control_point: ControlPoint) -> None:
|
||||
self.game = game
|
||||
self.control_point = control_point
|
||||
self.templates = templates
|
||||
|
||||
@property
|
||||
def faction_name(self) -> str:
|
||||
@@ -178,8 +176,7 @@ class ControlPointGroundObjectGenerator:
|
||||
return db.FACTIONS[self.faction_name]
|
||||
|
||||
def generate(self) -> bool:
|
||||
self.control_point.ground_objects = []
|
||||
self.generate_ground_points()
|
||||
self.control_point.connected_objectives = []
|
||||
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
|
||||
@@ -187,85 +184,8 @@ class ControlPointGroundObjectGenerator:
|
||||
# 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:
|
||||
@@ -295,30 +215,7 @@ class ControlPointGroundObjectGenerator:
|
||||
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
|
||||
self.control_point.connected_objectives.append(g)
|
||||
|
||||
|
||||
class CarrierGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
@@ -341,7 +238,7 @@ class CarrierGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
g.groups = []
|
||||
if group is not None:
|
||||
g.groups.append(group)
|
||||
self.control_point.ground_objects.append(g)
|
||||
self.control_point.connected_objectives.append(g)
|
||||
self.control_point.name = random.choice(carrier_names)
|
||||
return True
|
||||
|
||||
@@ -366,21 +263,23 @@ class LhaGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
g.groups = []
|
||||
if group is not None:
|
||||
g.groups.append(group)
|
||||
self.control_point.ground_objects.append(g)
|
||||
self.control_point.connected_objectives.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
|
||||
class BaseDefenseGenerator:
|
||||
def __init__(self, game: Game, control_point: ControlPoint,
|
||||
faction_name: str) -> None:
|
||||
self.game = game
|
||||
self.control_point = control_point
|
||||
self.faction_name = faction_name
|
||||
|
||||
def generate(self) -> None:
|
||||
for i in range(random.randint(3, 6)):
|
||||
self.generate_sam(i)
|
||||
return True
|
||||
self.generate_base_defense(i)
|
||||
|
||||
def generate_sam(self, index: int) -> None:
|
||||
def generate_base_defense(self, index: int) -> None:
|
||||
position = find_location(True, self.control_point.position,
|
||||
self.game.theater, 400, 3200, [], True)
|
||||
|
||||
@@ -403,8 +302,122 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
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)
|
||||
generate_airbase_defense_group(index, g, self.faction_name,
|
||||
self.game)
|
||||
self.control_point.base_defenses.append(g)
|
||||
|
||||
|
||||
class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
||||
def __init__(self, game: Game, control_point: ControlPoint,
|
||||
templates: GroundObjectTemplates) -> None:
|
||||
super().__init__(game, control_point)
|
||||
self.templates = templates
|
||||
|
||||
def generate(self) -> bool:
|
||||
if not super().generate():
|
||||
return False
|
||||
|
||||
BaseDefenseGenerator(self.game, self.control_point,
|
||||
self.faction_name).generate()
|
||||
self.generate_ground_points()
|
||||
|
||||
if self.faction.missiles:
|
||||
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
|
||||
|
||||
# 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:
|
||||
self.generate_ground_point()
|
||||
|
||||
def generate_ground_point(self) -> None:
|
||||
try:
|
||||
category = random.choice(self.faction.building_set)
|
||||
except IndexError:
|
||||
logging.exception("Faction has no buildings defined")
|
||||
return
|
||||
|
||||
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.connected_objectives.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.connected_objectives.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.connected_objectives.append(g)
|
||||
return
|
||||
|
||||
|
||||
class GroundObjectGenerator:
|
||||
@@ -424,11 +437,9 @@ class GroundObjectGenerator:
|
||||
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)
|
||||
generator = CarrierGroundObjectGenerator(self.game, control_point)
|
||||
elif control_point.cptype == ControlPointType.LHA_GROUP:
|
||||
generator = LhaGroundObjectGenerator(self.game, control_point,
|
||||
self.templates)
|
||||
generator = LhaGroundObjectGenerator(self.game, control_point)
|
||||
else:
|
||||
generator = AirbaseGroundObjectGenerator(self.game, control_point,
|
||||
self.templates)
|
||||
@@ -436,8 +447,8 @@ class GroundObjectGenerator:
|
||||
|
||||
|
||||
def generate_airbase_defense_group(airbase_defense_group_id: int,
|
||||
ground_obj: TheaterGroundObject,
|
||||
faction: str, game: Game) -> None:
|
||||
ground_obj: SamGroundObject, 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:
|
||||
|
||||
@@ -8,7 +8,6 @@ from dcs.unit import Unit
|
||||
from dcs.unitgroup import Group
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .conflicttheater import ConflictTheater
|
||||
from .controlpoint import ControlPoint
|
||||
from .missiontarget import MissionTarget
|
||||
|
||||
@@ -72,29 +71,21 @@ CATEGORY_MAP = {
|
||||
|
||||
class TheaterGroundObject(MissionTarget):
|
||||
|
||||
def __init__(self, name: str, category: str, group_id: int, object_id: int,
|
||||
position: Point, heading: int, cp_id: int, dcs_identifier: str,
|
||||
def __init__(self, name: str, category: str, group_id: int, position: Point,
|
||||
heading: int, control_point: ControlPoint, 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.control_point = control_point
|
||||
self.dcs_identifier = dcs_identifier
|
||||
self.airbase_group = airbase_group
|
||||
self.sea_object = sea_object
|
||||
self.is_dead = False
|
||||
# TODO: There is never more than one group.
|
||||
self.groups: List[Group] = []
|
||||
|
||||
@property
|
||||
def string_identifier(self):
|
||||
return "{}|{}|{}|{}".format(self.category, self.cp_id, self.group_id, self.object_id)
|
||||
|
||||
@property
|
||||
def group_identifier(self) -> str:
|
||||
return "{}|{}".format(self.category, self.group_id)
|
||||
|
||||
@property
|
||||
def units(self) -> List[Unit]:
|
||||
"""
|
||||
@@ -103,26 +94,20 @@ class TheaterGroundObject(MissionTarget):
|
||||
return list(itertools.chain.from_iterable([g.units for g in self.groups]))
|
||||
|
||||
@property
|
||||
def name_abbrev(self) -> str:
|
||||
return ABBREV_NAME[self.category]
|
||||
def group_name(self) -> str:
|
||||
"""The name of the unit group."""
|
||||
return f"{self.category}|{self.group_id}"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return NAME_BY_CATEGORY[self.category]
|
||||
|
||||
def matches_string_identifier(self, identifier):
|
||||
return self.string_identifier == identifier
|
||||
def is_same_group(self, identifier: str) -> bool:
|
||||
return self.group_id == identifier
|
||||
|
||||
@property
|
||||
def obj_name(self) -> str:
|
||||
return self.name
|
||||
|
||||
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,
|
||||
@@ -132,14 +117,19 @@ class BuildingGroundObject(TheaterGroundObject):
|
||||
name=name,
|
||||
category=category,
|
||||
group_id=group_id,
|
||||
object_id=object_id,
|
||||
position=position,
|
||||
heading=heading,
|
||||
cp_id=control_point.id,
|
||||
control_point=control_point,
|
||||
dcs_identifier=dcs_identifier,
|
||||
airbase_group=False,
|
||||
sea_object=False
|
||||
)
|
||||
self.object_id = object_id
|
||||
|
||||
@property
|
||||
def group_name(self) -> str:
|
||||
"""The name of the unit group."""
|
||||
return f"{self.category}|{self.group_id}|{self.object_id}"
|
||||
|
||||
|
||||
class GenericCarrierGroundObject(TheaterGroundObject):
|
||||
@@ -154,10 +144,9 @@ class CarrierGroundObject(GenericCarrierGroundObject):
|
||||
name=name,
|
||||
category="CARRIER",
|
||||
group_id=group_id,
|
||||
object_id=0,
|
||||
position=control_point.position,
|
||||
heading=0,
|
||||
cp_id=control_point.id,
|
||||
control_point=control_point,
|
||||
dcs_identifier="CARRIER",
|
||||
airbase_group=True,
|
||||
sea_object=True
|
||||
@@ -172,10 +161,9 @@ class LhaGroundObject(GenericCarrierGroundObject):
|
||||
name=name,
|
||||
category="LHA",
|
||||
group_id=group_id,
|
||||
object_id=0,
|
||||
position=control_point.position,
|
||||
heading=0,
|
||||
cp_id=control_point.id,
|
||||
control_point=control_point,
|
||||
dcs_identifier="LHA",
|
||||
airbase_group=True,
|
||||
sea_object=True
|
||||
@@ -189,16 +177,18 @@ class MissileSiteGroundObject(TheaterGroundObject):
|
||||
name=name,
|
||||
category="aa",
|
||||
group_id=group_id,
|
||||
object_id=0,
|
||||
position=position,
|
||||
heading=0,
|
||||
cp_id=control_point.id,
|
||||
control_point=control_point,
|
||||
dcs_identifier="AA",
|
||||
airbase_group=False,
|
||||
sea_object=False
|
||||
)
|
||||
|
||||
|
||||
# TODO: Differentiate types.
|
||||
# This type gets used both for AA sites (SAM, AAA, or SHORAD) but also for the
|
||||
# armor garrisons at airbases. These should each be split into their own types.
|
||||
class SamGroundObject(TheaterGroundObject):
|
||||
def __init__(self, name: str, group_id: int, position: Point,
|
||||
control_point: ControlPoint, for_airbase: bool) -> None:
|
||||
@@ -206,14 +196,26 @@ class SamGroundObject(TheaterGroundObject):
|
||||
name=name,
|
||||
category="aa",
|
||||
group_id=group_id,
|
||||
object_id=0,
|
||||
position=position,
|
||||
heading=0,
|
||||
cp_id=control_point.id,
|
||||
control_point=control_point,
|
||||
dcs_identifier="AA",
|
||||
airbase_group=for_airbase,
|
||||
sea_object=False
|
||||
)
|
||||
# Set by the SAM unit generator if the generated group is compatible
|
||||
# with Skynet.
|
||||
self.skynet_capable = False
|
||||
|
||||
@property
|
||||
def group_name(self) -> str:
|
||||
if self.skynet_capable:
|
||||
# Prefix the group names of SAM sites with the side color so Skynet
|
||||
# can find them.
|
||||
color = "BLUE" if self.control_point.captured else "RED"
|
||||
return f"{color}|SAM|{self.group_id}"
|
||||
else:
|
||||
return super().group_name
|
||||
|
||||
|
||||
class ShipGroundObject(TheaterGroundObject):
|
||||
@@ -223,10 +225,9 @@ class ShipGroundObject(TheaterGroundObject):
|
||||
name=name,
|
||||
category="aa",
|
||||
group_id=group_id,
|
||||
object_id=0,
|
||||
position=position,
|
||||
heading=0,
|
||||
cp_id=control_point.id,
|
||||
control_point=control_point,
|
||||
dcs_identifier="AA",
|
||||
airbase_group=False,
|
||||
sea_object=True
|
||||
|
||||
Reference in New Issue
Block a user