mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Support display of dead flights.
This commit is contained in:
parent
053a1287c9
commit
005090fbcd
@ -10,6 +10,7 @@ from dcs.planes import C_101CC, C_101EB, Su_33
|
|||||||
from game.savecompat import has_save_compat_for
|
from game.savecompat import has_save_compat_for
|
||||||
from .flightroster import FlightRoster
|
from .flightroster import FlightRoster
|
||||||
from .flightstate import FlightState, Uninitialized
|
from .flightstate import FlightState, Uninitialized
|
||||||
|
from .flightstate.killed import Killed
|
||||||
from .loadouts import Loadout
|
from .loadouts import Loadout
|
||||||
from ..sidc import (
|
from ..sidc import (
|
||||||
AirEntity,
|
AirEntity,
|
||||||
@ -117,7 +118,7 @@ class Flight(SidcDescribable):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def sidc_status(self) -> Status:
|
def sidc_status(self) -> Status:
|
||||||
return Status.PRESENT
|
return Status.PRESENT if self.alive else Status.PRESENT_DESTROYED
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]:
|
def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]:
|
||||||
@ -202,7 +203,11 @@ class Flight(SidcDescribable):
|
|||||||
def should_halt_sim(self) -> bool:
|
def should_halt_sim(self) -> bool:
|
||||||
return self.state.should_halt_sim()
|
return self.state.should_halt_sim()
|
||||||
|
|
||||||
def kill(self, results: SimulationResults) -> None:
|
@property
|
||||||
|
def alive(self) -> bool:
|
||||||
|
return self.state.alive
|
||||||
|
|
||||||
|
def kill(self, results: SimulationResults, events: GameUpdateEvents) -> None:
|
||||||
# This is a bit messy while we're in transition from turn-based to turnless
|
# This is a bit messy while we're in transition from turn-based to turnless
|
||||||
# because we want the simulation to have minimal impact on the save game while
|
# because we want the simulation to have minimal impact on the save game while
|
||||||
# turns exist so that loading a game is essentially a way to reset the
|
# turns exist so that loading a game is essentially a way to reset the
|
||||||
@ -211,17 +216,17 @@ class Flight(SidcDescribable):
|
|||||||
# the UI to reflect that aircraft were lost and avoid generating those flights
|
# the UI to reflect that aircraft were lost and avoid generating those flights
|
||||||
# when the mission is generated.
|
# when the mission is generated.
|
||||||
#
|
#
|
||||||
# For now we do this by removing the flight from the ATO and logging the kill in
|
# For now we do this by logging the kill in the SimulationResults, which is
|
||||||
# the SimulationResults, which is similar to the Debriefing. If a flight is
|
# similar to the Debriefing. We also set the flight's state to Killed, which
|
||||||
# killed and the player saves and reloads, those pilots/aircraft will be
|
# will prevent it from being spawned in the mission and updates the SIDC.
|
||||||
# unusable until the next turn, but otherwise will survive.
|
# This does leave an opportunity for players to cheat since the UI won't stop
|
||||||
#
|
# them from cancelling a dead flight, returning the aircraft to the pool. Not a
|
||||||
# This is going to be extremely temporary since the solution for other killable
|
# big deal for now.
|
||||||
# game objects (killed SAMs, sinking carriers, bombed out runways) will not be
|
|
||||||
# so easily worked around.
|
|
||||||
# TODO: Support partial kills.
|
# TODO: Support partial kills.
|
||||||
# TODO: Remove empty packages from the ATO?
|
self.set_state(
|
||||||
self.package.remove_flight(self)
|
Killed(self.state.estimate_position(), self, self.squadron.settings)
|
||||||
|
)
|
||||||
|
events.update_flight(self)
|
||||||
for pilot in self.roster.pilots:
|
for pilot in self.roster.pilots:
|
||||||
if pilot is not None:
|
if pilot is not None:
|
||||||
results.kill_pilot(self, pilot)
|
results.kill_pilot(self, pilot)
|
||||||
|
|||||||
@ -20,6 +20,10 @@ class FlightState(ABC):
|
|||||||
self.flight = flight
|
self.flight = flight
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
|
|
||||||
|
@property
|
||||||
|
def alive(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def on_game_tick(
|
def on_game_tick(
|
||||||
self, events: GameUpdateEvents, time: datetime, duration: timedelta
|
self, events: GameUpdateEvents, time: datetime, duration: timedelta
|
||||||
|
|||||||
@ -5,14 +5,26 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
from dcs import Point
|
from dcs import Point
|
||||||
|
|
||||||
|
from game.settings import Settings
|
||||||
from .flightstate import FlightState
|
from .flightstate import FlightState
|
||||||
from ..starttype import StartType
|
from ..starttype import StartType
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from .. import Flight
|
||||||
from game.sim.gameupdateevents import GameUpdateEvents
|
from game.sim.gameupdateevents import GameUpdateEvents
|
||||||
|
|
||||||
|
|
||||||
class Completed(FlightState):
|
class Killed(FlightState):
|
||||||
|
def __init__(
|
||||||
|
self, last_position: Point, flight: Flight, settings: Settings
|
||||||
|
) -> None:
|
||||||
|
super().__init__(flight, settings)
|
||||||
|
self.last_position = last_position
|
||||||
|
|
||||||
|
@property
|
||||||
|
def alive(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
def on_game_tick(
|
def on_game_tick(
|
||||||
self, events: GameUpdateEvents, time: datetime, duration: timedelta
|
self, events: GameUpdateEvents, time: datetime, duration: timedelta
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -23,12 +35,11 @@ class Completed(FlightState):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def estimate_position(self) -> Point:
|
def estimate_position(self) -> Point:
|
||||||
return self.flight.arrival.position
|
return self.last_position
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def spawn_type(self) -> StartType:
|
def spawn_type(self) -> StartType:
|
||||||
# TODO: May want to do something different to make these uncontrolled?
|
raise RuntimeError("Attempted to spawn a dead flight")
|
||||||
return StartType.COLD
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self) -> str:
|
def description(self) -> str:
|
||||||
|
|||||||
@ -105,11 +105,12 @@ class AircraftGenerator:
|
|||||||
if not package.flights:
|
if not package.flights:
|
||||||
continue
|
continue
|
||||||
for flight in package.flights:
|
for flight in package.flights:
|
||||||
logging.info(f"Generating flight: {flight.unit_type}")
|
if flight.alive:
|
||||||
group = self.create_and_configure_flight(
|
logging.info(f"Generating flight: {flight.unit_type}")
|
||||||
flight, country, dynamic_runways
|
group = self.create_and_configure_flight(
|
||||||
)
|
flight, country, dynamic_runways
|
||||||
self.unit_map.add_aircraft(group, flight)
|
)
|
||||||
|
self.unit_map.add_aircraft(group, flight)
|
||||||
|
|
||||||
def spawn_unused_aircraft(
|
def spawn_unused_aircraft(
|
||||||
self, player_country: Country, enemy_country: Country
|
self, player_country: Country, enemy_country: Country
|
||||||
|
|||||||
@ -49,7 +49,7 @@ class AircraftSimulation:
|
|||||||
|
|
||||||
still_active = []
|
still_active = []
|
||||||
for combat in self.combats:
|
for combat in self.combats:
|
||||||
if combat.on_game_tick(duration, self.results):
|
if combat.on_game_tick(duration, self.results, events):
|
||||||
events.end_combat(combat)
|
events.end_combat(combat)
|
||||||
else:
|
else:
|
||||||
still_active.append(combat)
|
still_active.append(combat)
|
||||||
|
|||||||
@ -10,6 +10,7 @@ from shapely.ops import unary_union
|
|||||||
from game.ato.flightstate import InCombat, InFlight
|
from game.ato.flightstate import InCombat, InFlight
|
||||||
from game.utils import dcs_to_shapely_point
|
from game.utils import dcs_to_shapely_point
|
||||||
from .joinablecombat import JoinableCombat
|
from .joinablecombat import JoinableCombat
|
||||||
|
from .. import GameUpdateEvents
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game.ato import Flight
|
from game.ato import Flight
|
||||||
@ -60,7 +61,7 @@ class AirCombat(JoinableCombat):
|
|||||||
def describe(self) -> str:
|
def describe(self) -> str:
|
||||||
return f"in air-to-air combat"
|
return f"in air-to-air combat"
|
||||||
|
|
||||||
def resolve(self, results: SimulationResults) -> None:
|
def resolve(self, results: SimulationResults, events: GameUpdateEvents) -> None:
|
||||||
blue = []
|
blue = []
|
||||||
red = []
|
red = []
|
||||||
for flight in self.flights:
|
for flight in self.flights:
|
||||||
@ -87,11 +88,11 @@ class AirCombat(JoinableCombat):
|
|||||||
logging.debug(f"{self} auto-resolved as red victory")
|
logging.debug(f"{self} auto-resolved as red victory")
|
||||||
|
|
||||||
for flight in loser:
|
for flight in loser:
|
||||||
flight.kill(results)
|
flight.kill(results, events)
|
||||||
|
|
||||||
for flight in winner:
|
for flight in winner:
|
||||||
assert isinstance(flight.state, InCombat)
|
assert isinstance(flight.state, InCombat)
|
||||||
if random.random() / flight.count >= 0.5:
|
if random.random() / flight.count >= 0.5:
|
||||||
flight.kill(results)
|
flight.kill(results, events)
|
||||||
else:
|
else:
|
||||||
flight.state.exit_combat()
|
flight.state.exit_combat()
|
||||||
|
|||||||
@ -6,6 +6,7 @@ from datetime import timedelta
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from .frozencombat import FrozenCombat
|
from .frozencombat import FrozenCombat
|
||||||
|
from .. import GameUpdateEvents
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game.ato import Flight
|
from game.ato import Flight
|
||||||
@ -26,7 +27,7 @@ class AtIp(FrozenCombat):
|
|||||||
def iter_flights(self) -> Iterator[Flight]:
|
def iter_flights(self) -> Iterator[Flight]:
|
||||||
yield self.flight
|
yield self.flight
|
||||||
|
|
||||||
def resolve(self, results: SimulationResults) -> None:
|
def resolve(self, results: SimulationResults, events: GameUpdateEvents) -> None:
|
||||||
logging.debug(
|
logging.debug(
|
||||||
f"{self.flight} attack on {self.flight.package.target} auto-resolved with "
|
f"{self.flight} attack on {self.flight.package.target} auto-resolved with "
|
||||||
"mission failure but no losses"
|
"mission failure but no losses"
|
||||||
|
|||||||
@ -8,6 +8,7 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
from game.ato.flightstate import InCombat
|
from game.ato.flightstate import InCombat
|
||||||
from .frozencombat import FrozenCombat
|
from .frozencombat import FrozenCombat
|
||||||
|
from .. import GameUpdateEvents
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game.ato import Flight
|
from game.ato import Flight
|
||||||
@ -36,11 +37,11 @@ class DefendingSam(FrozenCombat):
|
|||||||
def iter_flights(self) -> Iterator[Flight]:
|
def iter_flights(self) -> Iterator[Flight]:
|
||||||
yield self.flight
|
yield self.flight
|
||||||
|
|
||||||
def resolve(self, results: SimulationResults) -> None:
|
def resolve(self, results: SimulationResults, events: GameUpdateEvents) -> None:
|
||||||
assert isinstance(self.flight.state, InCombat)
|
assert isinstance(self.flight.state, InCombat)
|
||||||
if random.random() / self.flight.count >= 0.5:
|
if random.random() / self.flight.count >= 0.5:
|
||||||
logging.debug(f"Air defense combat auto-resolved with {self.flight} lost")
|
logging.debug(f"Air defense combat auto-resolved with {self.flight} lost")
|
||||||
self.flight.kill(results)
|
self.flight.kill(results, events)
|
||||||
else:
|
else:
|
||||||
logging.debug(
|
logging.debug(
|
||||||
f"Air defense combat auto-resolved with {self.flight} surviving"
|
f"Air defense combat auto-resolved with {self.flight} surviving"
|
||||||
|
|||||||
@ -7,6 +7,7 @@ from datetime import timedelta
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from game.ato.flightstate import InCombat, InFlight
|
from game.ato.flightstate import InCombat, InFlight
|
||||||
|
from .. import GameUpdateEvents
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game.ato import Flight
|
from game.ato import Flight
|
||||||
@ -19,15 +20,17 @@ class FrozenCombat(ABC):
|
|||||||
self.freeze_duration = freeze_duration
|
self.freeze_duration = freeze_duration
|
||||||
self.elapsed_time = timedelta()
|
self.elapsed_time = timedelta()
|
||||||
|
|
||||||
def on_game_tick(self, duration: timedelta, results: SimulationResults) -> bool:
|
def on_game_tick(
|
||||||
|
self, duration: timedelta, results: SimulationResults, events: GameUpdateEvents
|
||||||
|
) -> bool:
|
||||||
self.elapsed_time += duration
|
self.elapsed_time += duration
|
||||||
if self.elapsed_time >= self.freeze_duration:
|
if self.elapsed_time >= self.freeze_duration:
|
||||||
self.resolve(results)
|
self.resolve(results, events)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def resolve(self, results: SimulationResults) -> None:
|
def resolve(self, results: SimulationResults, events: GameUpdateEvents) -> None:
|
||||||
...
|
...
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user