Replace CP integer ID with a UUID.

This allows unique identification across saves. The front-end needs to
be able to differentiate the first carrier in game A and the first
carrier in game B, but because carriers (and other non-airfield CPs) are
assigned IDs sequentially, collisions were to be expected. The front-end
can't tell the difference between a reloaded game and a new turn, so we
need to ensure different IDs across games.

This is a handy cleanup anyway, since callers constructing CPs no longer
need to manually track the CP ID counter.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2078.
This commit is contained in:
Dan Albert 2022-03-20 15:11:58 -07:00
parent 941a7d441c
commit 039ac9ec74
21 changed files with 127 additions and 179 deletions

View File

@ -208,25 +208,25 @@ export type ListControlPointsApiArg = void;
export type GetControlPointByIdApiResponse = export type GetControlPointByIdApiResponse =
/** status 200 Successful Response */ ControlPoint; /** status 200 Successful Response */ ControlPoint;
export type GetControlPointByIdApiArg = { export type GetControlPointByIdApiArg = {
cpId: number; cpId: string;
}; };
export type ControlPointDestinationInRangeApiResponse = export type ControlPointDestinationInRangeApiResponse =
/** status 200 Successful Response */ boolean; /** status 200 Successful Response */ boolean;
export type ControlPointDestinationInRangeApiArg = { export type ControlPointDestinationInRangeApiArg = {
cpId: number; cpId: string;
lat: number; lat: number;
lng: number; lng: number;
}; };
export type SetControlPointDestinationApiResponse = export type SetControlPointDestinationApiResponse =
/** status 204 Successful Response */ undefined; /** status 204 Successful Response */ undefined;
export type SetControlPointDestinationApiArg = { export type SetControlPointDestinationApiArg = {
cpId: number; cpId: string;
body: LatLng; body: LatLng;
}; };
export type ClearControlPointDestinationApiResponse = export type ClearControlPointDestinationApiResponse =
/** status 204 Successful Response */ undefined; /** status 204 Successful Response */ undefined;
export type ClearControlPointDestinationApiArg = { export type ClearControlPointDestinationApiArg = {
cpId: number; cpId: string;
}; };
export type GetDebugHoldZonesApiResponse = export type GetDebugHoldZonesApiResponse =
/** status 200 Successful Response */ HoldZones; /** status 200 Successful Response */ HoldZones;
@ -302,12 +302,12 @@ export type OpenTgoInfoDialogApiArg = {
export type OpenNewControlPointPackageDialogApiResponse = export type OpenNewControlPointPackageDialogApiResponse =
/** status 204 Successful Response */ undefined; /** status 204 Successful Response */ undefined;
export type OpenNewControlPointPackageDialogApiArg = { export type OpenNewControlPointPackageDialogApiArg = {
cpId: number; cpId: string;
}; };
export type OpenControlPointInfoDialogApiResponse = export type OpenControlPointInfoDialogApiResponse =
/** status 204 Successful Response */ undefined; /** status 204 Successful Response */ undefined;
export type OpenControlPointInfoDialogApiArg = { export type OpenControlPointInfoDialogApiArg = {
cpId: number; cpId: string;
}; };
export type ListSupplyRoutesApiResponse = export type ListSupplyRoutesApiResponse =
/** status 200 Successful Response */ SupplyRoute[]; /** status 200 Successful Response */ SupplyRoute[];
@ -335,7 +335,7 @@ export type LatLng = {
lng: number; lng: number;
}; };
export type ControlPoint = { export type ControlPoint = {
id: number; id: string;
name: string; name: string;
blue: boolean; blue: boolean;
position: LatLng; position: LatLng;

View File

@ -4,7 +4,7 @@ import { ControlPoint } from "./liberationApi";
import { PayloadAction, createSlice } from "@reduxjs/toolkit"; import { PayloadAction, createSlice } from "@reduxjs/toolkit";
interface ControlPointsState { interface ControlPointsState {
controlPoints: { [key: number]: ControlPoint }; controlPoints: { [key: string]: ControlPoint };
} }
const initialState: ControlPointsState = { const initialState: ControlPointsState = {
@ -23,7 +23,7 @@ export const controlPointsSlice = createSlice({
extraReducers: (builder) => { extraReducers: (builder) => {
builder.addCase(gameLoaded, (state, action) => { builder.addCase(gameLoaded, (state, action) => {
state.controlPoints = action.payload.control_points.reduce( state.controlPoints = action.payload.control_points.reduce(
(acc: { [key: number]: ControlPoint }, curr) => { (acc: { [key: string]: ControlPoint }, curr) => {
acc[curr.id] = curr; acc[curr.id] = curr;
return acc; return acc;
}, },

View File

@ -9,7 +9,7 @@ export default function ControlPointsLayer() {
<LayerGroup> <LayerGroup>
{Object.values(controlPoints.controlPoints).map((controlPoint) => { {Object.values(controlPoints.controlPoints).map((controlPoint) => {
return ( return (
<ControlPoint key={controlPoint.name} controlPoint={controlPoint} /> <ControlPoint key={controlPoint.id} controlPoint={controlPoint} />
); );
})} })}
</LayerGroup> </LayerGroup>

View File

@ -1,9 +1,8 @@
from __future__ import annotations from __future__ import annotations
import logging
from collections import defaultdict from collections import defaultdict
from dataclasses import dataclass 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.ato.flighttype import FlightType
from game.theater.controlpoint import ControlPoint from game.theater.controlpoint import ControlPoint
@ -67,7 +66,7 @@ class CampaignAirWingConfig:
by_location: dict[ControlPoint, list[SquadronConfig]] = defaultdict(list) by_location: dict[ControlPoint, list[SquadronConfig]] = defaultdict(list)
for base_id, squadron_configs in data.items(): for base_id, squadron_configs in data.items():
if isinstance(base_id, int): 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: else:
base = theater.control_point_named(base_id) base = theater.control_point_named(base_id)

View File

@ -3,23 +3,23 @@ from __future__ import annotations
import itertools import itertools
from functools import cached_property from functools import cached_property
from pathlib import Path 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 import Mission
from dcs.countries import CombinedJointTaskForcesBlue, CombinedJointTaskForcesRed from dcs.countries import CombinedJointTaskForcesBlue, CombinedJointTaskForcesRed
from dcs.country import Country from dcs.country import Country
from dcs.planes import F_15C 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.statics import Fortification, Warehouse
from dcs.terrain import Airport from dcs.terrain import Airport
from dcs.unitgroup import PlaneGroup, ShipGroup, VehicleGroup, StaticGroup from dcs.unitgroup import PlaneGroup, ShipGroup, StaticGroup, VehicleGroup
from dcs.vehicles import Armor, Unarmed, MissilesSS, AirDefence from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed
from game.point_with_heading import PointWithHeading from game.point_with_heading import PointWithHeading
from game.positioned import Positioned from game.positioned import Positioned
from game.profiling import logged_duration from game.profiling import logged_duration
from game.scenery_group import SceneryGroup from game.scenery_group import SceneryGroup
from game.utils import Distance, meters, Heading
from game.theater.controlpoint import ( from game.theater.controlpoint import (
Airfield, Airfield,
Carrier, Carrier,
@ -28,6 +28,7 @@ from game.theater.controlpoint import (
Lha, Lha,
OffMapSpawn, OffMapSpawn,
) )
from game.utils import Distance, Heading, meters
if TYPE_CHECKING: if TYPE_CHECKING:
from game.theater.conflicttheater import ConflictTheater from game.theater.conflicttheater import ConflictTheater
@ -94,7 +95,6 @@ class MizCampaignLoader:
self.mission = Mission() self.mission = Mission()
with logged_duration("Loading miz"): with logged_duration("Loading miz"):
self.mission.load_file(str(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 # 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. # 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) return SceneryGroup.from_trigger_zones(self.mission.triggers._zones)
@cached_property @cached_property
def control_points(self) -> Dict[int, ControlPoint]: def control_points(self) -> dict[UUID, ControlPoint]:
control_points = {} control_points = {}
for airport in self.mission.terrain.airport_list(): for airport in self.mission.terrain.airport_list():
if airport.is_blue() or airport.is_red(): if airport.is_blue() or airport.is_red():
@ -248,38 +248,20 @@ class MizCampaignLoader:
for blue in (False, True): for blue in (False, True):
for group in self.off_map_spawns(blue): for group in self.off_map_spawns(blue):
control_point = OffMapSpawn( 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_point.captured_invert = group.late_activation
control_points[control_point.id] = control_point control_points[control_point.id] = control_point
for ship in self.carriers(blue): for ship in self.carriers(blue):
control_point = Carrier( control_point = Carrier(ship.name, ship.position, starts_blue=blue)
ship.name,
ship.position,
next(self.control_point_id),
starts_blue=blue,
)
control_point.captured_invert = ship.late_activation control_point.captured_invert = ship.late_activation
control_points[control_point.id] = control_point control_points[control_point.id] = control_point
for ship in self.lhas(blue): for ship in self.lhas(blue):
control_point = Lha( control_point = Lha(ship.name, ship.position, starts_blue=blue)
ship.name,
ship.position,
next(self.control_point_id),
starts_blue=blue,
)
control_point.captured_invert = ship.late_activation control_point.captured_invert = ship.late_activation
control_points[control_point.id] = control_point control_points[control_point.id] = control_point
for fob in self.fobs(blue): for fob in self.fobs(blue):
control_point = Fob( control_point = Fob(str(fob.name), fob.position, starts_blue=blue)
str(fob.name),
fob.position,
next(self.control_point_id),
starts_blue=blue,
)
control_point.captured_invert = fob.late_activation control_point.captured_invert = fob.late_activation
control_points[control_point.id] = control_point control_points[control_point.id] = control_point

View File

@ -12,6 +12,7 @@ from typing import (
TYPE_CHECKING, TYPE_CHECKING,
Union, Union,
) )
from uuid import UUID
from game.dcs.aircrafttype import AircraftType from game.dcs.aircrafttype import AircraftType
from game.dcs.groundunittype import GroundUnitType from game.dcs.groundunittype import GroundUnitType
@ -337,8 +338,10 @@ class Debriefing:
seen = set() seen = set()
captures = [] captures = []
for capture in reversed(self.state_data.base_capture_events): for capture in reversed(self.state_data.base_capture_events):
cp_id_str, new_owner_id_str, _name = capture.split("||") # The ID string in the JSON file will be an airport ID for airport captures
cp_id = int(cp_id_str) # 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. # Only the most recent capture event matters.
if cp_id in seen: if cp_id in seen:
@ -346,7 +349,12 @@ class Debriefing:
seen.add(cp_id) seen.add(cp_id)
try: 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: except KeyError:
# Captured base is not a part of the campaign. This happens when neutral # Captured base is not a part of the campaign. This happens when neutral
# bases are near the conflict. Nothing to do. # bases are near the conflict. Nothing to do.

View File

@ -7,6 +7,7 @@ from collections.abc import Iterator
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from enum import Enum from enum import Enum
from typing import Any, List, TYPE_CHECKING, Type, Union, cast from typing import Any, List, TYPE_CHECKING, Type, Union, cast
from uuid import UUID
from dcs.countries import Switzerland, USAFAggressors, UnitedNationsPeacekeepers from dcs.countries import Switzerland, USAFAggressors, UnitedNationsPeacekeepers
from dcs.country import Country from dcs.country import Country
@ -105,7 +106,7 @@ class Game:
self.date = date(start_date.year, start_date.month, start_date.day) self.date = date(start_date.year, start_date.month, start_date.day)
self.game_stats = GameStats() self.game_stats = GameStats()
self.notes = "" self.notes = ""
self.ground_planners: dict[int, GroundPlanner] = {} self.ground_planners: dict[UUID, GroundPlanner] = {}
self.informations: list[Information] = [] self.informations: list[Information] = []
self.message("Game Start", "-" * 40) self.message("Game Start", "-" * 40)
# Culling Zones are for areas around points of interest that contain things we may not wish to cull. # Culling Zones are for areas around points of interest that contain things we may not wish to cull.

View File

@ -3,7 +3,8 @@ from __future__ import annotations
import logging import logging
import random import random
from enum import Enum 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.data.units import UnitClass
from game.dcs.groundunittype import GroundUnitType from game.dcs.groundunittype import GroundUnitType
@ -82,7 +83,7 @@ class GroundPlanner:
self.shorad_groups: List[CombatGroup] = [] self.shorad_groups: List[CombatGroup] = []
self.recon_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: for cp in self.connected_enemy_cp:
self.units_per_cp[cp.id] = [] self.units_per_cp[cp.id] = []
self.reserve: List[CombatGroup] = [] self.reserve: List[CombatGroup] = []

View File

@ -1,4 +1,3 @@
import copy
import logging import logging
import random import random
from typing import Any, Union from typing import Any, Union
@ -8,7 +7,6 @@ from dcs.country import Country
from dcs.mapping import Vector2 from dcs.mapping import Vector2
from dcs.mission import StartType as DcsStartType from dcs.mission import StartType as DcsStartType
from dcs.planes import F_14A, Su_33 from dcs.planes import F_14A, Su_33
from dcs.point import PointAction
from dcs.ships import KUZNECOW from dcs.ships import KUZNECOW
from dcs.terrain import Airport, NoParkingSlotError from dcs.terrain import Airport, NoParkingSlotError
from dcs.unitgroup import FlyingGroup, ShipGroup, StaticGroup 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 import Flight
from game.ato.flightstate import InFlight from game.ato.flightstate import InFlight
from game.ato.starttype import StartType from game.ato.starttype import StartType
from game.ato.traveltime import GroundSpeed
from game.naming import namegen from game.naming import namegen
from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint, OffMapSpawn from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint, OffMapSpawn
from game.utils import feet, meters from game.utils import feet, meters
from game.ato.traveltime import GroundSpeed
WARM_START_HELI_ALT = meters(500) WARM_START_HELI_ALT = meters(500)
WARM_START_ALTITUDE = meters(3000) WARM_START_ALTITUDE = meters(3000)
@ -79,12 +77,11 @@ class FlightGroupSpawner:
return self.generate_mid_mission() return self.generate_mid_mission()
def create_idle_aircraft(self) -> FlyingGroup[Any]: 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( group = self._generate_at_airport(
name=namegen.next_aircraft_name( name=namegen.next_aircraft_name(self.country, self.flight),
self.country, self.flight.departure.id, self.flight airport=airport,
),
airport=self.flight.squadron.location.airport,
) )
group.uncontrolled = True group.uncontrolled = True
@ -95,9 +92,7 @@ class FlightGroupSpawner:
return self.flight.state.spawn_type return self.flight.state.spawn_type
def generate_flight_at_departure(self) -> FlyingGroup[Any]: def generate_flight_at_departure(self) -> FlyingGroup[Any]:
name = namegen.next_aircraft_name( name = namegen.next_aircraft_name(self.country, self.flight)
self.country, self.flight.departure.id, self.flight
)
cp = self.flight.departure cp = self.flight.departure
try: try:
if self.start_type is StartType.IN_FLIGHT: if self.start_type is StartType.IN_FLIGHT:
@ -135,9 +130,7 @@ class FlightGroupSpawner:
def generate_mid_mission(self) -> FlyingGroup[Any]: def generate_mid_mission(self) -> FlyingGroup[Any]:
assert isinstance(self.flight.state, InFlight) assert isinstance(self.flight.state, InFlight)
name = namegen.next_aircraft_name( name = namegen.next_aircraft_name(self.country, self.flight)
self.country, self.flight.departure.id, self.flight
)
speed = self.flight.state.estimate_speed() speed = self.flight.state.estimate_speed()
pos = self.flight.state.estimate_position() pos = self.flight.state.estimate_position()
pos += Vector2(random.randint(100, 1000), random.randint(100, 1000)) pos += Vector2(random.randint(100, 1000), random.randint(100, 1000))

View File

@ -18,7 +18,6 @@ from game.ato.flightwaypointtype import FlightWaypointType
from game.ato.starttype import StartType from game.ato.starttype import StartType
from game.missiongenerator.airsupport import AirSupport from game.missiongenerator.airsupport import AirSupport
from game.settings import Settings from game.settings import Settings
from game.theater import ControlPointType
from game.utils import pairwise from game.utils import pairwise
from .baiingress import BaiIngressBuilder from .baiingress import BaiIngressBuilder
from .cargostop import CargoStopBuilder from .cargostop import CargoStopBuilder
@ -27,7 +26,6 @@ from .deadingress import DeadIngressBuilder
from .default import DefaultWaypointBuilder from .default import DefaultWaypointBuilder
from .holdpoint import HoldPointBuilder from .holdpoint import HoldPointBuilder
from .joinpoint import JoinPointBuilder from .joinpoint import JoinPointBuilder
from .splitpoint import SplitPointBuilder
from .landingpoint import LandingPointBuilder from .landingpoint import LandingPointBuilder
from .ocaaircraftingress import OcaAircraftIngressBuilder from .ocaaircraftingress import OcaAircraftIngressBuilder
from .ocarunwayingress import OcaRunwayIngressBuilder from .ocarunwayingress import OcaRunwayIngressBuilder
@ -36,6 +34,7 @@ from .racetrack import RaceTrackBuilder
from .racetrackend import RaceTrackEndBuilder from .racetrackend import RaceTrackEndBuilder
from .refuel import RefuelPointBuilder from .refuel import RefuelPointBuilder
from .seadingress import SeadIngressBuilder from .seadingress import SeadIngressBuilder
from .splitpoint import SplitPointBuilder
from .strikeingress import StrikeIngressBuilder from .strikeingress import StrikeIngressBuilder
from .sweepingress import SweepIngressBuilder from .sweepingress import SweepIngressBuilder
@ -213,14 +212,12 @@ class WaypointGenerator:
def prevent_spawn_at_hostile_airbase(self, trigger: TriggerRule) -> None: def prevent_spawn_at_hostile_airbase(self, trigger: TriggerRule) -> None:
# Prevent delayed flights from spawning at airbases if they were # Prevent delayed flights from spawning at airbases if they were
# captured before they've spawned. # captured before they've spawned.
if self.flight.from_cp.cptype != ControlPointType.AIRBASE: if (airport := self.flight.departure.dcs_airport) is not None:
return trigger.add_condition(
CoalitionHasAirdrome(
trigger.add_condition( self.flight.squadron.coalition.coalition_id, airport.id
CoalitionHasAirdrome( )
self.flight.squadron.coalition.coalition_id, self.flight.from_cp.id
) )
)
def set_startup_time(self, delay: timedelta) -> None: def set_startup_time(self, delay: timedelta) -> None:
# Uncontrolled causes the AI unit to spawn, but not begin startup. # Uncontrolled causes the AI unit to spawn, but not begin startup.

View File

@ -227,7 +227,7 @@ class FlotGenerator:
)[0] )[0]
self.mission.vehicle_group( self.mission.vehicle_group(
side, side,
namegen.next_infantry_name(side, cp.id, u), namegen.next_infantry_name(side, u),
u.dcs_unit_type, u.dcs_unit_type,
position=infantry_position, position=infantry_position,
group_size=1, group_size=1,
@ -252,7 +252,7 @@ class FlotGenerator:
) )
self.mission.vehicle_group( self.mission.vehicle_group(
side, side,
namegen.next_infantry_name(side, cp.id, units[0]), namegen.next_infantry_name(side, units[0]),
units[0].dcs_unit_type, units[0].dcs_unit_type,
position=infantry_position, position=infantry_position,
group_size=1, group_size=1,
@ -264,7 +264,7 @@ class FlotGenerator:
position = infantry_position.random_point_within(55, 5) position = infantry_position.random_point_within(55, 5)
self.mission.vehicle_group( self.mission.vehicle_group(
side, side,
namegen.next_infantry_name(side, cp.id, unit), namegen.next_infantry_name(side, unit),
unit.dcs_unit_type, unit.dcs_unit_type,
position=position, position=position,
group_size=1, group_size=1,
@ -782,7 +782,7 @@ class FlotGenerator:
group = self.mission.vehicle_group( group = self.mission.vehicle_group(
side, side,
namegen.next_unit_name(side, cp.id, unit_type), namegen.next_unit_name(side, unit_type),
unit_type.dcs_unit_type, unit_type.dcs_unit_type,
position=at, position=at,
group_size=count, group_size=count,

View File

@ -2,22 +2,18 @@ from __future__ import annotations
from typing import TYPE_CHECKING 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 ( from dcs.condition import (
TimeAfter,
AllOfCoalitionOutsideZone, AllOfCoalitionOutsideZone,
PartOfCoalitionInZone,
FlagIsFalse, FlagIsFalse,
FlagIsTrue, FlagIsTrue,
PartOfCoalitionInZone,
TimeAfter,
) )
from dcs.mission import Mission from dcs.mission import Mission
from dcs.task import Option from dcs.task import Option
from dcs.translation import String from dcs.translation import String
from dcs.triggers import ( from dcs.triggers import Event, TriggerCondition, TriggerOnce
Event,
TriggerOnce,
TriggerCondition,
)
from dcs.unit import Skill from dcs.unit import Skill
from game.theater import Airfield from game.theater import Airfield
@ -61,9 +57,12 @@ class TriggerGenerator:
""" """
# Empty neutrals airports # 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(): 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_fuel = False
airport.unlimited_munitions = False airport.unlimited_munitions = False
airport.unlimited_aircrafts = False airport.unlimited_aircrafts = False
@ -76,21 +75,20 @@ class TriggerGenerator:
airport.operating_level_fuel = 0 airport.operating_level_fuel = 0
for airport in self.mission.terrain.airport_list(): 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_fuel = True
airport.unlimited_munitions = True airport.unlimited_munitions = True
airport.unlimited_aircrafts = True airport.unlimited_aircrafts = True
for cp in self.game.theater.controlpoints: for airfield in airfields:
if isinstance(cp, Airfield): cp_airport = self.mission.terrain.airport_by_id(airfield.airport.id)
cp_airport = self.mission.terrain.airport_by_id(cp.airport.id) if cp_airport is None:
if cp_airport is None: raise RuntimeError(
raise RuntimeError( f"Could not find {airfield.airport.name} in the mission"
f"Could not find {cp.airport.name} in the mission"
)
cp_airport.set_coalition(
cp.captured and player_coalition or enemy_coalition
) )
cp_airport.set_coalition(
airfield.captured and player_coalition or enemy_coalition
)
def _set_skill(self, player_coalition: str, enemy_coalition: str) -> None: def _set_skill(self, player_coalition: str, enemy_coalition: str) -> None:
""" """

View File

@ -2,7 +2,7 @@ from __future__ import annotations
import random import random
import time import time
from typing import List, Any, TYPE_CHECKING from typing import Any, List, TYPE_CHECKING
from dcs.country import Country from dcs.country import Country
@ -474,45 +474,27 @@ class NameGenerator:
cls.cargo_ship_number = 0 cls.cargo_ship_number = 0
@classmethod @classmethod
def next_aircraft_name( def next_aircraft_name(cls, country: Country, flight: Flight) -> str:
cls, country: Country, parent_base_id: int, flight: Flight
) -> str:
cls.aircraft_number += 1 cls.aircraft_number += 1
try: if flight.custom_name:
if flight.custom_name: name_str = flight.custom_name
name_str = flight.custom_name else:
else:
name_str = "{} {}".format(
flight.package.target.name, flight.flight_type
)
except AttributeError: # Here to maintain save compatibility with 2.3
name_str = "{} {}".format(flight.package.target.name, flight.flight_type) name_str = "{} {}".format(flight.package.target.name, flight.flight_type)
return "{}|{}|{}|{}|{}|".format( return "{}|{}|{}|{}|".format(
name_str, name_str, country.id, cls.aircraft_number, flight.unit_type.name
country.id,
cls.aircraft_number,
parent_base_id,
flight.unit_type.name,
) )
@classmethod @classmethod
def next_unit_name( def next_unit_name(cls, country: Country, unit_type: UnitType[Any]) -> str:
cls, country: Country, parent_base_id: int, unit_type: UnitType[Any]
) -> str:
cls.number += 1 cls.number += 1
return "unit|{}|{}|{}|{}|".format( return "unit|{}|{}|{}|".format(country.id, cls.number, unit_type.name)
country.id, cls.number, parent_base_id, unit_type.name
)
@classmethod @classmethod
def next_infantry_name( def next_infantry_name(cls, country: Country, unit_type: UnitType[Any]) -> str:
cls, country: Country, parent_base_id: int, unit_type: UnitType[Any]
) -> str:
cls.infantry_number += 1 cls.infantry_number += 1
return "infantry|{}|{}|{}|{}|".format( return "infantry|{}|{}|{}|".format(
country.id, country.id,
cls.infantry_number, cls.infantry_number,
parent_base_id,
unit_type.name, unit_type.name,
) )

View File

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from uuid import UUID
from pydantic import BaseModel from pydantic import BaseModel
@ -12,7 +13,7 @@ if TYPE_CHECKING:
class ControlPointJs(BaseModel): class ControlPointJs(BaseModel):
id: int id: UUID
name: str name: str
blue: bool blue: bool
position: LeafletPoint position: LeafletPoint

View File

@ -1,3 +1,5 @@
from uuid import UUID
from dcs import Point from dcs import Point
from dcs.mapping import LatLng from dcs.mapping import LatLng
from fastapi import APIRouter, Body, Depends, HTTPException, status 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 "/{cp_id}", operation_id="get_control_point_by_id", response_model=ControlPointJs
) )
def get_control_point( def get_control_point(
cp_id: int, game: Game = Depends(GameContext.require) cp_id: UUID, game: Game = Depends(GameContext.require)
) -> ControlPointJs: ) -> ControlPointJs:
cp = game.theater.find_control_point_by_id(cp_id) cp = game.theater.find_control_point_by_id(cp_id)
if cp is None: if cp is None:
@ -42,7 +44,7 @@ def get_control_point(
response_model=bool, response_model=bool,
) )
def destination_in_range( 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: ) -> bool:
cp = game.theater.find_control_point_by_id(cp_id) cp = game.theater.find_control_point_by_id(cp_id)
if cp is None: if cp is None:
@ -61,7 +63,7 @@ def destination_in_range(
status_code=status.HTTP_204_NO_CONTENT, status_code=status.HTTP_204_NO_CONTENT,
) )
def set_destination( def set_destination(
cp_id: int, cp_id: UUID,
destination: LeafletPoint = Body(..., title="destination"), destination: LeafletPoint = Body(..., title="destination"),
game: Game = Depends(GameContext.require), game: Game = Depends(GameContext.require),
) -> None: ) -> None:
@ -96,7 +98,7 @@ def set_destination(
operation_id="clear_control_point_destination", operation_id="clear_control_point_destination",
status_code=status.HTTP_204_NO_CONTENT, 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) cp = game.theater.find_control_point_by_id(cp_id)
if cp is None: if cp is None:
raise HTTPException( raise HTTPException(

View File

@ -32,7 +32,7 @@ class GameUpdateEventsJs(BaseModel):
updated_front_lines: set[UUID] updated_front_lines: set[UUID]
deleted_front_lines: set[UUID] deleted_front_lines: set[UUID]
updated_tgos: set[UUID] updated_tgos: set[UUID]
updated_control_points: set[int] updated_control_points: set[UUID]
reset_on_map_center: LeafletPoint | None reset_on_map_center: LeafletPoint | None
game_unloaded: bool game_unloaded: bool
new_turn: bool new_turn: bool

View File

@ -53,7 +53,7 @@ def show_tgo_info(
status_code=status.HTTP_204_NO_CONTENT, status_code=status.HTTP_204_NO_CONTENT,
) )
def new_cp_package( def new_cp_package(
cp_id: int, cp_id: UUID,
game: Game = Depends(GameContext.require), game: Game = Depends(GameContext.require),
qt: QtCallbacks = Depends(QtContext.get), qt: QtCallbacks = Depends(QtContext.get),
) -> None: ) -> None:
@ -72,7 +72,7 @@ def new_cp_package(
status_code=status.HTTP_204_NO_CONTENT, status_code=status.HTTP_204_NO_CONTENT,
) )
def show_control_point_info( def show_control_point_info(
cp_id: int, cp_id: UUID,
game: Game = Depends(GameContext.require), game: Game = Depends(GameContext.require),
qt: QtCallbacks = Depends(QtContext.get), qt: QtCallbacks = Depends(QtContext.get),
) -> None: ) -> None:

View File

@ -33,7 +33,7 @@ class GameUpdateEvents:
updated_front_lines: set[UUID] = field(default_factory=set) updated_front_lines: set[UUID] = field(default_factory=set)
deleted_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_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 reset_on_map_center: LatLng | None = None
game_unloaded: bool = False game_unloaded: bool = False
new_turn: bool = False new_turn: bool = False

View File

@ -5,6 +5,7 @@ import math
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Dict, Iterator, List, Optional, TYPE_CHECKING, Tuple from typing import Dict, Iterator, List, Optional, TYPE_CHECKING, Tuple
from uuid import UUID
from dcs.mapping import Point from dcs.mapping import Point
from dcs.terrain import ( from dcs.terrain import (
@ -203,11 +204,17 @@ class ConflictTheater:
assert closest_red is not None assert closest_red is not None
return closest_blue, closest_red 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: for i in self.controlpoints:
if i.id == id: if i.id == cp_id:
return i 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: def control_point_named(self, name: str) -> ControlPoint:
for cp in self.controlpoints: for cp in self.controlpoints:

View File

@ -4,6 +4,7 @@ import heapq
import itertools import itertools
import logging import logging
import math import math
import uuid
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from collections import defaultdict from collections import defaultdict
from dataclasses import dataclass, field from dataclasses import dataclass, field
@ -21,6 +22,7 @@ from typing import (
TYPE_CHECKING, TYPE_CHECKING,
Tuple, Tuple,
) )
from uuid import UUID
from dcs.mapping import Point from dcs.mapping import Point
from dcs.ships import Forrestal, KUZNECOW, LHA_Tarawa, Stennis, Type_071 from dcs.ships import Forrestal, KUZNECOW, LHA_Tarawa, Stennis, Type_071
@ -294,7 +296,6 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
# TODO: cptype is obsolete. # TODO: cptype is obsolete.
def __init__( def __init__(
self, self,
cp_id: int,
name: str, name: str,
position: Point, position: Point,
at: StartingPosition, at: StartingPosition,
@ -302,8 +303,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
cptype: ControlPointType = ControlPointType.AIRBASE, cptype: ControlPointType = ControlPointType.AIRBASE,
) -> None: ) -> None:
super().__init__(name, position) super().__init__(name, position)
# TODO: Should be Airbase specific. self.id = uuid.uuid4()
self.id = cp_id
self.full_name = name self.full_name = name
self.at = at self.at = at
self.starts_blue = starts_blue self.starts_blue = starts_blue
@ -321,7 +321,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
self.base: Base = Base() self.base: Base = Base()
self.cptype = cptype self.cptype = cptype
# TODO: Should be Airbase specific. # TODO: Should be Airbase specific.
self.stances: Dict[int, CombatStance] = {} self.stances: dict[UUID, CombatStance] = {}
from ..groundunitorders import GroundUnitOrders from ..groundunitorders import GroundUnitOrders
self.ground_unit_orders = GroundUnitOrders(self) self.ground_unit_orders = GroundUnitOrders(self)
@ -334,6 +334,10 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
def __repr__(self) -> str: def __repr__(self) -> str:
return f"<{self.__class__}: {self.name}>" return f"<{self.__class__}: {self.name}>"
@property
def dcs_airport(self) -> Airport | None:
return None
@property @property
def coalition(self) -> Coalition: def coalition(self) -> Coalition:
if self._coalition is None: if self._coalition is None:
@ -956,7 +960,6 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
class Airfield(ControlPoint): class Airfield(ControlPoint):
def __init__(self, airport: Airport, starts_blue: bool) -> None: def __init__(self, airport: Airport, starts_blue: bool) -> None:
super().__init__( super().__init__(
airport.id,
airport.name, airport.name,
airport.position, airport.position,
airport, airport,
@ -966,6 +969,10 @@ class Airfield(ControlPoint):
self.airport = airport self.airport = airport
self._runway_status = RunwayStatus() self._runway_status = RunwayStatus()
@property
def dcs_airport(self) -> Airport:
return self.airport
@property @property
def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]: def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]:
return SymbolSet.LAND_INSTALLATIONS, LandInstallationEntity.AIPORT_AIR_BASE return SymbolSet.LAND_INSTALLATIONS, LandInstallationEntity.AIPORT_AIR_BASE
@ -1142,14 +1149,9 @@ class NavalControlPoint(ControlPoint, ABC):
class Carrier(NavalControlPoint): 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__( 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 @property
@ -1186,15 +1188,8 @@ class Carrier(NavalControlPoint):
class Lha(NavalControlPoint): class Lha(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__( super().__init__(name, at, at, starts_blue, cptype=ControlPointType.LHA_GROUP)
cp_id,
name,
at,
at,
starts_blue,
cptype=ControlPointType.LHA_GROUP,
)
@property @property
def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]: def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]:
@ -1223,14 +1218,9 @@ class OffMapSpawn(ControlPoint):
def runway_is_operational(self) -> bool: def runway_is_operational(self) -> bool:
return True 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__( super().__init__(
cp_id, name, position, position, starts_blue, cptype=ControlPointType.OFF_MAP
name,
position,
position,
starts_blue,
cptype=ControlPointType.OFF_MAP,
) )
@property @property
@ -1287,15 +1277,8 @@ class OffMapSpawn(ControlPoint):
class Fob(ControlPoint): class Fob(ControlPoint):
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__( super().__init__(name, at, at, starts_blue, cptype=ControlPointType.FOB)
cp_id,
name,
at,
at,
starts_blue,
cptype=ControlPointType.FOB,
)
self.name = name self.name = name
@property @property

View File

@ -39,12 +39,6 @@ class QBaseMenu2(QDialog):
self.game_model = game_model self.game_model = game_model
self.objectName = "menuDialogue" 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: if self.cp.captured:
self.deliveryEvent = None self.deliveryEvent = None