diff --git a/client/src/api/_liberationApi.ts b/client/src/api/_liberationApi.ts index 58f37821..ac55fc07 100644 --- a/client/src/api/_liberationApi.ts +++ b/client/src/api/_liberationApi.ts @@ -208,25 +208,25 @@ export type ListControlPointsApiArg = void; export type GetControlPointByIdApiResponse = /** status 200 Successful Response */ ControlPoint; export type GetControlPointByIdApiArg = { - cpId: number; + cpId: string; }; export type ControlPointDestinationInRangeApiResponse = /** status 200 Successful Response */ boolean; export type ControlPointDestinationInRangeApiArg = { - cpId: number; + cpId: string; lat: number; lng: number; }; export type SetControlPointDestinationApiResponse = /** status 204 Successful Response */ undefined; export type SetControlPointDestinationApiArg = { - cpId: number; + cpId: string; body: LatLng; }; export type ClearControlPointDestinationApiResponse = /** status 204 Successful Response */ undefined; export type ClearControlPointDestinationApiArg = { - cpId: number; + cpId: string; }; export type GetDebugHoldZonesApiResponse = /** status 200 Successful Response */ HoldZones; @@ -302,12 +302,12 @@ export type OpenTgoInfoDialogApiArg = { export type OpenNewControlPointPackageDialogApiResponse = /** status 204 Successful Response */ undefined; export type OpenNewControlPointPackageDialogApiArg = { - cpId: number; + cpId: string; }; export type OpenControlPointInfoDialogApiResponse = /** status 204 Successful Response */ undefined; export type OpenControlPointInfoDialogApiArg = { - cpId: number; + cpId: string; }; export type ListSupplyRoutesApiResponse = /** status 200 Successful Response */ SupplyRoute[]; @@ -335,7 +335,7 @@ export type LatLng = { lng: number; }; export type ControlPoint = { - id: number; + id: string; name: string; blue: boolean; position: LatLng; diff --git a/client/src/api/controlPointsSlice.ts b/client/src/api/controlPointsSlice.ts index 0a92e713..39c74587 100644 --- a/client/src/api/controlPointsSlice.ts +++ b/client/src/api/controlPointsSlice.ts @@ -4,7 +4,7 @@ import { ControlPoint } from "./liberationApi"; import { PayloadAction, createSlice } from "@reduxjs/toolkit"; interface ControlPointsState { - controlPoints: { [key: number]: ControlPoint }; + controlPoints: { [key: string]: ControlPoint }; } const initialState: ControlPointsState = { @@ -23,7 +23,7 @@ export const controlPointsSlice = createSlice({ extraReducers: (builder) => { builder.addCase(gameLoaded, (state, action) => { state.controlPoints = action.payload.control_points.reduce( - (acc: { [key: number]: ControlPoint }, curr) => { + (acc: { [key: string]: ControlPoint }, curr) => { acc[curr.id] = curr; return acc; }, diff --git a/client/src/components/controlpointslayer/ControlPointsLayer.tsx b/client/src/components/controlpointslayer/ControlPointsLayer.tsx index cea477ee..a4e0de15 100644 --- a/client/src/components/controlpointslayer/ControlPointsLayer.tsx +++ b/client/src/components/controlpointslayer/ControlPointsLayer.tsx @@ -9,7 +9,7 @@ export default function ControlPointsLayer() { {Object.values(controlPoints.controlPoints).map((controlPoint) => { return ( - + ); })} diff --git a/game/campaignloader/campaignairwingconfig.py b/game/campaignloader/campaignairwingconfig.py index e64e75b4..8065db90 100644 --- a/game/campaignloader/campaignairwingconfig.py +++ b/game/campaignloader/campaignairwingconfig.py @@ -1,9 +1,8 @@ from __future__ import annotations -import logging from collections import defaultdict from dataclasses import dataclass -from typing import Any, TYPE_CHECKING, Union, Optional +from typing import Any, Optional, TYPE_CHECKING, Union from game.ato.flighttype import FlightType from game.theater.controlpoint import ControlPoint @@ -67,7 +66,7 @@ class CampaignAirWingConfig: by_location: dict[ControlPoint, list[SquadronConfig]] = defaultdict(list) for base_id, squadron_configs in data.items(): if isinstance(base_id, int): - base = theater.find_control_point_by_id(base_id) + base = theater.find_control_point_by_airport_id(base_id) else: base = theater.control_point_named(base_id) diff --git a/game/campaignloader/mizcampaignloader.py b/game/campaignloader/mizcampaignloader.py index cab5ba8d..9384789a 100644 --- a/game/campaignloader/mizcampaignloader.py +++ b/game/campaignloader/mizcampaignloader.py @@ -3,23 +3,23 @@ from __future__ import annotations import itertools from functools import cached_property from pathlib import Path -from typing import Iterator, List, Dict, Tuple, TYPE_CHECKING +from typing import Iterator, List, TYPE_CHECKING, Tuple +from uuid import UUID from dcs import Mission from dcs.countries import CombinedJointTaskForcesBlue, CombinedJointTaskForcesRed from dcs.country import Country from dcs.planes import F_15C -from dcs.ships import Stennis, LHA_Tarawa, HandyWind, USS_Arleigh_Burke_IIa +from dcs.ships import HandyWind, LHA_Tarawa, Stennis, USS_Arleigh_Burke_IIa from dcs.statics import Fortification, Warehouse from dcs.terrain import Airport -from dcs.unitgroup import PlaneGroup, ShipGroup, VehicleGroup, StaticGroup -from dcs.vehicles import Armor, Unarmed, MissilesSS, AirDefence +from dcs.unitgroup import PlaneGroup, ShipGroup, StaticGroup, VehicleGroup +from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed from game.point_with_heading import PointWithHeading from game.positioned import Positioned from game.profiling import logged_duration from game.scenery_group import SceneryGroup -from game.utils import Distance, meters, Heading from game.theater.controlpoint import ( Airfield, Carrier, @@ -28,6 +28,7 @@ from game.theater.controlpoint import ( Lha, OffMapSpawn, ) +from game.utils import Distance, Heading, meters if TYPE_CHECKING: from game.theater.conflicttheater import ConflictTheater @@ -94,7 +95,6 @@ class MizCampaignLoader: self.mission = Mission() with logged_duration("Loading miz"): self.mission.load_file(str(miz)) - self.control_point_id = itertools.count(1000) # If there are no red carriers there usually aren't red units. Make sure # both countries are initialized so we don't have to deal with None. @@ -238,7 +238,7 @@ class MizCampaignLoader: return SceneryGroup.from_trigger_zones(self.mission.triggers._zones) @cached_property - def control_points(self) -> Dict[int, ControlPoint]: + def control_points(self) -> dict[UUID, ControlPoint]: control_points = {} for airport in self.mission.terrain.airport_list(): if airport.is_blue() or airport.is_red(): @@ -248,38 +248,20 @@ class MizCampaignLoader: for blue in (False, True): for group in self.off_map_spawns(blue): control_point = OffMapSpawn( - next(self.control_point_id), - str(group.name), - group.position, - starts_blue=blue, + str(group.name), group.position, starts_blue=blue ) control_point.captured_invert = group.late_activation control_points[control_point.id] = control_point for ship in self.carriers(blue): - control_point = Carrier( - ship.name, - ship.position, - next(self.control_point_id), - starts_blue=blue, - ) + control_point = Carrier(ship.name, ship.position, starts_blue=blue) control_point.captured_invert = ship.late_activation control_points[control_point.id] = control_point for ship in self.lhas(blue): - control_point = Lha( - ship.name, - ship.position, - next(self.control_point_id), - starts_blue=blue, - ) + control_point = Lha(ship.name, ship.position, starts_blue=blue) control_point.captured_invert = ship.late_activation control_points[control_point.id] = control_point for fob in self.fobs(blue): - control_point = Fob( - str(fob.name), - fob.position, - next(self.control_point_id), - starts_blue=blue, - ) + control_point = Fob(str(fob.name), fob.position, starts_blue=blue) control_point.captured_invert = fob.late_activation control_points[control_point.id] = control_point diff --git a/game/debriefing.py b/game/debriefing.py index 817f0288..57637faa 100644 --- a/game/debriefing.py +++ b/game/debriefing.py @@ -12,6 +12,7 @@ from typing import ( TYPE_CHECKING, Union, ) +from uuid import UUID from game.dcs.aircrafttype import AircraftType from game.dcs.groundunittype import GroundUnitType @@ -337,8 +338,10 @@ class Debriefing: seen = set() captures = [] for capture in reversed(self.state_data.base_capture_events): - cp_id_str, new_owner_id_str, _name = capture.split("||") - cp_id = int(cp_id_str) + # The ID string in the JSON file will be an airport ID for airport captures + # but will be a UUID for all other types, since DCS doesn't know the UUIDs + # for the captured FOBs. + cp_id, new_owner_id_str, _name = capture.split("||") # Only the most recent capture event matters. if cp_id in seen: @@ -346,7 +349,12 @@ class Debriefing: seen.add(cp_id) try: - control_point = self.game.theater.find_control_point_by_id(cp_id) + control_point = self.game.theater.find_control_point_by_airport_id( + int(cp_id) + ) + except ValueError: + # The CP ID could not be converted to an int, so it's a UUID. + control_point = self.game.theater.find_control_point_by_id(UUID(cp_id)) except KeyError: # Captured base is not a part of the campaign. This happens when neutral # bases are near the conflict. Nothing to do. diff --git a/game/game.py b/game/game.py index a81e686b..8e7f4aae 100644 --- a/game/game.py +++ b/game/game.py @@ -7,6 +7,7 @@ from collections.abc import Iterator from datetime import date, datetime, timedelta from enum import Enum from typing import Any, List, TYPE_CHECKING, Type, Union, cast +from uuid import UUID from dcs.countries import Switzerland, USAFAggressors, UnitedNationsPeacekeepers from dcs.country import Country @@ -105,7 +106,7 @@ class Game: self.date = date(start_date.year, start_date.month, start_date.day) self.game_stats = GameStats() self.notes = "" - self.ground_planners: dict[int, GroundPlanner] = {} + self.ground_planners: dict[UUID, GroundPlanner] = {} self.informations: list[Information] = [] self.message("Game Start", "-" * 40) # Culling Zones are for areas around points of interest that contain things we may not wish to cull. diff --git a/game/ground_forces/ai_ground_planner.py b/game/ground_forces/ai_ground_planner.py index 0c4355e4..89120ebc 100644 --- a/game/ground_forces/ai_ground_planner.py +++ b/game/ground_forces/ai_ground_planner.py @@ -3,7 +3,8 @@ from __future__ import annotations import logging import random from enum import Enum -from typing import Dict, List, TYPE_CHECKING +from typing import List, TYPE_CHECKING +from uuid import UUID from game.data.units import UnitClass from game.dcs.groundunittype import GroundUnitType @@ -82,7 +83,7 @@ class GroundPlanner: self.shorad_groups: List[CombatGroup] = [] self.recon_groups: List[CombatGroup] = [] - self.units_per_cp: Dict[int, List[CombatGroup]] = {} + self.units_per_cp: dict[UUID, List[CombatGroup]] = {} for cp in self.connected_enemy_cp: self.units_per_cp[cp.id] = [] self.reserve: List[CombatGroup] = [] diff --git a/game/missiongenerator/aircraft/flightgroupspawner.py b/game/missiongenerator/aircraft/flightgroupspawner.py index 127299da..5bc3bbd1 100644 --- a/game/missiongenerator/aircraft/flightgroupspawner.py +++ b/game/missiongenerator/aircraft/flightgroupspawner.py @@ -1,4 +1,3 @@ -import copy import logging import random from typing import Any, Union @@ -8,7 +7,6 @@ from dcs.country import Country from dcs.mapping import Vector2 from dcs.mission import StartType as DcsStartType from dcs.planes import F_14A, Su_33 -from dcs.point import PointAction from dcs.ships import KUZNECOW from dcs.terrain import Airport, NoParkingSlotError from dcs.unitgroup import FlyingGroup, ShipGroup, StaticGroup @@ -16,10 +14,10 @@ from dcs.unitgroup import FlyingGroup, ShipGroup, StaticGroup from game.ato import Flight from game.ato.flightstate import InFlight from game.ato.starttype import StartType +from game.ato.traveltime import GroundSpeed from game.naming import namegen from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint, OffMapSpawn from game.utils import feet, meters -from game.ato.traveltime import GroundSpeed WARM_START_HELI_ALT = meters(500) WARM_START_ALTITUDE = meters(3000) @@ -79,12 +77,11 @@ class FlightGroupSpawner: return self.generate_mid_mission() def create_idle_aircraft(self) -> FlyingGroup[Any]: - assert isinstance(self.flight.squadron.location, Airfield) + airport = self.flight.squadron.location.dcs_airport + assert airport is not None group = self._generate_at_airport( - name=namegen.next_aircraft_name( - self.country, self.flight.departure.id, self.flight - ), - airport=self.flight.squadron.location.airport, + name=namegen.next_aircraft_name(self.country, self.flight), + airport=airport, ) group.uncontrolled = True @@ -95,9 +92,7 @@ class FlightGroupSpawner: return self.flight.state.spawn_type def generate_flight_at_departure(self) -> FlyingGroup[Any]: - name = namegen.next_aircraft_name( - self.country, self.flight.departure.id, self.flight - ) + name = namegen.next_aircraft_name(self.country, self.flight) cp = self.flight.departure try: if self.start_type is StartType.IN_FLIGHT: @@ -135,9 +130,7 @@ class FlightGroupSpawner: def generate_mid_mission(self) -> FlyingGroup[Any]: assert isinstance(self.flight.state, InFlight) - name = namegen.next_aircraft_name( - self.country, self.flight.departure.id, self.flight - ) + name = namegen.next_aircraft_name(self.country, self.flight) speed = self.flight.state.estimate_speed() pos = self.flight.state.estimate_position() pos += Vector2(random.randint(100, 1000), random.randint(100, 1000)) diff --git a/game/missiongenerator/aircraft/waypoints/waypointgenerator.py b/game/missiongenerator/aircraft/waypoints/waypointgenerator.py index c3adabc9..03c34e75 100644 --- a/game/missiongenerator/aircraft/waypoints/waypointgenerator.py +++ b/game/missiongenerator/aircraft/waypoints/waypointgenerator.py @@ -18,7 +18,6 @@ from game.ato.flightwaypointtype import FlightWaypointType from game.ato.starttype import StartType from game.missiongenerator.airsupport import AirSupport from game.settings import Settings -from game.theater import ControlPointType from game.utils import pairwise from .baiingress import BaiIngressBuilder from .cargostop import CargoStopBuilder @@ -27,7 +26,6 @@ from .deadingress import DeadIngressBuilder from .default import DefaultWaypointBuilder from .holdpoint import HoldPointBuilder from .joinpoint import JoinPointBuilder -from .splitpoint import SplitPointBuilder from .landingpoint import LandingPointBuilder from .ocaaircraftingress import OcaAircraftIngressBuilder from .ocarunwayingress import OcaRunwayIngressBuilder @@ -36,6 +34,7 @@ from .racetrack import RaceTrackBuilder from .racetrackend import RaceTrackEndBuilder from .refuel import RefuelPointBuilder from .seadingress import SeadIngressBuilder +from .splitpoint import SplitPointBuilder from .strikeingress import StrikeIngressBuilder from .sweepingress import SweepIngressBuilder @@ -213,14 +212,12 @@ class WaypointGenerator: def prevent_spawn_at_hostile_airbase(self, trigger: TriggerRule) -> None: # Prevent delayed flights from spawning at airbases if they were # captured before they've spawned. - if self.flight.from_cp.cptype != ControlPointType.AIRBASE: - return - - trigger.add_condition( - CoalitionHasAirdrome( - self.flight.squadron.coalition.coalition_id, self.flight.from_cp.id + if (airport := self.flight.departure.dcs_airport) is not None: + trigger.add_condition( + CoalitionHasAirdrome( + self.flight.squadron.coalition.coalition_id, airport.id + ) ) - ) def set_startup_time(self, delay: timedelta) -> None: # Uncontrolled causes the AI unit to spawn, but not begin startup. diff --git a/game/missiongenerator/flotgenerator.py b/game/missiongenerator/flotgenerator.py index ceaa25a3..6179127c 100644 --- a/game/missiongenerator/flotgenerator.py +++ b/game/missiongenerator/flotgenerator.py @@ -227,7 +227,7 @@ class FlotGenerator: )[0] self.mission.vehicle_group( side, - namegen.next_infantry_name(side, cp.id, u), + namegen.next_infantry_name(side, u), u.dcs_unit_type, position=infantry_position, group_size=1, @@ -252,7 +252,7 @@ class FlotGenerator: ) self.mission.vehicle_group( side, - namegen.next_infantry_name(side, cp.id, units[0]), + namegen.next_infantry_name(side, units[0]), units[0].dcs_unit_type, position=infantry_position, group_size=1, @@ -264,7 +264,7 @@ class FlotGenerator: position = infantry_position.random_point_within(55, 5) self.mission.vehicle_group( side, - namegen.next_infantry_name(side, cp.id, unit), + namegen.next_infantry_name(side, unit), unit.dcs_unit_type, position=position, group_size=1, @@ -782,7 +782,7 @@ class FlotGenerator: group = self.mission.vehicle_group( side, - namegen.next_unit_name(side, cp.id, unit_type), + namegen.next_unit_name(side, unit_type), unit_type.dcs_unit_type, position=at, group_size=count, diff --git a/game/missiongenerator/triggergenerator.py b/game/missiongenerator/triggergenerator.py index 3983b798..70561e30 100644 --- a/game/missiongenerator/triggergenerator.py +++ b/game/missiongenerator/triggergenerator.py @@ -2,22 +2,18 @@ from __future__ import annotations from typing import TYPE_CHECKING -from dcs.action import MarkToAll, SetFlag, DoScript, ClearFlag +from dcs.action import ClearFlag, DoScript, MarkToAll, SetFlag from dcs.condition import ( - TimeAfter, AllOfCoalitionOutsideZone, - PartOfCoalitionInZone, FlagIsFalse, FlagIsTrue, + PartOfCoalitionInZone, + TimeAfter, ) from dcs.mission import Mission from dcs.task import Option from dcs.translation import String -from dcs.triggers import ( - Event, - TriggerOnce, - TriggerCondition, -) +from dcs.triggers import Event, TriggerCondition, TriggerOnce from dcs.unit import Skill from game.theater import Airfield @@ -61,9 +57,12 @@ class TriggerGenerator: """ # Empty neutrals airports - cp_ids = [cp.id for cp in self.game.theater.controlpoints] + airfields = [ + cp for cp in self.game.theater.controlpoints if isinstance(cp, Airfield) + ] + airport_ids = {cp.airport.id for cp in airfields} for airport in self.mission.terrain.airport_list(): - if airport.id not in cp_ids: + if airport.id not in airport_ids: airport.unlimited_fuel = False airport.unlimited_munitions = False airport.unlimited_aircrafts = False @@ -76,21 +75,20 @@ class TriggerGenerator: airport.operating_level_fuel = 0 for airport in self.mission.terrain.airport_list(): - if airport.id not in cp_ids: + if airport.id not in airport_ids: airport.unlimited_fuel = True airport.unlimited_munitions = True airport.unlimited_aircrafts = True - for cp in self.game.theater.controlpoints: - if isinstance(cp, Airfield): - cp_airport = self.mission.terrain.airport_by_id(cp.airport.id) - if cp_airport is None: - raise RuntimeError( - f"Could not find {cp.airport.name} in the mission" - ) - cp_airport.set_coalition( - cp.captured and player_coalition or enemy_coalition + for airfield in airfields: + cp_airport = self.mission.terrain.airport_by_id(airfield.airport.id) + if cp_airport is None: + raise RuntimeError( + f"Could not find {airfield.airport.name} in the mission" ) + cp_airport.set_coalition( + airfield.captured and player_coalition or enemy_coalition + ) def _set_skill(self, player_coalition: str, enemy_coalition: str) -> None: """ diff --git a/game/naming.py b/game/naming.py index f0ceb107..34e9afce 100644 --- a/game/naming.py +++ b/game/naming.py @@ -2,7 +2,7 @@ from __future__ import annotations import random import time -from typing import List, Any, TYPE_CHECKING +from typing import Any, List, TYPE_CHECKING from dcs.country import Country @@ -474,45 +474,27 @@ class NameGenerator: cls.cargo_ship_number = 0 @classmethod - def next_aircraft_name( - cls, country: Country, parent_base_id: int, flight: Flight - ) -> str: + def next_aircraft_name(cls, country: Country, flight: Flight) -> str: cls.aircraft_number += 1 - try: - if flight.custom_name: - name_str = flight.custom_name - else: - name_str = "{} {}".format( - flight.package.target.name, flight.flight_type - ) - except AttributeError: # Here to maintain save compatibility with 2.3 + if flight.custom_name: + name_str = flight.custom_name + else: name_str = "{} {}".format(flight.package.target.name, flight.flight_type) - return "{}|{}|{}|{}|{}|".format( - name_str, - country.id, - cls.aircraft_number, - parent_base_id, - flight.unit_type.name, + return "{}|{}|{}|{}|".format( + name_str, country.id, cls.aircraft_number, flight.unit_type.name ) @classmethod - def next_unit_name( - cls, country: Country, parent_base_id: int, unit_type: UnitType[Any] - ) -> str: + def next_unit_name(cls, country: Country, unit_type: UnitType[Any]) -> str: cls.number += 1 - return "unit|{}|{}|{}|{}|".format( - country.id, cls.number, parent_base_id, unit_type.name - ) + return "unit|{}|{}|{}|".format(country.id, cls.number, unit_type.name) @classmethod - def next_infantry_name( - cls, country: Country, parent_base_id: int, unit_type: UnitType[Any] - ) -> str: + def next_infantry_name(cls, country: Country, unit_type: UnitType[Any]) -> str: cls.infantry_number += 1 - return "infantry|{}|{}|{}|{}|".format( + return "infantry|{}|{}|{}|".format( country.id, cls.infantry_number, - parent_base_id, unit_type.name, ) diff --git a/game/server/controlpoints/models.py b/game/server/controlpoints/models.py index b21314b6..a0a22e76 100644 --- a/game/server/controlpoints/models.py +++ b/game/server/controlpoints/models.py @@ -1,6 +1,7 @@ from __future__ import annotations from typing import TYPE_CHECKING +from uuid import UUID from pydantic import BaseModel @@ -12,7 +13,7 @@ if TYPE_CHECKING: class ControlPointJs(BaseModel): - id: int + id: UUID name: str blue: bool position: LeafletPoint diff --git a/game/server/controlpoints/routes.py b/game/server/controlpoints/routes.py index 0e644b3f..d586725e 100644 --- a/game/server/controlpoints/routes.py +++ b/game/server/controlpoints/routes.py @@ -1,3 +1,5 @@ +from uuid import UUID + from dcs import Point from dcs.mapping import LatLng from fastapi import APIRouter, Body, Depends, HTTPException, status @@ -25,7 +27,7 @@ def list_control_points( "/{cp_id}", operation_id="get_control_point_by_id", response_model=ControlPointJs ) def get_control_point( - cp_id: int, game: Game = Depends(GameContext.require) + cp_id: UUID, game: Game = Depends(GameContext.require) ) -> ControlPointJs: cp = game.theater.find_control_point_by_id(cp_id) if cp is None: @@ -42,7 +44,7 @@ def get_control_point( response_model=bool, ) def destination_in_range( - cp_id: int, lat: float, lng: float, game: Game = Depends(GameContext.require) + cp_id: UUID, lat: float, lng: float, game: Game = Depends(GameContext.require) ) -> bool: cp = game.theater.find_control_point_by_id(cp_id) if cp is None: @@ -61,7 +63,7 @@ def destination_in_range( status_code=status.HTTP_204_NO_CONTENT, ) def set_destination( - cp_id: int, + cp_id: UUID, destination: LeafletPoint = Body(..., title="destination"), game: Game = Depends(GameContext.require), ) -> None: @@ -96,7 +98,7 @@ def set_destination( operation_id="clear_control_point_destination", status_code=status.HTTP_204_NO_CONTENT, ) -def cancel_travel(cp_id: int, game: Game = Depends(GameContext.require)) -> None: +def cancel_travel(cp_id: UUID, game: Game = Depends(GameContext.require)) -> None: cp = game.theater.find_control_point_by_id(cp_id) if cp is None: raise HTTPException( diff --git a/game/server/eventstream/models.py b/game/server/eventstream/models.py index a9eb35a6..e0e15288 100644 --- a/game/server/eventstream/models.py +++ b/game/server/eventstream/models.py @@ -32,7 +32,7 @@ class GameUpdateEventsJs(BaseModel): updated_front_lines: set[UUID] deleted_front_lines: set[UUID] updated_tgos: set[UUID] - updated_control_points: set[int] + updated_control_points: set[UUID] reset_on_map_center: LeafletPoint | None game_unloaded: bool new_turn: bool diff --git a/game/server/qt/routes.py b/game/server/qt/routes.py index ac08b261..919ca42a 100644 --- a/game/server/qt/routes.py +++ b/game/server/qt/routes.py @@ -53,7 +53,7 @@ def show_tgo_info( status_code=status.HTTP_204_NO_CONTENT, ) def new_cp_package( - cp_id: int, + cp_id: UUID, game: Game = Depends(GameContext.require), qt: QtCallbacks = Depends(QtContext.get), ) -> None: @@ -72,7 +72,7 @@ def new_cp_package( status_code=status.HTTP_204_NO_CONTENT, ) def show_control_point_info( - cp_id: int, + cp_id: UUID, game: Game = Depends(GameContext.require), qt: QtCallbacks = Depends(QtContext.get), ) -> None: diff --git a/game/sim/gameupdateevents.py b/game/sim/gameupdateevents.py index 850850d6..9c1e065e 100644 --- a/game/sim/gameupdateevents.py +++ b/game/sim/gameupdateevents.py @@ -33,7 +33,7 @@ class GameUpdateEvents: updated_front_lines: set[UUID] = field(default_factory=set) deleted_front_lines: set[UUID] = field(default_factory=set) updated_tgos: set[UUID] = field(default_factory=set) - updated_control_points: set[int] = field(default_factory=set) + updated_control_points: set[UUID] = field(default_factory=set) reset_on_map_center: LatLng | None = None game_unloaded: bool = False new_turn: bool = False diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 775d010b..b1b6b8a5 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -5,6 +5,7 @@ import math from dataclasses import dataclass from pathlib import Path from typing import Dict, Iterator, List, Optional, TYPE_CHECKING, Tuple +from uuid import UUID from dcs.mapping import Point from dcs.terrain import ( @@ -203,11 +204,17 @@ class ConflictTheater: assert closest_red is not None return closest_blue, closest_red - def find_control_point_by_id(self, id: int) -> ControlPoint: + def find_control_point_by_id(self, cp_id: UUID) -> ControlPoint: for i in self.controlpoints: - if i.id == id: + if i.id == cp_id: return i - raise KeyError(f"Cannot find ControlPoint with ID {id}") + raise KeyError(f"Cannot find ControlPoint with ID {cp_id}") + + def find_control_point_by_airport_id(self, airport_id: int) -> ControlPoint: + for cp in self.controlpoints: + if cp.dcs_airport is not None and cp.dcs_airport.id == airport_id: + return cp + raise KeyError(f"Cannot find ControlPoint with airport ID {airport_id}") def control_point_named(self, name: str) -> ControlPoint: for cp in self.controlpoints: diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index a955a9e9..7e1157d0 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -4,6 +4,7 @@ import heapq import itertools import logging import math +import uuid from abc import ABC, abstractmethod from collections import defaultdict from dataclasses import dataclass, field @@ -21,6 +22,7 @@ from typing import ( TYPE_CHECKING, Tuple, ) +from uuid import UUID from dcs.mapping import Point from dcs.ships import Forrestal, KUZNECOW, LHA_Tarawa, Stennis, Type_071 @@ -294,7 +296,6 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC): # TODO: cptype is obsolete. def __init__( self, - cp_id: int, name: str, position: Point, at: StartingPosition, @@ -302,8 +303,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC): cptype: ControlPointType = ControlPointType.AIRBASE, ) -> None: super().__init__(name, position) - # TODO: Should be Airbase specific. - self.id = cp_id + self.id = uuid.uuid4() self.full_name = name self.at = at self.starts_blue = starts_blue @@ -321,7 +321,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC): self.base: Base = Base() self.cptype = cptype # TODO: Should be Airbase specific. - self.stances: Dict[int, CombatStance] = {} + self.stances: dict[UUID, CombatStance] = {} from ..groundunitorders import GroundUnitOrders self.ground_unit_orders = GroundUnitOrders(self) @@ -334,6 +334,10 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC): def __repr__(self) -> str: return f"<{self.__class__}: {self.name}>" + @property + def dcs_airport(self) -> Airport | None: + return None + @property def coalition(self) -> Coalition: if self._coalition is None: @@ -956,7 +960,6 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC): class Airfield(ControlPoint): def __init__(self, airport: Airport, starts_blue: bool) -> None: super().__init__( - airport.id, airport.name, airport.position, airport, @@ -966,6 +969,10 @@ class Airfield(ControlPoint): self.airport = airport self._runway_status = RunwayStatus() + @property + def dcs_airport(self) -> Airport: + return self.airport + @property def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]: return SymbolSet.LAND_INSTALLATIONS, LandInstallationEntity.AIPORT_AIR_BASE @@ -1142,14 +1149,9 @@ class NavalControlPoint(ControlPoint, ABC): class Carrier(NavalControlPoint): - def __init__(self, name: str, at: Point, cp_id: int, starts_blue: bool): + def __init__(self, name: str, at: Point, starts_blue: bool): super().__init__( - cp_id, - name, - at, - at, - starts_blue, - cptype=ControlPointType.AIRCRAFT_CARRIER_GROUP, + name, at, at, starts_blue, cptype=ControlPointType.AIRCRAFT_CARRIER_GROUP ) @property @@ -1186,15 +1188,8 @@ class Carrier(NavalControlPoint): class Lha(NavalControlPoint): - def __init__(self, name: str, at: Point, cp_id: int, starts_blue: bool): - super().__init__( - cp_id, - name, - at, - at, - starts_blue, - cptype=ControlPointType.LHA_GROUP, - ) + def __init__(self, name: str, at: Point, starts_blue: bool): + super().__init__(name, at, at, starts_blue, cptype=ControlPointType.LHA_GROUP) @property def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]: @@ -1223,14 +1218,9 @@ class OffMapSpawn(ControlPoint): def runway_is_operational(self) -> bool: return True - def __init__(self, cp_id: int, name: str, position: Point, starts_blue: bool): + def __init__(self, name: str, position: Point, starts_blue: bool): super().__init__( - cp_id, - name, - position, - position, - starts_blue, - cptype=ControlPointType.OFF_MAP, + name, position, position, starts_blue, cptype=ControlPointType.OFF_MAP ) @property @@ -1287,15 +1277,8 @@ class OffMapSpawn(ControlPoint): class Fob(ControlPoint): - def __init__(self, name: str, at: Point, cp_id: int, starts_blue: bool): - super().__init__( - cp_id, - name, - at, - at, - starts_blue, - cptype=ControlPointType.FOB, - ) + def __init__(self, name: str, at: Point, starts_blue: bool): + super().__init__(name, at, at, starts_blue, cptype=ControlPointType.FOB) self.name = name @property diff --git a/qt_ui/windows/basemenu/QBaseMenu2.py b/qt_ui/windows/basemenu/QBaseMenu2.py index b6d7b343..668c94ac 100644 --- a/qt_ui/windows/basemenu/QBaseMenu2.py +++ b/qt_ui/windows/basemenu/QBaseMenu2.py @@ -39,12 +39,6 @@ class QBaseMenu2(QDialog): self.game_model = game_model self.objectName = "menuDialogue" - try: - game = self.game_model.game - self.airport = game.theater.terrain.airport_by_id(self.cp.id) - except: - self.airport = None - if self.cp.captured: self.deliveryEvent = None