diff --git a/client/src/api/eventstream.tsx b/client/src/api/eventstream.tsx index defc90e3..ebe9c5b0 100644 --- a/client/src/api/eventstream.tsx +++ b/client/src/api/eventstream.tsx @@ -31,7 +31,7 @@ import { updateTgo } from "./tgosSlice"; import { threatZonesUpdated } from "./threatZonesSlice"; import { unculledZonesUpdated } from "./unculledZonesSlice"; import { LatLng } from "leaflet"; -import { updateIadsConnection } from "./iadsNetworkSlice"; +import { updateIadsConnection, removeIadsConnection } from "./iadsNetworkSlice"; interface GameUpdateEvents { updated_flight_positions: { [id: string]: LatLng }; @@ -50,6 +50,8 @@ interface GameUpdateEvents { deleted_front_lines: string[]; updated_tgos: string[]; updated_control_points: ControlPoint[]; + updated_iads: IadsConnection[]; + deleted_iads: string[]; reset_on_map_center: LatLng | null; game_unloaded: boolean; new_turn: boolean; @@ -134,17 +136,20 @@ export const handleStreamedEvents = ( const tgo = response.data as Tgo; dispatch(updateTgo(tgo)); }); - backend.get(`/iads-network/for-tgo/${id}`).then((response) => { - for (const connection of response.data) { - dispatch(updateIadsConnection(connection as IadsConnection)); - } - }); } if (events.updated_control_points.length > 0) { dispatch(updateControlPoint(events.updated_control_points)); } + if (events.deleted_iads.length > 0) { + dispatch(removeIadsConnection(events.deleted_iads)); + } + + if (events.updated_iads.length > 0) { + dispatch(updateIadsConnection(events.updated_iads)); + } + if (events.reset_on_map_center != null) { reloadGameState(dispatch); } diff --git a/client/src/api/iadsNetworkSlice.ts b/client/src/api/iadsNetworkSlice.ts index 27042c14..f61fcff7 100644 --- a/client/src/api/iadsNetworkSlice.ts +++ b/client/src/api/iadsNetworkSlice.ts @@ -15,10 +15,16 @@ export const IadsNetworkSlice = createSlice({ name: "iadsNetwork", initialState, reducers: { - updateIadsConnection: (state, action: PayloadAction) => { - const connection = action.payload; - state.connections[connection.id] = connection + updateIadsConnection: (state, action: PayloadAction) => { + for (const connection of action.payload) { + state.connections[connection.id] = connection + } }, + removeIadsConnection: (state, action: PayloadAction) => { + for (const cID of action.payload) { + delete state.connections[cID]; + } + } }, extraReducers: (builder) => { builder.addCase(gameLoaded, (state, action) => { @@ -36,7 +42,7 @@ export const IadsNetworkSlice = createSlice({ }, }); -export const { updateIadsConnection } = IadsNetworkSlice.actions; +export const { updateIadsConnection, removeIadsConnection } = IadsNetworkSlice.actions; export const selectIadsNetwork = (state: RootState) => state.iadsNetwork; diff --git a/game/server/eventstream/models.py b/game/server/eventstream/models.py index 6873357b..a1662f35 100644 --- a/game/server/eventstream/models.py +++ b/game/server/eventstream/models.py @@ -9,6 +9,7 @@ from game.server.combat.models import FrozenCombatJs from game.server.controlpoints.models import ControlPointJs from game.server.flights.models import FlightJs from game.server.frontlines.models import FrontLineJs +from game.server.iadsnetwork.models import IadsConnectionJs from game.server.leaflet import LeafletPoint from game.server.mapzones.models import UnculledZoneJs @@ -34,6 +35,8 @@ class GameUpdateEventsJs(BaseModel): deleted_front_lines: set[UUID] updated_tgos: set[UUID] updated_control_points: list[ControlPointJs] + updated_iads: list[IadsConnectionJs] + deleted_iads: set[UUID] reset_on_map_center: LeafletPoint | None game_unloaded: bool new_turn: bool @@ -48,6 +51,7 @@ class GameUpdateEventsJs(BaseModel): new_combats = [] updated_combats = [] updated_unculled_zones = [] + updated_iads = [] if game is not None: new_combats = [ FrozenCombatJs.for_combat(c, game.theater) for c in events.new_combats @@ -57,6 +61,8 @@ class GameUpdateEventsJs(BaseModel): for c in events.updated_combats ] updated_unculled_zones = UnculledZoneJs.from_game(game) + for node in events.updated_iads: + updated_iads.extend(IadsConnectionJs.connections_for_node(node)) return GameUpdateEventsJs( updated_flight_positions={ @@ -87,6 +93,8 @@ class GameUpdateEventsJs(BaseModel): ControlPointJs.for_control_point(cp) for cp in events.updated_control_points ], + updated_iads=updated_iads, + deleted_iads=events.deleted_iads_connections, reset_on_map_center=events.reset_on_map_center, game_unloaded=events.game_unloaded, new_turn=events.new_turn, diff --git a/game/sim/gameupdateevents.py b/game/sim/gameupdateevents.py index 4ef2df63..f4e2a395 100644 --- a/game/sim/gameupdateevents.py +++ b/game/sim/gameupdateevents.py @@ -12,6 +12,7 @@ if TYPE_CHECKING: from game.ato import Flight, Package from game.sim.combat import FrozenCombat from game.theater import ControlPoint, FrontLine, TheaterGroundObject + from game.theater.iadsnetwork.iadsnetwork import IadsNetworkNode @dataclass @@ -33,6 +34,8 @@ class GameUpdateEvents: deleted_front_lines: set[UUID] = field(default_factory=set) updated_tgos: set[UUID] = field(default_factory=set) updated_control_points: set[ControlPoint] = field(default_factory=set) + updated_iads: set[IadsNetworkNode] = field(default_factory=set) + deleted_iads_connections: set[UUID] = field(default_factory=set) reset_on_map_center: LatLng | None = None game_unloaded: bool = False new_turn: bool = False @@ -121,6 +124,14 @@ class GameUpdateEvents: self.updated_control_points.add(control_point) return self + def update_iads_node(self, iads_node: IadsNetworkNode) -> GameUpdateEvents: + self.updated_iads.add(iads_node) + return self + + def delete_iads_connection(self, connection_id: UUID) -> GameUpdateEvents: + self.deleted_iads_connections.add(connection_id) + return self + def game_loaded(self, game: Game | None) -> GameUpdateEvents: if game is None: self.game_unloaded = True diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index e61b447e..b6d5037d 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -843,7 +843,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC): tgo, VehicleGroupGroundObject ): if isinstance(tgo, IadsGroundObject): - game.theater.iads_network.update_tgo(tgo) + 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) if not tgo.is_control_point: diff --git a/game/theater/iadsnetwork/iadsnetwork.py b/game/theater/iadsnetwork/iadsnetwork.py index 3be2c5de..759a8690 100644 --- a/game/theater/iadsnetwork/iadsnetwork.py +++ b/game/theater/iadsnetwork/iadsnetwork.py @@ -18,6 +18,7 @@ from game.theater.theatergroup import IadsGroundGroup if TYPE_CHECKING: from game.game import Game + from game.sim import GameUpdateEvents class IadsNetworkException(Exception): @@ -133,19 +134,21 @@ class IadsNetwork: skynet_nodes.append(skynet_node) return skynet_nodes - def update_tgo(self, tgo: TheaterGroundObject) -> None: + def update_tgo(self, tgo: TheaterGroundObject, events: GameUpdateEvents) -> None: """Update the IADS Network for the given TGO""" # Remove existing nodes for the given tgo for cn in self.nodes: if cn.group.ground_object == tgo: self.nodes.remove(cn) + for cID in cn.connections: + events.delete_iads_connection(cID) - # Create a new node for the tgo node = self.node_for_tgo(tgo) if node is None: # Not participating return # TODO Add the connections or calculate them.. + events.update_iads_node(node) def node_for_group(self, group: IadsGroundGroup) -> IadsNetworkNode: """Get existing node from the iads network or create a new node""" diff --git a/qt_ui/windows/groundobject/QGroundObjectMenu.py b/qt_ui/windows/groundobject/QGroundObjectMenu.py index e81f69ea..4db606bf 100644 --- a/qt_ui/windows/groundobject/QGroundObjectMenu.py +++ b/qt_ui/windows/groundobject/QGroundObjectMenu.py @@ -258,7 +258,7 @@ class QGroundObjectMenu(QDialog): def update_game(self) -> None: events = GameUpdateEvents() events.update_tgo(self.ground_object) - self.game.theater.iads_network.update_tgo(self.ground_object) + self.game.theater.iads_network.update_tgo(self.ground_object, events) if any( package.target == self.ground_object for package in self.game.ato_for(player=False).packages