mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Move front lines out of MapModel.
This commit is contained in:
parent
89b987fc87
commit
ccb510fe47
@ -4,8 +4,10 @@ from .database import Database
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.ato import Flight
|
||||
from game.theater import FrontLine
|
||||
|
||||
|
||||
class GameDb:
|
||||
def __init__(self) -> None:
|
||||
self.flights: Database[Flight] = Database()
|
||||
self.front_lines: Database[FrontLine] = Database()
|
||||
|
||||
10
game/game.py
10
game/game.py
@ -222,7 +222,7 @@ class Game:
|
||||
# reset when we're done.
|
||||
self.compute_threat_zones(GameUpdateEvents())
|
||||
|
||||
def finish_turn(self, skipped: bool = False) -> None:
|
||||
def finish_turn(self, events: GameUpdateEvents, skipped: bool = False) -> None:
|
||||
"""Finalizes the current turn and advances to the next turn.
|
||||
|
||||
This handles the turn-end portion of passing a turn. Initialization of the next
|
||||
@ -265,6 +265,9 @@ class Game:
|
||||
|
||||
if not skipped:
|
||||
for cp in self.theater.player_points():
|
||||
for front_line in cp.front_lines.values():
|
||||
front_line.update_position()
|
||||
events.update_front_line(front_line)
|
||||
cp.base.affect_strength(+PLAYER_BASE_STRENGTH_RECOVERY)
|
||||
|
||||
self.conditions = self.generate_conditions()
|
||||
@ -293,11 +296,12 @@ class Game:
|
||||
from .server import EventStream
|
||||
from .sim import GameUpdateEvents
|
||||
|
||||
events = GameUpdateEvents()
|
||||
|
||||
logging.info("Pass turn")
|
||||
with logged_duration("Turn finalization"):
|
||||
self.finish_turn(no_action)
|
||||
self.finish_turn(events, no_action)
|
||||
|
||||
events = GameUpdateEvents()
|
||||
with logged_duration("Turn initialization"):
|
||||
self.initialize_turn(events)
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ from . import (
|
||||
frontlines,
|
||||
mapzones,
|
||||
navmesh,
|
||||
packagedialog,
|
||||
supplyroutes,
|
||||
tgos,
|
||||
waypoints,
|
||||
@ -28,6 +29,7 @@ app.include_router(flights.router)
|
||||
app.include_router(frontlines.router)
|
||||
app.include_router(mapzones.router)
|
||||
app.include_router(navmesh.router)
|
||||
app.include_router(packagedialog.router)
|
||||
app.include_router(supplyroutes.router)
|
||||
app.include_router(tgos.router)
|
||||
app.include_router(waypoints.router)
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Callable, TYPE_CHECKING
|
||||
|
||||
from game.theater import MissionTarget
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
@ -23,3 +25,22 @@ class GameContext:
|
||||
@classmethod
|
||||
def get_model(cls) -> GameModel:
|
||||
return cls._game_model
|
||||
|
||||
|
||||
class QtCallbacks:
|
||||
def __init__(self, create_new_package: Callable[[MissionTarget], None]) -> None:
|
||||
self.create_new_package = create_new_package
|
||||
|
||||
|
||||
class QtContext:
|
||||
_callbacks: QtCallbacks
|
||||
|
||||
@classmethod
|
||||
def set_callbacks(cls, callbacks: QtCallbacks) -> None:
|
||||
cls._callbacks = callbacks
|
||||
|
||||
@classmethod
|
||||
def get(cls) -> QtCallbacks:
|
||||
if cls._callbacks is None:
|
||||
raise RuntimeError("QtContext has no callbacks set")
|
||||
return cls._callbacks
|
||||
|
||||
@ -7,6 +7,7 @@ from pydantic import BaseModel
|
||||
|
||||
from game.server.combat.models import FrozenCombatJs
|
||||
from game.server.flights.models import FlightJs
|
||||
from game.server.frontlines.models import FrontLineJs
|
||||
from game.server.leaflet import LeafletLatLon
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -27,6 +28,9 @@ class GameUpdateEventsJs(BaseModel):
|
||||
deleted_flights: set[UUID]
|
||||
selected_flight: UUID | None
|
||||
deselected_flight: bool
|
||||
new_front_lines: list[FrontLineJs]
|
||||
updated_front_lines: set[UUID]
|
||||
deleted_front_lines: set[UUID]
|
||||
|
||||
@classmethod
|
||||
def from_events(cls, events: GameUpdateEvents, game: Game) -> GameUpdateEventsJs:
|
||||
@ -53,4 +57,9 @@ class GameUpdateEventsJs(BaseModel):
|
||||
deleted_flights=events.deleted_flights,
|
||||
selected_flight=events.selected_flight,
|
||||
deselected_flight=events.deselected_flight,
|
||||
new_front_lines=[
|
||||
FrontLineJs.for_front_line(f) for f in events.new_front_lines
|
||||
],
|
||||
updated_front_lines=events.updated_front_lines,
|
||||
deleted_front_lines=events.deleted_front_lines,
|
||||
)
|
||||
|
||||
@ -1,9 +1,24 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from game.server.leaflet import LeafletPoint
|
||||
from game.theater import FrontLine
|
||||
from game.utils import nautical_miles
|
||||
|
||||
|
||||
class FrontLineJs(BaseModel):
|
||||
id: UUID
|
||||
extents: list[LeafletPoint]
|
||||
|
||||
@staticmethod
|
||||
def for_front_line(front_line: FrontLine) -> FrontLineJs:
|
||||
a = front_line.position.point_from_heading(
|
||||
front_line.attack_heading.right.degrees, nautical_miles(2).meters
|
||||
)
|
||||
b = front_line.position.point_from_heading(
|
||||
front_line.attack_heading.left.degrees, nautical_miles(2).meters
|
||||
)
|
||||
return FrontLineJs(id=front_line.id, extents=[a.latlng(), b.latlng()])
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from game import Game
|
||||
from game.utils import nautical_miles
|
||||
from .models import FrontLineJs
|
||||
from ..dependencies import GameContext
|
||||
|
||||
@ -10,13 +11,11 @@ router: APIRouter = APIRouter(prefix="/front-lines")
|
||||
|
||||
@router.get("/")
|
||||
def list_front_lines(game: Game = Depends(GameContext.get)) -> list[FrontLineJs]:
|
||||
front_lines = []
|
||||
for front_line in game.theater.conflicts():
|
||||
a = front_line.position.point_from_heading(
|
||||
front_line.attack_heading.right.degrees, nautical_miles(2).meters
|
||||
)
|
||||
b = front_line.position.point_from_heading(
|
||||
front_line.attack_heading.left.degrees, nautical_miles(2).meters
|
||||
)
|
||||
front_lines.append(FrontLineJs(extents=[a.latlng(), b.latlng()]))
|
||||
return front_lines
|
||||
return [FrontLineJs.for_front_line(f) for f in game.theater.conflicts()]
|
||||
|
||||
|
||||
@router.get("/{front_line_id}")
|
||||
def get_front_line(
|
||||
front_line_id: UUID, game: Game = Depends(GameContext.get)
|
||||
) -> FrontLineJs:
|
||||
return FrontLineJs.for_front_line(game.db.front_lines.get(front_line_id))
|
||||
|
||||
1
game/server/packagedialog/__init__.py
Normal file
1
game/server/packagedialog/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .routes import router
|
||||
18
game/server/packagedialog/routes.py
Normal file
18
game/server/packagedialog/routes.py
Normal file
@ -0,0 +1,18 @@
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from game import Game
|
||||
from ..dependencies import GameContext, QtCallbacks, QtContext
|
||||
|
||||
router: APIRouter = APIRouter(prefix="/package-dialog")
|
||||
|
||||
|
||||
@router.post("/front-line/{front_line_id}")
|
||||
def new_front_line_package(
|
||||
front_line_id: UUID,
|
||||
game: Game = Depends(GameContext.get),
|
||||
qt: QtCallbacks = Depends(QtContext.get),
|
||||
) -> None:
|
||||
front_line = game.db.front_lines.get(front_line_id)
|
||||
qt.create_new_package(front_line)
|
||||
@ -68,8 +68,9 @@ class GameLoop:
|
||||
|
||||
def complete_with_results(self, debriefing: Debriefing) -> None:
|
||||
self.pause()
|
||||
self.sim.process_results(debriefing)
|
||||
self.sim.process_results(debriefing, self.events)
|
||||
self.completed = True
|
||||
self.send_update(rate_limit=False)
|
||||
|
||||
def send_update(self, rate_limit: bool) -> None:
|
||||
# We don't skip empty events because we still want the tick in the Qt part of
|
||||
|
||||
@ -9,6 +9,7 @@ from dcs import Point
|
||||
if TYPE_CHECKING:
|
||||
from game.ato import Flight, Package
|
||||
from game.sim.combat import FrozenCombat
|
||||
from game.theater import FrontLine
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -26,6 +27,9 @@ class GameUpdateEvents:
|
||||
deleted_flights: set[UUID] = field(default_factory=set)
|
||||
selected_flight: UUID | None = None
|
||||
deselected_flight: bool = False
|
||||
new_front_lines: set[FrontLine] = field(default_factory=set)
|
||||
updated_front_lines: set[UUID] = field(default_factory=set)
|
||||
deleted_front_lines: set[UUID] = field(default_factory=set)
|
||||
shutting_down: bool = False
|
||||
|
||||
@property
|
||||
@ -95,6 +99,18 @@ class GameUpdateEvents:
|
||||
self.selected_flight = None
|
||||
return self
|
||||
|
||||
def new_front_line(self, front_line: FrontLine) -> GameUpdateEvents:
|
||||
self.new_front_lines.add(front_line)
|
||||
return self
|
||||
|
||||
def update_front_line(self, front_line: FrontLine) -> GameUpdateEvents:
|
||||
self.updated_front_lines.add(front_line.id)
|
||||
return self
|
||||
|
||||
def delete_front_line(self, front_line: FrontLine) -> GameUpdateEvents:
|
||||
self.deleted_front_lines.add(front_line.id)
|
||||
return self
|
||||
|
||||
def shut_down(self) -> GameUpdateEvents:
|
||||
self.shutting_down = True
|
||||
return self
|
||||
|
||||
@ -6,6 +6,7 @@ from typing import TYPE_CHECKING
|
||||
from game.debriefing import Debriefing
|
||||
from game.ground_forces.combat_stance import CombatStance
|
||||
from game.theater import ControlPoint
|
||||
from .gameupdateevents import GameUpdateEvents
|
||||
from ..ato.airtaaskingorder import AirTaskingOrder
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -21,7 +22,7 @@ class MissionResultsProcessor:
|
||||
def __init__(self, game: Game) -> None:
|
||||
self.game = game
|
||||
|
||||
def commit(self, debriefing: Debriefing) -> None:
|
||||
def commit(self, debriefing: Debriefing, events: GameUpdateEvents) -> None:
|
||||
logging.info("Committing mission results")
|
||||
self.commit_air_losses(debriefing)
|
||||
self.commit_pilot_experience()
|
||||
@ -31,8 +32,8 @@ class MissionResultsProcessor:
|
||||
self.commit_airlift_losses(debriefing)
|
||||
self.commit_ground_losses(debriefing)
|
||||
self.commit_damaged_runways(debriefing)
|
||||
self.commit_captures(debriefing)
|
||||
self.commit_front_line_battle_impact(debriefing)
|
||||
self.commit_captures(debriefing, events)
|
||||
self.commit_front_line_battle_impact(debriefing, events)
|
||||
self.record_carcasses(debriefing)
|
||||
|
||||
def commit_air_losses(self, debriefing: Debriefing) -> None:
|
||||
@ -141,7 +142,7 @@ class MissionResultsProcessor:
|
||||
for damaged_runway in debriefing.damaged_runways:
|
||||
damaged_runway.damage_runway()
|
||||
|
||||
def commit_captures(self, debriefing: Debriefing) -> None:
|
||||
def commit_captures(self, debriefing: Debriefing, events: GameUpdateEvents) -> None:
|
||||
for captured in debriefing.base_captures:
|
||||
try:
|
||||
if captured.captured_by_player:
|
||||
@ -155,7 +156,9 @@ class MissionResultsProcessor:
|
||||
f"The enemy took control of {captured.control_point}.",
|
||||
)
|
||||
|
||||
captured.control_point.capture(self.game, captured.captured_by_player)
|
||||
captured.control_point.capture(
|
||||
self.game, events, captured.captured_by_player
|
||||
)
|
||||
logging.info(f"Will run redeploy for {captured.control_point}")
|
||||
self.redeploy_units(captured.control_point)
|
||||
except Exception:
|
||||
@ -165,10 +168,16 @@ class MissionResultsProcessor:
|
||||
for destroyed_unit in debriefing.state_data.destroyed_statics:
|
||||
self.game.add_destroyed_units(destroyed_unit)
|
||||
|
||||
def commit_front_line_battle_impact(self, debriefing: Debriefing) -> None:
|
||||
def commit_front_line_battle_impact(
|
||||
self, debriefing: Debriefing, events: GameUpdateEvents
|
||||
) -> None:
|
||||
for cp in self.game.theater.player_points():
|
||||
enemy_cps = [e for e in cp.connected_points if not e.captured]
|
||||
for enemy_cp in enemy_cps:
|
||||
front_line = cp.front_line_with(enemy_cp)
|
||||
front_line.update_position()
|
||||
events.update_front_line(front_line)
|
||||
|
||||
print(
|
||||
"Compute frontline progression for : "
|
||||
+ cp.name
|
||||
|
||||
@ -66,14 +66,14 @@ class MissionSimulation:
|
||||
debriefing.merge_simulation_results(self.aircraft_simulation.results)
|
||||
return debriefing
|
||||
|
||||
def process_results(self, debriefing: Debriefing) -> None:
|
||||
def process_results(self, debriefing: Debriefing, events: GameUpdateEvents) -> None:
|
||||
if self.unit_map is None:
|
||||
raise RuntimeError(
|
||||
"Simulation has no unit map. Results processing began before a mission "
|
||||
"was generated."
|
||||
)
|
||||
|
||||
MissionResultsProcessor(self.game).commit(debriefing)
|
||||
MissionResultsProcessor(self.game).commit(debriefing, events)
|
||||
|
||||
def finish(self) -> None:
|
||||
self.unit_map = None
|
||||
|
||||
@ -52,6 +52,7 @@ from .theatergroundobject import (
|
||||
from .theatergroup import TheaterUnit
|
||||
from ..ato.starttype import StartType
|
||||
from ..data.units import UnitClass
|
||||
from ..db import Database
|
||||
from ..dcs.aircrafttype import AircraftType
|
||||
from ..dcs.groundunittype import GroundUnitType
|
||||
from ..utils import nautical_miles
|
||||
@ -59,10 +60,11 @@ from ..weather import Conditions
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
from ..ato.flighttype import FlightType
|
||||
from game.ato.flighttype import FlightType
|
||||
from game.coalition import Coalition
|
||||
from game.sim import GameUpdateEvents
|
||||
from game.squadrons.squadron import Squadron
|
||||
from ..coalition import Coalition
|
||||
from ..transfers import PendingTransfers
|
||||
from game.transfers import PendingTransfers
|
||||
from .conflicttheater import ConflictTheater
|
||||
|
||||
FREE_FRONTLINE_UNIT_SUPPLY: int = 15
|
||||
@ -326,6 +328,9 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
|
||||
|
||||
self.target_position: Optional[Point] = None
|
||||
|
||||
# Initialized late because ControlPoints are constructed before the game is.
|
||||
self._front_line_db: Database[FrontLine] | None = None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<{self.__class__}: {self.name}>"
|
||||
|
||||
@ -338,31 +343,50 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
|
||||
def finish_init(self, game: Game) -> None:
|
||||
assert self._coalition is None
|
||||
self._coalition = game.coalition_for(self.starts_blue)
|
||||
assert self._front_line_db is None
|
||||
self._front_line_db = game.db.front_lines
|
||||
|
||||
def initialize_turn_0(self) -> None:
|
||||
self._recreate_front_lines()
|
||||
# We don't need to send events for turn 0. The UI isn't up yet, and it'll fetch
|
||||
# the entire game state when it comes up.
|
||||
from game.sim import GameUpdateEvents
|
||||
|
||||
def _recreate_front_lines(self) -> None:
|
||||
self._clear_front_lines()
|
||||
self._create_missing_front_lines(GameUpdateEvents())
|
||||
|
||||
@property
|
||||
def front_line_db(self) -> Database[FrontLine]:
|
||||
assert self._front_line_db is not None
|
||||
return self._front_line_db
|
||||
|
||||
def _create_missing_front_lines(self, events: GameUpdateEvents) -> None:
|
||||
for connection in self.convoy_routes.keys():
|
||||
if not connection.front_line_active_with(
|
||||
self
|
||||
) and not connection.is_friendly_to(self):
|
||||
self._create_front_line_with(connection)
|
||||
self._create_front_line_with(connection, events)
|
||||
|
||||
def _create_front_line_with(self, connection: ControlPoint) -> None:
|
||||
def _create_front_line_with(
|
||||
self, connection: ControlPoint, events: GameUpdateEvents
|
||||
) -> None:
|
||||
blue, red = FrontLine.sort_control_points(self, connection)
|
||||
front = FrontLine(blue, red)
|
||||
self.front_lines[connection] = front
|
||||
connection.front_lines[self] = front
|
||||
self.front_line_db.add(front.id, front)
|
||||
events.new_front_line(front)
|
||||
|
||||
def _remove_front_line_with(self, connection: ControlPoint) -> None:
|
||||
def _remove_front_line_with(
|
||||
self, connection: ControlPoint, events: GameUpdateEvents
|
||||
) -> None:
|
||||
front = self.front_lines[connection]
|
||||
del self.front_lines[connection]
|
||||
del connection.front_lines[self]
|
||||
self.front_line_db.remove(front.id)
|
||||
events.delete_front_line(front)
|
||||
|
||||
def _clear_front_lines(self) -> None:
|
||||
def _clear_front_lines(self, events: GameUpdateEvents) -> None:
|
||||
for opponent in list(self.front_lines.keys()):
|
||||
self._remove_front_line_with(opponent)
|
||||
self._remove_front_line_with(opponent, events)
|
||||
|
||||
@property
|
||||
def has_frontline(self) -> bool:
|
||||
@ -713,7 +737,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
|
||||
tgo.clear()
|
||||
|
||||
# TODO: Should be Airbase specific.
|
||||
def capture(self, game: Game, for_player: bool) -> None:
|
||||
def capture(self, game: Game, events: GameUpdateEvents, for_player: bool) -> None:
|
||||
new_coalition = game.coalition_for(for_player)
|
||||
self.ground_unit_orders.refund_all(self.coalition)
|
||||
self.retreat_ground_units(game)
|
||||
@ -722,7 +746,8 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
|
||||
|
||||
self._coalition = new_coalition
|
||||
self.base.set_strength_to_minimum()
|
||||
self._recreate_front_lines()
|
||||
self._clear_front_lines(events)
|
||||
self._create_missing_front_lines(events)
|
||||
|
||||
@property
|
||||
def required_aircraft_start_type(self) -> Optional[StartType]:
|
||||
@ -1127,7 +1152,7 @@ class Carrier(NavalControlPoint):
|
||||
FlightType.REFUELING,
|
||||
]
|
||||
|
||||
def capture(self, game: Game, for_player: bool) -> None:
|
||||
def capture(self, game: Game, events: GameUpdateEvents, for_player: bool) -> None:
|
||||
raise RuntimeError("Carriers cannot be captured")
|
||||
|
||||
@property
|
||||
@ -1161,7 +1186,7 @@ class Lha(NavalControlPoint):
|
||||
def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]:
|
||||
return SymbolSet.SEA_SURFACE, SeaSurfaceEntity.AMPHIBIOUS_ASSAULT_SHIP_GENERAL
|
||||
|
||||
def capture(self, game: Game, for_player: bool) -> None:
|
||||
def capture(self, game: Game, events: GameUpdateEvents, for_player: bool) -> None:
|
||||
raise RuntimeError("LHAs cannot be captured")
|
||||
|
||||
@property
|
||||
@ -1198,7 +1223,7 @@ class OffMapSpawn(ControlPoint):
|
||||
def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]:
|
||||
return SymbolSet.LAND_INSTALLATIONS, LandInstallationEntity.AIPORT_AIR_BASE
|
||||
|
||||
def capture(self, game: Game, for_player: bool) -> None:
|
||||
def capture(self, game: Game, events: GameUpdateEvents, for_player: bool) -> None:
|
||||
raise RuntimeError("Off map control points cannot be captured")
|
||||
|
||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import uuid
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Iterator, List, TYPE_CHECKING, Tuple
|
||||
|
||||
@ -49,6 +50,7 @@ class FrontLine(MissionTarget):
|
||||
blue_point: ControlPoint,
|
||||
red_point: ControlPoint,
|
||||
) -> None:
|
||||
self.id = uuid.uuid4()
|
||||
self.blue_cp = blue_point
|
||||
self.red_cp = red_point
|
||||
try:
|
||||
@ -67,8 +69,7 @@ class FrontLine(MissionTarget):
|
||||
FrontLineSegment(a, b) for a, b in pairwise(route)
|
||||
]
|
||||
super().__init__(
|
||||
f"Front line {blue_point}/{red_point}",
|
||||
self.point_from_a(self._position_distance),
|
||||
f"Front line {blue_point}/{red_point}", self._compute_position()
|
||||
)
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
@ -79,10 +80,11 @@ class FrontLine(MissionTarget):
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.blue_cp, self.red_cp))
|
||||
|
||||
def __setstate__(self, state: dict[str, Any]) -> None:
|
||||
self.__dict__.update(state)
|
||||
if not hasattr(self, "position"):
|
||||
self.position = self.point_from_a(self._position_distance)
|
||||
def _compute_position(self) -> Point:
|
||||
return self.point_from_a(self._position_distance)
|
||||
|
||||
def update_position(self) -> None:
|
||||
self.position = self._compute_position()
|
||||
|
||||
def control_point_friendly_to(self, player: bool) -> ControlPoint:
|
||||
if player:
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List
|
||||
|
||||
from PySide2.QtCore import Property, QObject, Signal, Slot
|
||||
|
||||
from game.server.leaflet import LeafletLatLon
|
||||
from game.theater import FrontLine
|
||||
from game.utils import nautical_miles
|
||||
from qt_ui.dialogs import Dialog
|
||||
|
||||
|
||||
class FrontLineJs(QObject):
|
||||
extentsChanged = Signal()
|
||||
|
||||
def __init__(self, front_line: FrontLine) -> None:
|
||||
super().__init__()
|
||||
self.front_line = front_line
|
||||
|
||||
@Property(list, notify=extentsChanged)
|
||||
def extents(self) -> List[LeafletLatLon]:
|
||||
a = self.front_line.position.point_from_heading(
|
||||
self.front_line.attack_heading.right.degrees, nautical_miles(2).meters
|
||||
).latlng()
|
||||
b = self.front_line.position.point_from_heading(
|
||||
self.front_line.attack_heading.left.degrees, nautical_miles(2).meters
|
||||
).latlng()
|
||||
return [a.as_list(), b.as_list()]
|
||||
|
||||
@Slot()
|
||||
def showPackageDialog(self) -> None:
|
||||
Dialog.open_new_package_dialog(self.front_line)
|
||||
@ -3,7 +3,7 @@ import traceback
|
||||
import webbrowser
|
||||
from typing import Optional
|
||||
|
||||
from PySide2.QtCore import QSettings, Qt
|
||||
from PySide2.QtCore import QSettings, Qt, Signal
|
||||
from PySide2.QtGui import QCloseEvent, QIcon
|
||||
from PySide2.QtWidgets import (
|
||||
QAction,
|
||||
@ -22,7 +22,9 @@ from game import Game, VERSION, persistency
|
||||
from game.debriefing import Debriefing
|
||||
from game.layout import LAYOUTS
|
||||
from game.server import EventStream, GameContext
|
||||
from game.server.dependencies import QtCallbacks, QtContext
|
||||
from game.server.security import ApiKeyManager
|
||||
from game.theater import MissionTarget
|
||||
from qt_ui import liberation_install
|
||||
from qt_ui.dialogs import Dialog
|
||||
from qt_ui.models import GameModel
|
||||
@ -46,6 +48,8 @@ from qt_ui.windows.stats.QStatsWindow import QStatsWindow
|
||||
|
||||
|
||||
class QLiberationWindow(QMainWindow):
|
||||
new_package_signal = Signal(MissionTarget)
|
||||
|
||||
def __init__(self, game: Optional[Game], new_map: bool) -> None:
|
||||
super().__init__()
|
||||
|
||||
@ -56,6 +60,12 @@ class QLiberationWindow(QMainWindow):
|
||||
self.sim_controller.sim_update.connect(EventStream.put_nowait)
|
||||
self.game_model = GameModel(game, self.sim_controller)
|
||||
GameContext.set_model(self.game_model)
|
||||
self.new_package_signal.connect(
|
||||
lambda target: Dialog.open_new_package_dialog(target, self)
|
||||
)
|
||||
QtContext.set_callbacks(
|
||||
QtCallbacks(lambda target: self.new_package_signal.emit(target))
|
||||
)
|
||||
Dialog.set_game(self.game_model)
|
||||
self.ato_panel = QAirTaskingOrderPanel(self.game_model)
|
||||
self.info_panel = QInfoPanel(self.game)
|
||||
|
||||
@ -124,10 +124,10 @@ class QBaseMenu2(QDialog):
|
||||
return self.game_model.game.settings.enable_base_capture_cheat
|
||||
|
||||
def cheat_capture(self) -> None:
|
||||
self.cp.capture(self.game_model.game, for_player=not self.cp.captured)
|
||||
events = GameUpdateEvents()
|
||||
self.cp.capture(self.game_model.game, events, for_player=not self.cp.captured)
|
||||
# Reinitialized ground planners and the like. The ATO needs to be reset because
|
||||
# missions planned against the flipped base are no longer valid.
|
||||
events = GameUpdateEvents()
|
||||
self.game_model.game.initialize_turn(events)
|
||||
EventStream.put_nowait(events)
|
||||
GameUpdateSignal.get_instance().updateGame(self.game_model.game)
|
||||
|
||||
@ -57,8 +57,10 @@ class QGroundForcesStrategy(QGroupBox):
|
||||
amount *= -1
|
||||
self.cp.base.affect_strength(amount)
|
||||
enemy_point.base.affect_strength(-amount)
|
||||
front_line = self.cp.front_line_with(enemy_point)
|
||||
front_line.update_position()
|
||||
events = GameUpdateEvents().update_front_line(front_line)
|
||||
# Clear the ATO to replan missions affected by the front line.
|
||||
events = GameUpdateEvents()
|
||||
self.game.initialize_turn(events)
|
||||
EventStream.put_nowait(events)
|
||||
GameUpdateSignal.get_instance().updateGame(self.game)
|
||||
|
||||
@ -308,6 +308,18 @@ function handleStreamedEvents(events) {
|
||||
for (const flightId of events.deleted_flights) {
|
||||
Flight.popId(flightId).clear();
|
||||
}
|
||||
|
||||
for (const frontLine of events.new_front_lines) {
|
||||
new FrontLine(frontLine).draw();
|
||||
}
|
||||
|
||||
for (const id of events.updated_front_lines) {
|
||||
FrontLine.withId(id).update();
|
||||
}
|
||||
|
||||
for (const id of events.deleted_front_lines) {
|
||||
FrontLine.popId(id).clear();
|
||||
}
|
||||
}
|
||||
|
||||
function recenterMap(center) {
|
||||
@ -622,15 +634,64 @@ function drawSupplyRoutes() {
|
||||
});
|
||||
}
|
||||
|
||||
class FrontLine {
|
||||
static registered = {};
|
||||
|
||||
constructor(frontLine) {
|
||||
this.id = frontLine.id;
|
||||
this.extents = frontLine.extents;
|
||||
this.line = null;
|
||||
FrontLine.register(this);
|
||||
}
|
||||
|
||||
static register(frontLine) {
|
||||
FrontLine.registered[frontLine.id] = frontLine;
|
||||
}
|
||||
|
||||
static unregister(id) {
|
||||
delete FrontLine.registered[id];
|
||||
}
|
||||
|
||||
static withId(id) {
|
||||
return FrontLine.registered[id];
|
||||
}
|
||||
|
||||
static popId(id) {
|
||||
const front = FrontLine.withId(id);
|
||||
FrontLine.unregister(id);
|
||||
return front;
|
||||
}
|
||||
|
||||
update() {
|
||||
getJson(`/front-lines/${this.id}`).then((frontLine) => {
|
||||
this.extents = frontLine.extents;
|
||||
this.draw();
|
||||
});
|
||||
}
|
||||
|
||||
clear() {
|
||||
if (this.line != null) {
|
||||
this.line.removeFrom(frontLinesLayer);
|
||||
}
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.clear();
|
||||
this.line = L.polyline(this.extents, { weight: 8, color: "#fe7d0a" })
|
||||
.on("contextmenu", () => this.openNewPackageDialog())
|
||||
.addTo(frontLinesLayer);
|
||||
}
|
||||
|
||||
openNewPackageDialog() {
|
||||
postJson(`/package-dialog/front-line/${this.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
function drawFrontLines() {
|
||||
frontLinesLayer.clearLayers();
|
||||
getJson("/front-lines").then((frontLines) => {
|
||||
for (const front of frontLines) {
|
||||
L.polyline(front.extents, { weight: 8, color: "#fe7d0a" })
|
||||
.on("contextmenu", function () {
|
||||
front.showPackageDialog();
|
||||
})
|
||||
.addTo(frontLinesLayer);
|
||||
new FrontLine(front).draw();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user