diff --git a/client/src/api/eventstream.tsx b/client/src/api/eventstream.tsx index ab6e713a..298601ec 100644 --- a/client/src/api/eventstream.tsx +++ b/client/src/api/eventstream.tsx @@ -33,7 +33,6 @@ import { threatZonesUpdated } from "./threatZonesSlice"; import { unculledZonesUpdated } from "./unculledZonesSlice"; import { LatLng } from "leaflet"; import { updateIadsConnection, removeIadsConnection } from "./iadsNetworkSlice"; -import { IadsConnection } from "./_liberationApi"; import { supplyRoutesUpdated } from "./supplyRoutesSlice"; interface GameUpdateEvents { diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index b6d5037d..b875226f 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -839,10 +839,9 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC): # 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 for tgo in self.connected_objectives: - if isinstance(tgo, IadsGroundObject) or isinstance( - tgo, VehicleGroupGroundObject - ): - if isinstance(tgo, IadsGroundObject): + is_vehicle_go = isinstance(tgo, VehicleGroupGroundObject) + if tgo.is_iads or is_vehicle_go: + if tgo.is_iads: game.theater.iads_network.update_tgo(tgo, events) conflict_heading = game.theater.heading_to_conflict_from(tgo.position) tgo.rotate(conflict_heading or tgo.heading) diff --git a/game/theater/iadsnetwork/iadsnetwork.py b/game/theater/iadsnetwork/iadsnetwork.py index f6db3335..2edc9a57 100644 --- a/game/theater/iadsnetwork/iadsnetwork.py +++ b/game/theater/iadsnetwork/iadsnetwork.py @@ -120,13 +120,6 @@ class IadsNetwork: # Skip culled ground objects continue - # HOTFIX! Skip non-static nodes with no alive units left - # Delete this as soon as PRs #2285, #2286 & #2287 are merged - unit_count = len(node.group.units) - is_static = node.group.units[0].is_static if unit_count > 0 else False - if node.group.alive_units == 0 and not is_static: - continue - # SkynetNode.from_group(node.group) may raise an exception # (originating from SkynetNode.dcs_name_for_group) # but if it does, we want to know because it's supposed to be impossible afaict @@ -141,8 +134,53 @@ class IadsNetwork: skynet_nodes.append(skynet_node) return skynet_nodes + def _update_iads_comms_and_power( + self, tgo: TheaterGroundObject, events: GameUpdateEvents + ) -> None: + assert self.advanced_iads, "_update_iads_comms_and_power requires advanced IADS" + is_comm_or_power = IadsRole.for_category(tgo.category).is_comms_or_power + assert is_comm_or_power, "Invalid TGO was given for _update_iads_building" + + """ + Delete/Update connections to the comm tower/power station + If this function is called, it should imply only 2 possibilities (unless I missed one): + 1) A capture event occurred, thus the building now belongs to the capturing team + => All connections to this building are to the enemy, thus delete them + (mind that no new connections could have been formed since all TGOs were depopulated) + 2) The building was destroyed during a mission + => In this case we don't need to delete the connections + instead we just update the nodes that connect to this building + because those connections are still coming from friendly TGOs + Given the above, we should be able to use the following implications: + If the building is friendly compared to the node that we're checking + => Building was destroyed during mission + => Update nodes that connect to this building + Otherwise if the building is not friendly compared to the node we're checking + => Capture event occurred + => Delete all connections to this building + + TODO: clean up the code below by wrapping comm towers and power stations + preferably in a class like IadsNetworkNode, keeping a reference to connected nodes + """ + for node in self.nodes: + to_delete = [] + for cID in node.connections: + group = node.connections[cID] + if group.ground_object is tgo: + if self._is_friendly(node, tgo): + events.update_iads_node(node) + else: + to_delete.append(cID) + events.delete_iads_connection(cID) + for cID in to_delete: + del node.connections[cID] + if not self.iads_config: + self._update_network(tgo, events) + def update_tgo(self, tgo: TheaterGroundObject, events: GameUpdateEvents) -> None: """Update the IADS Network for the given TGO""" + if IadsRole.for_category(tgo.category).is_comms_or_power: + return self._update_iads_comms_and_power(tgo, events) # Remove existing nodes for the given tgo for cn in self.nodes: if cn.group.ground_object == tgo: @@ -275,3 +313,23 @@ class IadsNetwork: <= node.group.iads_role.connection_range.meters ): node.add_connection_for_tgo(nearby_go) + + def _is_friendly(self, node: IadsNetworkNode, tgo: TheaterGroundObject) -> bool: + node_friendly = node.group.ground_object.is_friendly(True) + tgo_friendly = tgo.is_friendly(True) + return node_friendly == tgo_friendly + + def _update_network( + self, tgo: TheaterGroundObject, events: GameUpdateEvents + ) -> None: + if tgo.is_dead: + return + iads_role = IadsRole.for_category(tgo.category) + if not iads_role.is_comms_or_power: + return + for node in self.nodes: + dist = node.group.ground_object.position.distance_to_point(tgo.position) + in_range = dist < iads_role.connection_range.meters + if in_range and self._is_friendly(node, tgo): + node.add_connection_for_tgo(tgo) + events.update_iads_node(node) diff --git a/game/theater/iadsnetwork/iadsrole.py b/game/theater/iadsnetwork/iadsrole.py index 17025173..135d1208 100644 --- a/game/theater/iadsnetwork/iadsrole.py +++ b/game/theater/iadsnetwork/iadsrole.py @@ -79,3 +79,10 @@ class IadsRole(Enum): IadsRole.NO_BEHAVIOR, IadsRole.POINT_DEFENSE, ] + + @property + def is_comms_or_power(self) -> bool: + return self in [ + IadsRole.POWER_SOURCE, + IadsRole.CONNECTION_NODE, + ] diff --git a/game/theater/theatergroundobject.py b/game/theater/theatergroundobject.py index ae219e0d..8fe14ce0 100644 --- a/game/theater/theatergroundobject.py +++ b/game/theater/theatergroundobject.py @@ -267,6 +267,10 @@ class TheaterGroundObject(MissionTarget, SidcDescribable, ABC): """Should this TGO head towards the closest conflict to work properly?""" return False + @property + def is_iads(self) -> bool: + return False + class BuildingGroundObject(TheaterGroundObject): def __init__( @@ -353,6 +357,10 @@ class NavalGroundObject(TheaterGroundObject, ABC): def purchasable(self) -> bool: return False + @property + def is_iads(self) -> bool: + return True + class GenericCarrierGroundObject(NavalGroundObject, ABC): @property @@ -490,6 +498,10 @@ class IadsGroundObject(TheaterGroundObject, ABC): def should_head_to_conflict(self) -> bool: return True + @property + def is_iads(self) -> bool: + return True + # The SamGroundObject represents all type of AA # The TGO can have multiple types of units (AAA,SAM,Support...) @@ -630,3 +642,7 @@ class IadsBuildingGroundObject(BuildingGroundObject): for mission_type in super().mission_types(for_player): if mission_type not in skippers: yield mission_type + + @property + def is_iads(self) -> bool: + return True diff --git a/game/theater/theatergroup.py b/game/theater/theatergroup.py index 6a23d8de..6d803643 100644 --- a/game/theater/theatergroup.py +++ b/game/theater/theatergroup.py @@ -63,6 +63,9 @@ class TheaterUnit: self.alive = False self.ground_object.invalidate_threat_poly() events.update_tgo(self.ground_object) + if self.ground_object.is_iads: + iads = self.ground_object.control_point.coalition.game.theater.iads_network + iads.update_tgo(self.ground_object, events) @property def unit_name(self) -> str: