From 95b0b851a55131cba9438ac78201724136009da5 Mon Sep 17 00:00:00 2001 From: SnappyComebacks <74509817+SnappyComebacks@users.noreply.github.com> Date: Wed, 26 May 2021 18:37:43 -0600 Subject: [PATCH] Limit front line size with ammo depots. This limit is determined by the number of buildings that belong to Ammo Depots at the front line's connected Control Point. The limit increases for every surviving building at ammo depot objectives. There is a lower limit to the number of units that will spawn, so that if there are no surviving ammo depot buildings at a control point, there will still be some ground conflict. --- game/data/building_data.py | 1 - game/procurement.py | 11 ++++++++++- game/theater/conflicttheater.py | 16 +++++++++++++++- game/theater/controlpoint.py | 20 ++++++++++++++++++++ game/theater/start_generator.py | 6 ++++++ game/version.py | 10 +++++++++- gen/ground_forces/ai_ground_planner.py | 13 +++++++++++++ 7 files changed, 73 insertions(+), 4 deletions(-) diff --git a/game/data/building_data.py b/game/data/building_data.py index 5688fcb8..c3bd62d9 100644 --- a/game/data/building_data.py +++ b/game/data/building_data.py @@ -3,7 +3,6 @@ import dcs DEFAULT_AVAILABLE_BUILDINGS = [ "fuel", - "ammo", "comms", "oil", "ware", diff --git a/game/procurement.py b/game/procurement.py index 0fe0e833..b7222dc4 100644 --- a/game/procurement.py +++ b/game/procurement.py @@ -262,10 +262,19 @@ class ProcurementAi: # Prefer to buy front line units at active front lines that are not # already overloaded. for cp in self.owned_points: + + total_ground_units_allocated_to_this_control_point = ( + self.total_ground_units_allocated_to(cp) + ) + if not cp.has_ground_unit_source(self.game): continue - if self.total_ground_units_allocated_to(cp) >= 50: + if ( + total_ground_units_allocated_to_this_control_point >= 50 + or total_ground_units_allocated_to_this_control_point + >= cp.frontline_unit_count_limit + ): # Control point is already sufficiently defended. continue for connected in cp.connected_points: diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 26192cb7..e62a39f0 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -22,7 +22,7 @@ from dcs.ships import ( DDG_Arleigh_Burke_IIa, LHA_1_Tarawa, ) -from dcs.statics import Fortification +from dcs.statics import Fortification, Warehouse from dcs.terrain import ( caucasus, nevada, @@ -128,6 +128,8 @@ class MizCampaignLoader: FACTORY_UNIT_TYPE = Fortification.Workshop_A.id + AMMUNITION_DEPOT_UNIT_TYPE = Warehouse.Ammunition_depot.id + REQUIRED_STRIKE_TARGET_UNIT_TYPE = Fortification.Tech_combine.id BASE_DEFENSE_RADIUS = nautical_miles(2) @@ -320,6 +322,12 @@ class MizCampaignLoader: if group.units[0].type in self.FACTORY_UNIT_TYPE: yield group + @property + def ammunition_depots(self) -> Iterator[StaticGroup]: + for group in itertools.chain(self.blue.static_group, self.red.static_group): + if group.units[0].type in self.AMMUNITION_DEPOT_UNIT_TYPE: + yield group + @property def required_strike_targets(self) -> Iterator[StaticGroup]: for group in itertools.chain(self.blue.static_group, self.red.static_group): @@ -559,6 +567,12 @@ class MizCampaignLoader: PointWithHeading.from_point(group.position, group.units[0].heading) ) + for group in self.ammunition_depots: + closest, distance = self.objective_info(group) + closest.preset_locations.ammunition_depots.append( + PointWithHeading.from_point(group.position, group.units[0].heading) + ) + for group in self.required_strike_targets: closest, distance = self.objective_info(group) closest.preset_locations.required_strike_locations.append( diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index bfa707d3..a9312545 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -59,6 +59,9 @@ if TYPE_CHECKING: from game import Game from gen.flights.flight import FlightType +FREE_FRONTLINE_UNIT_SUPPLY: int = 15 +AMMO_DEPOT_FRONTLINE_UNIT_CONTRIBUTION: int = 12 + class ControlPointType(Enum): #: An airbase with slots for everything. @@ -161,6 +164,9 @@ class PresetLocations: #: Locations of factories for producing ground units. These will always be spawned. factories: List[PointWithHeading] = field(default_factory=list) + #: Locations of ammo depots for controlling number of units on the front line at a control point. + ammunition_depots: List[PointWithHeading] = field(default_factory=list) + #: Locations of stationary armor groups. These will always be spawned. armor_groups: List[PointWithHeading] = field(default_factory=list) @@ -783,6 +789,20 @@ class ControlPoint(MissionTarget, ABC): return self.captured != other.captured + @property + def frontline_unit_count_limit(self) -> int: + + tally_connected_ammo_depots = 0 + + for cp_objective in self.connected_objectives: + if cp_objective.category == "ammo" and not cp_objective.is_dead: + tally_connected_ammo_depots += 1 + + return ( + FREE_FRONTLINE_UNIT_SUPPLY + + tally_connected_ammo_depots * AMMO_DEPOT_FRONTLINE_UNIT_CONTRIBUTION + ) + @property def strike_targets(self) -> List[Union[MissionTarget, Unit]]: return [] diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index 2fbb7c57..88ad2bcd 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -471,6 +471,7 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator): self.generate_strike_targets() self.generate_offshore_strike_targets() self.generate_factories() + self.generate_ammunition_depots() if self.faction.missiles: self.generate_missile_sites() @@ -629,6 +630,10 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator): self.control_point.connected_objectives.append(g) + def generate_ammunition_depots(self) -> None: + for position in self.control_point.preset_locations.ammunition_depots: + self.generate_strike_target_at(category="ammo", position=position) + def generate_factories(self) -> None: """Generates the factories that are required by the campaign.""" for position in self.control_point.preset_locations.factories: @@ -828,6 +833,7 @@ class FobGroundObjectGenerator(AirbaseGroundObjectGenerator): FobDefenseGenerator(self.game, self.control_point).generate() self.generate_armor_groups() self.generate_factories() + self.generate_ammunition_depots() self.generate_required_aa() self.generate_required_ewr() self.generate_scenery_sites() diff --git a/game/version.py b/game/version.py index 2a9cebbb..564c43e3 100644 --- a/game/version.py +++ b/game/version.py @@ -73,4 +73,12 @@ VERSION = _build_version_string() #: * AAA_8_8cm_Flak_18, #: * SPAAA_Vulcan_M163, #: * SPAAA_ZSU_23_4_Shilka_Gun_Dish, -CAMPAIGN_FORMAT_VERSION = (4, 2) +#: +#: Version 5.0 +#: * Ammunition Depots objective locations are now predetermined using the "Ammunition Depot" +#: Warehouse object, and through trigger zone based scenery objects. +#: * The number of alive Ammunition Depot objective buildings connected to a control point +#: directly influences how many ground units can be supported on the front line. +#: * The number of supported ground units at any control point is artificially capped at 50, +#: even if the number of alive Ammunition Depot objectives can support more. +CAMPAIGN_FORMAT_VERSION = (5, 0) diff --git a/gen/ground_forces/ai_ground_planner.py b/gen/ground_forces/ai_ground_planner.py index 777efd01..761bf76b 100644 --- a/gen/ground_forces/ai_ground_planner.py +++ b/gen/ground_forces/ai_ground_planner.py @@ -80,6 +80,10 @@ class GroundPlanner: def plan_groundwar(self): + ground_unit_limit = self.cp.frontline_unit_count_limit + + remaining_available_frontline_units = ground_unit_limit + if hasattr(self.cp, "stance"): group_size_choice = GROUP_SIZES_BY_COMBAT_STANCE[self.cp.stance] else: @@ -118,6 +122,12 @@ class GroundPlanner: continue available = self.cp.base.armor[key] + + if available > remaining_available_frontline_units: + available = remaining_available_frontline_units + + remaining_available_frontline_units -= available + while available > 0: if role == CombatGroupRole.SHORAD: @@ -144,6 +154,9 @@ class GroundPlanner: group.units.append(key) collection.append(group) + if remaining_available_frontline_units == 0: + break + print("------------------") print("Ground Planner : ") print(self.cp.name)