diff --git a/game/sim/missionresultsprocessor.py b/game/sim/missionresultsprocessor.py index 46d903f1..7ab7c0c6 100644 --- a/game/sim/missionresultsprocessor.py +++ b/game/sim/missionresultsprocessor.py @@ -132,10 +132,20 @@ class MissionResultsProcessor: @staticmethod def commit_ground_losses(debriefing: Debriefing, events: GameUpdateEvents) -> None: + killed_ground_objects = [] for ground_object_loss in debriefing.ground_object_losses: ground_object_loss.theater_unit.kill(events) + killed_ground_objects.append(ground_object_loss.theater_unit.ground_object) for scenery_object_loss in debriefing.scenery_object_losses: scenery_object_loss.ground_unit.kill(events) + killed_ground_objects.append(scenery_object_loss.ground_unit.ground_object) + + # Update the IADS network if any participant had losses + iads_network = debriefing.game.theater.iads_network + for killed_ground_object in killed_ground_objects: + if killed_ground_object in iads_network.participating: + iads_network.update_network(events) + return @staticmethod def commit_damaged_runways(debriefing: Debriefing) -> None: diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 8727ff00..edf2c35e 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -839,18 +839,24 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC): events.update_control_point(self) # All the attached TGOs have either been depopulated or captured. Tell the UI to - # update their state. Also update orientation and IADS state for specific tgos + # update their state. Also update the orientation of all TGOs + iads_update_required = False + iads_network = game.theater.iads_network for tgo in self.connected_objectives: if isinstance(tgo, IadsGroundObject) or isinstance( tgo, VehicleGroupGroundObject ): - if isinstance(tgo, IadsGroundObject): - game.theater.iads_network.update_tgo(tgo, events) + if not iads_update_required and tgo in iads_network.participating: + iads_update_required = True conflict_heading = game.theater.heading_to_conflict_from(tgo.position) tgo.rotate(conflict_heading or tgo.heading) if not tgo.is_control_point: events.update_tgo(tgo) + # Update the IADS Network + if iads_update_required: + iads_network.update_network(events) + @property def required_aircraft_start_type(self) -> Optional[StartType]: return None diff --git a/game/theater/iadsnetwork/iadsnetwork.py b/game/theater/iadsnetwork/iadsnetwork.py index 5a0e2e9e..f8d28cb0 100644 --- a/game/theater/iadsnetwork/iadsnetwork.py +++ b/game/theater/iadsnetwork/iadsnetwork.py @@ -112,6 +112,17 @@ class IadsNetwork: else: raise RuntimeError("Invalid iads_config in campaign") + @property + def participating(self) -> Iterator[TheaterGroundObject]: + """All unique participating TGOs. First primary then secondary""" + secondary_nodes = [] + for node in self.nodes: + yield node.group.ground_object + for connection in node.connections.values(): + if connection.ground_object not in secondary_nodes: + secondary_nodes.append(connection.ground_object) + yield from secondary_nodes + def skynet_nodes(self, game: Game) -> list[SkynetNode]: """Get all skynet nodes from the IADS Network""" skynet_nodes: list[SkynetNode] = [] @@ -143,30 +154,39 @@ class IadsNetwork: def update_tgo(self, tgo: TheaterGroundObject, events: GameUpdateEvents) -> None: """Update the IADS Network for the given TGO""" - # Remove existing nodes for the given tgo + # Remove existing nodes for the given tgo if there are any for cn in self.nodes: if cn.group.ground_object == tgo: self.nodes.remove(cn) + # Also delete all connections for the given node for cID in cn.connections: events.delete_iads_connection(cID) - node = self.node_for_tgo(tgo) + # Try to create a new primary node for the TGO + node = self._new_node_for_tgo(tgo) if node is None: - # Not participating + # the ground object is not participating to the IADS Network return - # Add connections to the new node - primary_node = node.group.ground_object.original_name - if self.iads_config and primary_node in self.iads_config: + # Create the connections to the secondary nodes + if self.iads_config: # If iads_config was defined and campaign designer added a config for the - # given primary node generate the connections from the config + # given primary node generate the connections from the config. + # If the primary node was not defined in the iads_config it will be added + # without any connections self._add_connections_from_config(node) else: # Otherwise calculate the connections by range - self._add_connections_by_range(node) + self._calculate_connections_by_range(node) events.update_iads_node(node) + def update_network(self, events: GameUpdateEvents) -> None: + """Update all primary nodes of the IADS and recalculate connections""" + primary_nodes = [node.group.ground_object for node in self.nodes] + for primary_node in primary_nodes: + self.update_tgo(primary_node, events) + def node_for_group(self, group: IadsGroundGroup) -> IadsNetworkNode: """Get existing node from the iads network or create a new node""" for cn in self.nodes: @@ -182,7 +202,9 @@ class IadsNetwork: for cn in self.nodes: if cn.group.ground_object == tgo: return cn + return self._new_node_for_tgo(tgo) + def _new_node_for_tgo(self, tgo: TheaterGroundObject) -> Optional[IadsNetworkNode]: # Create new connection_node if none exists node: Optional[IadsNetworkNode] = None for group in tgo.groups: @@ -249,6 +271,8 @@ class IadsNetwork: def _add_connections_from_config(self, node: IadsNetworkNode) -> None: """Add all connections for the given primary node based on the iads_config""" primary_node = node.group.ground_object.original_name + # iads_config uses defaultdict, therefore when the connections of a primary + # node where not defined in the iads_config they will just be empty connections = self.iads_config[primary_node] for secondary_node in connections: try: @@ -259,8 +283,8 @@ class IadsNetwork: ) continue - def _add_connections_by_range(self, node: IadsNetworkNode) -> None: - """Add all connections for the given primary node based range calculation""" + def _calculate_connections_by_range(self, node: IadsNetworkNode) -> None: + """Add all connections for the primary node by calculating them by range""" primary_tgo = node.group.ground_object for nearby_go in self.ground_objects.values(): # Find nearby Power or Connection @@ -294,4 +318,4 @@ class IadsNetwork: if node is None: # TGO does not participate to iads network continue - self._add_connections_by_range(node) + self._calculate_connections_by_range(node)