Add a UUID -> Flight database to Game.

This will be expanded with other types as needed.
This commit is contained in:
Dan Albert 2022-02-19 12:46:29 -08:00
parent ab6f44cb6f
commit cba68549d8
14 changed files with 89 additions and 53 deletions

View File

@ -4,10 +4,11 @@ import logging
from collections import defaultdict from collections import defaultdict
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import timedelta from datetime import timedelta
from typing import List, Optional, Dict, TYPE_CHECKING from typing import Dict, List, Optional, TYPE_CHECKING
from game.ato import Flight, FlightType from game.ato import Flight, FlightType
from game.ato.packagewaypoints import PackageWaypoints from game.ato.packagewaypoints import PackageWaypoints
from game.db import Database
from game.utils import Speed from game.utils import Speed
from gen.flights.flightplan import FormationFlightPlan from gen.flights.flightplan import FormationFlightPlan
from gen.flights.traveltime import TotEstimator from gen.flights.traveltime import TotEstimator
@ -24,6 +25,8 @@ class Package:
#: TheaterGroundObject (non-ControlPoint map objectives). #: TheaterGroundObject (non-ControlPoint map objectives).
target: MissionTarget target: MissionTarget
_db: Database[Flight]
#: The set of flights in the package. #: The set of flights in the package.
flights: List[Flight] = field(default_factory=list) flights: List[Flight] = field(default_factory=list)
@ -119,10 +122,12 @@ class Package:
def add_flight(self, flight: Flight) -> None: def add_flight(self, flight: Flight) -> None:
"""Adds a flight to the package.""" """Adds a flight to the package."""
self.flights.append(flight) self.flights.append(flight)
self._db.add(flight.id, flight)
def remove_flight(self, flight: Flight) -> None: def remove_flight(self, flight: Flight) -> None:
"""Removes a flight from the package.""" """Removes a flight from the package."""
self.flights.remove(flight) self.flights.remove(flight)
self._db.remove(flight.id)
if not self.flights: if not self.flights:
self.waypoints = None self.waypoints = None

View File

@ -2,11 +2,12 @@ from __future__ import annotations
from typing import Optional, TYPE_CHECKING from typing import Optional, TYPE_CHECKING
from game.theater import ControlPoint, MissionTarget, OffMapSpawn
from game.utils import nautical_miles from game.utils import nautical_miles
from ..ato.package import Package
from game.theater import MissionTarget, OffMapSpawn, ControlPoint
from ..ato.flight import Flight from ..ato.flight import Flight
from ..ato.package import Package
from ..ato.starttype import StartType from ..ato.starttype import StartType
from ..db.database import Database
if TYPE_CHECKING: if TYPE_CHECKING:
from game.dcs.aircrafttype import AircraftType from game.dcs.aircrafttype import AircraftType
@ -23,6 +24,7 @@ class PackageBuilder:
location: MissionTarget, location: MissionTarget,
closest_airfields: ClosestAirfields, closest_airfields: ClosestAirfields,
air_wing: AirWing, air_wing: AirWing,
flight_db: Database[Flight],
is_player: bool, is_player: bool,
package_country: str, package_country: str,
start_type: StartType, start_type: StartType,
@ -31,7 +33,7 @@ class PackageBuilder:
self.closest_airfields = closest_airfields self.closest_airfields = closest_airfields
self.is_player = is_player self.is_player = is_player
self.package_country = package_country self.package_country = package_country
self.package = Package(location, auto_asap=asap) self.package = Package(location, flight_db, auto_asap=asap)
self.air_wing = air_wing self.air_wing = air_wing
self.start_type = start_type self.start_type = start_type

View File

@ -2,24 +2,26 @@ from __future__ import annotations
import logging import logging
from collections import defaultdict from collections import defaultdict
from typing import Set, Iterable, Dict, TYPE_CHECKING, Optional from typing import Dict, Iterable, Optional, Set, TYPE_CHECKING
from game.commander.missionproposals import ProposedMission, ProposedFlight, EscortType from game.ato.airtaaskingorder import AirTaskingOrder
from game.ato.flighttype import FlightType
from game.ato.package import Package
from game.commander.missionproposals import EscortType, ProposedFlight, ProposedMission
from game.commander.packagebuilder import PackageBuilder from game.commander.packagebuilder import PackageBuilder
from game.data.doctrine import Doctrine from game.data.doctrine import Doctrine
from game.db import Database
from game.procurement import AircraftProcurementRequest from game.procurement import AircraftProcurementRequest
from game.profiling import MultiEventTracer from game.profiling import MultiEventTracer
from game.settings import Settings from game.settings import Settings
from game.squadrons import AirWing from game.squadrons import AirWing
from game.theater import ConflictTheater from game.theater import ConflictTheater
from game.threatzones import ThreatZones from game.threatzones import ThreatZones
from game.ato.airtaaskingorder import AirTaskingOrder
from game.ato.package import Package
from gen.flights.closestairfields import ObjectiveDistanceCache from gen.flights.closestairfields import ObjectiveDistanceCache
from game.ato.flighttype import FlightType
from gen.flights.flightplan import FlightPlanBuilder from gen.flights.flightplan import FlightPlanBuilder
if TYPE_CHECKING: if TYPE_CHECKING:
from game.ato import Flight
from game.coalition import Coalition from game.coalition import Coalition
@ -27,10 +29,15 @@ class PackageFulfiller:
"""Responsible for package aircraft allocation and flight plan layout.""" """Responsible for package aircraft allocation and flight plan layout."""
def __init__( def __init__(
self, coalition: Coalition, theater: ConflictTheater, settings: Settings self,
coalition: Coalition,
theater: ConflictTheater,
flight_db: Database[Flight],
settings: Settings,
) -> None: ) -> None:
self.coalition = coalition self.coalition = coalition
self.theater = theater self.theater = theater
self.flight_db = flight_db
self.player_missions_asap = settings.auto_ato_player_missions_asap self.player_missions_asap = settings.auto_ato_player_missions_asap
self.default_start_type = settings.default_start_type self.default_start_type = settings.default_start_type
@ -133,6 +140,7 @@ class PackageFulfiller:
mission.location, mission.location,
ObjectiveDistanceCache.get_closest_airfields(mission.location), ObjectiveDistanceCache.get_closest_airfields(mission.location),
self.air_wing, self.air_wing,
self.flight_db,
self.is_player, self.is_player,
self.coalition.country_name, self.coalition.country_name,
self.default_start_type, self.default_start_type,

View File

@ -4,10 +4,12 @@ import itertools
import operator import operator
from abc import abstractmethod from abc import abstractmethod
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import unique, IntEnum, auto from enum import IntEnum, auto, unique
from typing import TYPE_CHECKING, Optional, Generic, TypeVar, Iterator, Union from typing import Generic, Iterator, Optional, TYPE_CHECKING, TypeVar, Union
from game.commander.missionproposals import ProposedFlight, EscortType, ProposedMission from game.ato.flighttype import FlightType
from game.ato.package import Package
from game.commander.missionproposals import EscortType, ProposedFlight, ProposedMission
from game.commander.packagefulfiller import PackageFulfiller from game.commander.packagefulfiller import PackageFulfiller
from game.commander.tasks.theatercommandertask import TheaterCommanderTask from game.commander.tasks.theatercommandertask import TheaterCommanderTask
from game.commander.theaterstate import TheaterState from game.commander.theaterstate import TheaterState
@ -15,8 +17,6 @@ from game.settings import AutoAtoBehavior
from game.theater import MissionTarget from game.theater import MissionTarget
from game.theater.theatergroundobject import IadsGroundObject, NavalGroundObject from game.theater.theatergroundobject import IadsGroundObject, NavalGroundObject
from game.utils import Distance, meters from game.utils import Distance, meters
from game.ato.package import Package
from game.ato.flighttype import FlightType
if TYPE_CHECKING: if TYPE_CHECKING:
from game.coalition import Coalition from game.coalition import Coalition
@ -40,7 +40,6 @@ class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]):
def __post_init__(self) -> None: def __post_init__(self) -> None:
self.flights = [] self.flights = []
self.package = Package(self.target)
def preconditions_met(self, state: TheaterState) -> bool: def preconditions_met(self, state: TheaterState) -> bool:
if ( if (
@ -97,6 +96,7 @@ class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]):
fulfiller = PackageFulfiller( fulfiller = PackageFulfiller(
state.context.coalition, state.context.coalition,
state.context.theater, state.context.theater,
state.context.game_db.flights,
state.context.settings, state.context.settings,
) )
self.package = fulfiller.plan_mission( self.package = fulfiller.plan_mission(

View File

@ -9,6 +9,7 @@ from typing import Any, Optional, TYPE_CHECKING, Union
from game.commander.garrisons import Garrisons from game.commander.garrisons import Garrisons
from game.commander.objectivefinder import ObjectiveFinder from game.commander.objectivefinder import ObjectiveFinder
from game.db import GameDb
from game.htn import WorldState from game.htn import WorldState
from game.profiling import MultiEventTracer from game.profiling import MultiEventTracer
from game.settings import Settings from game.settings import Settings
@ -31,6 +32,7 @@ if TYPE_CHECKING:
@dataclass(frozen=True) @dataclass(frozen=True)
class PersistentContext: class PersistentContext:
game_db: GameDb
coalition: Coalition coalition: Coalition
theater: ConflictTheater theater: ConflictTheater
turn: int turn: int
@ -140,7 +142,7 @@ class TheaterState(WorldState["TheaterState"]):
ordered_capturable_points = finder.prioritized_unisolated_points() ordered_capturable_points = finder.prioritized_unisolated_points()
context = PersistentContext( context = PersistentContext(
coalition, game.theater, game.turn, game.settings, tracer game.db, coalition, game.theater, game.turn, game.settings, tracer
) )
# Plan enough rounds of CAP that the target has coverage over the expected # Plan enough rounds of CAP that the target has coverage over the expected

2
game/db/__init__.py Normal file
View File

@ -0,0 +1,2 @@
from .database import Database
from .gamedb import GameDb

20
game/db/database.py Normal file
View File

@ -0,0 +1,20 @@
from typing import Generic, TypeVar
from uuid import UUID
T = TypeVar("T")
class Database(Generic[T]):
def __init__(self) -> None:
self.objects: dict[UUID, T] = {}
def add(self, uuid: UUID, obj: T) -> None:
if uuid in self.objects:
raise KeyError(f"Object with UUID {uuid} already exists")
self.objects[uuid] = obj
def get(self, uuid: UUID) -> T:
return self.objects[uuid]
def remove(self, uuid: UUID) -> None:
del self.objects[uuid]

11
game/db/gamedb.py Normal file
View File

@ -0,0 +1,11 @@
from typing import TYPE_CHECKING
from .database import Database
if TYPE_CHECKING:
from game.ato import Flight
class GameDb:
def __init__(self) -> None:
self.flights: Database[Flight] = Database()

View File

@ -6,7 +6,7 @@ import math
from collections.abc import Iterator 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, TypeVar, Union, cast from typing import Any, List, TYPE_CHECKING, Type, Union, cast
from dcs.countries import Switzerland, USAFAggressors, UnitedNationsPeacekeepers from dcs.countries import Switzerland, USAFAggressors, UnitedNationsPeacekeepers
from dcs.country import Country from dcs.country import Country
@ -25,6 +25,7 @@ from . import persistency
from .ato.flighttype import FlightType from .ato.flighttype import FlightType
from .campaignloader import CampaignAirWingConfig from .campaignloader import CampaignAirWingConfig
from .coalition import Coalition from .coalition import Coalition
from .db.gamedb import GameDb
from .factions.faction import Faction from .factions.faction import Faction
from .infos.information import Information from .infos.information import Information
from .profiling import logged_duration from .profiling import logged_duration
@ -115,6 +116,8 @@ class Game:
self.current_group_id = 0 self.current_group_id = 0
self.name_generator = naming.namegen self.name_generator = naming.namegen
self.db = GameDb()
self.conditions = self.generate_conditions() self.conditions = self.generate_conditions()
self.sanitize_sides(player_faction, enemy_faction) self.sanitize_sides(player_faction, enemy_faction)

View File

@ -140,7 +140,7 @@ class AircraftGenerator:
# reuse the existing debriefing code. # reuse the existing debriefing code.
# TODO: Special flight type? # TODO: Special flight type?
flight = Flight( flight = Flight(
Package(squadron.location), Package(squadron.location, self.game.db.flights),
faction.country, faction.country,
squadron, squadron,
1, 1,

View File

@ -3,36 +3,22 @@ from uuid import UUID
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from game import Game from game import Game
from game.ato import Flight
from game.server import GameContext from game.server import GameContext
from .models import HoldZonesJs, IpZonesJs, JoinZonesJs from .models import HoldZonesJs, IpZonesJs, JoinZonesJs
router: APIRouter = APIRouter(prefix="/debug/waypoint-geometries") router: APIRouter = APIRouter(prefix="/debug/waypoint-geometries")
# TODO: Maintain map of UUID -> Flight in Game.
def find_flight(game: Game, flight_id: UUID) -> Flight:
for coalition in game.coalitions:
for package in coalition.ato.packages:
for flight in package.flights:
if flight.id == flight_id:
return flight
raise KeyError(f"No flight found with ID {flight_id}")
@router.get("/hold/{flight_id}") @router.get("/hold/{flight_id}")
def hold_zones(flight_id: UUID, game: Game = Depends(GameContext.get)) -> HoldZonesJs: def hold_zones(flight_id: UUID, game: Game = Depends(GameContext.get)) -> HoldZonesJs:
flight = find_flight(game, flight_id) return HoldZonesJs.for_flight(game.db.flights.get(flight_id), game)
return HoldZonesJs.for_flight(flight, game)
@router.get("/ip/{flight_id}") @router.get("/ip/{flight_id}")
def ip_zones(flight_id: UUID, game: Game = Depends(GameContext.get)) -> IpZonesJs: def ip_zones(flight_id: UUID, game: Game = Depends(GameContext.get)) -> IpZonesJs:
flight = find_flight(game, flight_id) return IpZonesJs.for_flight(game.db.flights.get(flight_id), game)
return IpZonesJs.for_flight(flight, game)
@router.get("/join/{flight_id}") @router.get("/join/{flight_id}")
def join_zones(flight_id: UUID, game: Game = Depends(GameContext.get)) -> JoinZonesJs: def join_zones(flight_id: UUID, game: Game = Depends(GameContext.get)) -> JoinZonesJs:
flight = find_flight(game, flight_id) return JoinZonesJs.for_flight(game.db.flights.get(flight_id), game)
return JoinZonesJs.for_flight(flight, game)

View File

@ -12,6 +12,7 @@ from game.ato import Flight, FlightType, Package
from game.settings import AutoAtoBehavior, Settings from game.settings import AutoAtoBehavior, Settings
from gen.flights.flightplan import FlightPlanBuilder from gen.flights.flightplan import FlightPlanBuilder
from .pilot import Pilot, PilotStatus from .pilot import Pilot, PilotStatus
from ..db.database import Database
from ..utils import meters from ..utils import meters
if TYPE_CHECKING: if TYPE_CHECKING:
@ -50,6 +51,7 @@ class Squadron:
) )
coalition: Coalition = field(hash=False, compare=False) coalition: Coalition = field(hash=False, compare=False)
flight_db: Database[Flight] = field(hash=False, compare=False)
settings: Settings = field(hash=False, compare=False) settings: Settings = field(hash=False, compare=False)
location: ControlPoint location: ControlPoint
@ -388,7 +390,7 @@ class Squadron:
if not remaining: if not remaining:
return return
package = Package(self.destination) package = Package(self.destination, self.flight_db)
builder = FlightPlanBuilder(package, self.coalition, theater) builder = FlightPlanBuilder(package, self.coalition, theater)
while remaining: while remaining:
size = min(remaining, self.aircraft.max_group_size) size = min(remaining, self.aircraft.max_group_size)
@ -437,6 +439,7 @@ class Squadron:
squadron_def.female_pilot_percentage, squadron_def.female_pilot_percentage,
squadron_def.pilot_pool, squadron_def.pilot_pool,
coalition, coalition,
game.db.flights,
game.settings, game.settings,
base, base,
) )

View File

@ -36,18 +36,13 @@ import math
from collections import defaultdict from collections import defaultdict
from dataclasses import dataclass, field from dataclasses import dataclass, field
from functools import singledispatchmethod from functools import singledispatchmethod
from typing import ( from typing import Generic, Iterator, List, Optional, Sequence, TYPE_CHECKING, TypeVar
Generic,
Iterator,
List,
Optional,
TYPE_CHECKING,
TypeVar,
Sequence,
)
from dcs.mapping import Point from dcs.mapping import Point
from game.ato.flight import Flight
from game.ato.flighttype import FlightType
from game.ato.package import Package
from game.dcs.aircrafttype import AircraftType from game.dcs.aircrafttype import AircraftType
from game.dcs.groundunittype import GroundUnitType from game.dcs.groundunittype import GroundUnitType
from game.procurement import AircraftProcurementRequest from game.procurement import AircraftProcurementRequest
@ -57,11 +52,8 @@ from game.theater.transitnetwork import (
TransitNetwork, TransitNetwork,
) )
from game.utils import meters, nautical_miles from game.utils import meters, nautical_miles
from game.ato.package import Package
from gen.flights.ai_flight_planner_db import aircraft_for_task from gen.flights.ai_flight_planner_db import aircraft_for_task
from gen.flights.closestairfields import ObjectiveDistanceCache from gen.flights.closestairfields import ObjectiveDistanceCache
from game.ato.flighttype import FlightType
from game.ato.flight import Flight
from gen.flights.flightplan import FlightPlanBuilder from gen.flights.flightplan import FlightPlanBuilder
from gen.naming import namegen from gen.naming import namegen
@ -271,7 +263,7 @@ class AirliftPlanner:
self.transfer = transfer self.transfer = transfer
self.next_stop = next_stop self.next_stop = next_stop
self.for_player = transfer.destination.captured self.for_player = transfer.destination.captured
self.package = Package(target=next_stop, auto_asap=True) self.package = Package(next_stop, game.db.flights, auto_asap=True)
def compatible_with_mission( def compatible_with_mission(
self, unit_type: AircraftType, airfield: ControlPoint self, unit_type: AircraftType, airfield: ControlPoint

View File

@ -15,10 +15,10 @@ from PySide2.QtWidgets import (
QVBoxLayout, QVBoxLayout,
) )
from game.ato.flight import Flight
from game.ato.package import Package
from game.game import Game from game.game import Game
from game.theater.missiontarget import MissionTarget from game.theater.missiontarget import MissionTarget
from game.ato.package import Package
from game.ato.flight import Flight
from gen.flights.flightplan import FlightPlanBuilder, PlanningError from gen.flights.flightplan import FlightPlanBuilder, PlanningError
from qt_ui.models import AtoModel, GameModel, PackageModel from qt_ui.models import AtoModel, GameModel, PackageModel
from qt_ui.uiconstants import EVENT_ICONS from qt_ui.uiconstants import EVENT_ICONS
@ -215,7 +215,9 @@ class QNewPackageDialog(QPackageDialog):
) -> None: ) -> None:
super().__init__( super().__init__(
game_model, game_model,
PackageModel(Package(target, auto_asap=True), game_model), PackageModel(
Package(target, game_model.game.db.flights, auto_asap=True), game_model
),
parent=parent, parent=parent,
) )
self.ato_model = model self.ato_model = model