diff --git a/changelog.md b/changelog.md index 39e886c9..4508cbfa 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,7 @@ # Retribution v1.5.0 ## Features/Improvements +* **[Campaigns]** Ability to define invisible FOBs * **[Plugins]** Improvements to AI support for EW Script 2.0 * **[Config]** New preference setting to trigger the first-start window on every start (could help in scenarios multiple Retribution instances need to run concurrently) diff --git a/game/campaignloader/mizcampaignloader.py b/game/campaignloader/mizcampaignloader.py index ebcdab5c..be722e02 100644 --- a/game/campaignloader/mizcampaignloader.py +++ b/game/campaignloader/mizcampaignloader.py @@ -53,6 +53,7 @@ class MizCampaignLoader: FOB_UNIT_TYPE = Unarmed.SKP_11.id FARP_HELIPADS_TYPE = ["Invisible FARP", "SINGLE_HELIPAD", "FARP"] + INVISIBLE_FOB_UNIT_TYPE = Unarmed.M_818.id OFFSHORE_STRIKE_TARGET_UNIT_TYPE = Fortification.Oil_platform.id SHIP_UNIT_TYPE = USS_Arleigh_Burke_IIa.id @@ -166,6 +167,11 @@ class MizCampaignLoader: if group.units[0].type == self.FOB_UNIT_TYPE: yield group + def invisible_fobs(self, blue: bool) -> Iterator[VehicleGroup]: + for group in self.country(blue).vehicle_group: + if group.units[0].type == self.INVISIBLE_FOB_UNIT_TYPE: + yield group + @property def ships(self) -> Iterator[ShipGroup]: for group in self.red.ship_group: @@ -312,6 +318,19 @@ class MizCampaignLoader: control_point.captured_invert = fob.late_activation control_points[control_point.id] = control_point + for fob in self.invisible_fobs(blue): + ctld_zones = self.get_ctld_zones(fob.name) + control_point = Fob( + str(fob.name), + fob.position, + self.theater, + starts_blue=blue, + ctld_zones=ctld_zones, + is_invisible=True, + ) + control_point.captured_invert = fob.late_activation + control_points[control_point.id] = control_point + return control_points @property diff --git a/game/data/building_data.py b/game/data/building_data.py index d50c6c0f..4b759633 100644 --- a/game/data/building_data.py +++ b/game/data/building_data.py @@ -6,6 +6,7 @@ REQUIRED_BUILDINGS = [ "ammo", "factory", "fob", + "invisiblefob", "oil", ] diff --git a/game/data/groups.py b/game/data/groups.py index 9f792f82..d8ef62b0 100644 --- a/game/data/groups.py +++ b/game/data/groups.py @@ -60,6 +60,7 @@ class GroupTask(Enum): FARP = ("Farp", GroupRole.BUILDING) FOB = ("FOB", GroupRole.BUILDING) FUEL = ("Fuel", GroupRole.BUILDING) + INVISIBLE_FOB = ("InvisibleFOB", GroupRole.BUILDING) OFFSHORE_STRIKE_TARGET = ("OffShoreStrikeTarget", GroupRole.BUILDING) OIL = ("Oil", GroupRole.BUILDING) diff --git a/game/layout/layout.py b/game/layout/layout.py index 32e8fe7d..5ebd4738 100644 --- a/game/layout/layout.py +++ b/game/layout/layout.py @@ -288,6 +288,8 @@ class BuildingLayout(TgoLayout): @property def category(self) -> str: for task in self.tasks: + if task is GroupTask.INVISIBLE_FOB: + return "fob" if task not in [GroupTask.STRIKE_TARGET, GroupTask.OFFSHORE_STRIKE_TARGET]: return task.description.lower() raise RuntimeError(f"Building Template {self.name} has no building category") diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 389809a2..d4f089a1 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -377,6 +377,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC): theater: ConflictTheater, starts_blue: bool, cptype: ControlPointType = ControlPointType.AIRBASE, + is_invisible: bool = False, ) -> None: super().__init__(name, position) self.id = uuid.uuid4() @@ -384,6 +385,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC): self.at = at self.theater = theater self.starts_blue = starts_blue + self.is_invisible = is_invisible self.connected_objectives: List[TheaterGroundObject] = [] self.preset_locations = PresetLocations() self.helipads: List[PointWithHeading] = [] @@ -1618,12 +1620,14 @@ class Fob(ControlPoint, RadioFrequencyContainer, CTLD): theater: ConflictTheater, starts_blue: bool, ctld_zones: Optional[List[Tuple[Point, float]]] = None, + is_invisible: bool = False, ) -> None: super().__init__( name, at, at, theater, starts_blue, cptype=ControlPointType.FOB ) self.name = name self.ctld_zones = ctld_zones + self.is_invisible = is_invisible @property def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]: diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index 600a7a37..f9e3bdb7 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -586,14 +586,24 @@ class FobGroundObjectGenerator(AirbaseGroundObjectGenerator): return False def generate_fob(self) -> None: - self.generate_building_at( - GroupTask.FOB, - PresetLocation( - self.control_point.name, - self.control_point.position, - self.control_point.heading, - ), - ) + if self.control_point.is_invisible: + self.generate_building_at( + GroupTask.INVISIBLE_FOB, + PresetLocation( + self.control_point.name, + self.control_point.position, + self.control_point.heading, + ), + ) + else: + self.generate_building_at( + GroupTask.FOB, + PresetLocation( + self.control_point.name, + self.control_point.position, + self.control_point.heading, + ), + ) class GroundObjectGenerator: diff --git a/resources/layouts/buildings/invisiblefob1.yaml b/resources/layouts/buildings/invisiblefob1.yaml new file mode 100644 index 00000000..fca46305 --- /dev/null +++ b/resources/layouts/buildings/invisiblefob1.yaml @@ -0,0 +1,14 @@ +name: invisiblefob1 +generic: true +tasks: + - InvisibleFOB +groups: + - FOB: + - name: fob1 0 + statics: + - fob1 0-0 + unit_count: + - 1 + unit_types: + - "Jerrycan" +layout_file: resources/layouts/buildings/buildings.miz