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

View File

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

View File

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

View File

@ -4,10 +4,12 @@ import itertools
import operator
from abc import abstractmethod
from dataclasses import dataclass, field
from enum import unique, IntEnum, auto
from typing import TYPE_CHECKING, Optional, Generic, TypeVar, Iterator, Union
from enum import IntEnum, auto, unique
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.tasks.theatercommandertask import TheaterCommanderTask
from game.commander.theaterstate import TheaterState
@ -15,8 +17,6 @@ from game.settings import AutoAtoBehavior
from game.theater import MissionTarget
from game.theater.theatergroundobject import IadsGroundObject, NavalGroundObject
from game.utils import Distance, meters
from game.ato.package import Package
from game.ato.flighttype import FlightType
if TYPE_CHECKING:
from game.coalition import Coalition
@ -40,7 +40,6 @@ class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]):
def __post_init__(self) -> None:
self.flights = []
self.package = Package(self.target)
def preconditions_met(self, state: TheaterState) -> bool:
if (
@ -97,6 +96,7 @@ class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]):
fulfiller = PackageFulfiller(
state.context.coalition,
state.context.theater,
state.context.game_db.flights,
state.context.settings,
)
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.objectivefinder import ObjectiveFinder
from game.db import GameDb
from game.htn import WorldState
from game.profiling import MultiEventTracer
from game.settings import Settings
@ -31,6 +32,7 @@ if TYPE_CHECKING:
@dataclass(frozen=True)
class PersistentContext:
game_db: GameDb
coalition: Coalition
theater: ConflictTheater
turn: int
@ -140,7 +142,7 @@ class TheaterState(WorldState["TheaterState"]):
ordered_capturable_points = finder.prioritized_unisolated_points()
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

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 datetime import date, datetime, timedelta
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.country import Country
@ -25,6 +25,7 @@ from . import persistency
from .ato.flighttype import FlightType
from .campaignloader import CampaignAirWingConfig
from .coalition import Coalition
from .db.gamedb import GameDb
from .factions.faction import Faction
from .infos.information import Information
from .profiling import logged_duration
@ -115,6 +116,8 @@ class Game:
self.current_group_id = 0
self.name_generator = naming.namegen
self.db = GameDb()
self.conditions = self.generate_conditions()
self.sanitize_sides(player_faction, enemy_faction)

View File

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

View File

@ -3,36 +3,22 @@ from uuid import UUID
from fastapi import APIRouter, Depends
from game import Game
from game.ato import Flight
from game.server import GameContext
from .models import HoldZonesJs, IpZonesJs, JoinZonesJs
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}")
def hold_zones(flight_id: UUID, game: Game = Depends(GameContext.get)) -> HoldZonesJs:
flight = find_flight(game, flight_id)
return HoldZonesJs.for_flight(flight, game)
return HoldZonesJs.for_flight(game.db.flights.get(flight_id), game)
@router.get("/ip/{flight_id}")
def ip_zones(flight_id: UUID, game: Game = Depends(GameContext.get)) -> IpZonesJs:
flight = find_flight(game, flight_id)
return IpZonesJs.for_flight(flight, game)
return IpZonesJs.for_flight(game.db.flights.get(flight_id), game)
@router.get("/join/{flight_id}")
def join_zones(flight_id: UUID, game: Game = Depends(GameContext.get)) -> JoinZonesJs:
flight = find_flight(game, flight_id)
return JoinZonesJs.for_flight(flight, game)
return JoinZonesJs.for_flight(game.db.flights.get(flight_id), game)

View File

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

View File

@ -36,18 +36,13 @@ import math
from collections import defaultdict
from dataclasses import dataclass, field
from functools import singledispatchmethod
from typing import (
Generic,
Iterator,
List,
Optional,
TYPE_CHECKING,
TypeVar,
Sequence,
)
from typing import Generic, Iterator, List, Optional, Sequence, TYPE_CHECKING, TypeVar
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.groundunittype import GroundUnitType
from game.procurement import AircraftProcurementRequest
@ -57,11 +52,8 @@ from game.theater.transitnetwork import (
TransitNetwork,
)
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.closestairfields import ObjectiveDistanceCache
from game.ato.flighttype import FlightType
from game.ato.flight import Flight
from gen.flights.flightplan import FlightPlanBuilder
from gen.naming import namegen
@ -271,7 +263,7 @@ class AirliftPlanner:
self.transfer = transfer
self.next_stop = next_stop
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(
self, unit_type: AircraftType, airfield: ControlPoint

View File

@ -15,10 +15,10 @@ from PySide2.QtWidgets import (
QVBoxLayout,
)
from game.ato.flight import Flight
from game.ato.package import Package
from game.game import Game
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 qt_ui.models import AtoModel, GameModel, PackageModel
from qt_ui.uiconstants import EVENT_ICONS
@ -215,7 +215,9 @@ class QNewPackageDialog(QPackageDialog):
) -> None:
super().__init__(
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,
)
self.ato_model = model