mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Simplfy fast forward settings, introduce ability to skip combat instead of resolving. (#3448)
This PR simplifies fast forward settings and introduces the ability to skip combat instead of resolving.
This commit is contained in:
parent
5d0ddea753
commit
df43d2eed6
@ -7,10 +7,12 @@ Saves from 11.x are not compatible with 12.0.0.
|
||||
* **[Engine]** Support for DCS 2.9.8.1214.
|
||||
* **[Campaign]** Flights are assigned different callsigns appropriate to the faction.
|
||||
* **[Campaign]** Removed deprecated settings for generating persistent and invulnerable AWACs and tankers.
|
||||
* **[Mission Generation]** Added option to skip combat when fast forwarding, which progresses fast forward as if the combat did not occur. Simplified fast forward settings by consolidating "Fast forward mission to first contact" and "Player missions interrupt fast forward" into a single setting and expanding options for "Auto-resolve combat during fast-forward (WIP)".
|
||||
* **[Mods]** F/A-18 E/F/G Super Hornet mod version updated to 2.3.
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[Mission Generation]** Fixed aircraft not spawning correctly on CVNs, LHAs and FARPs.
|
||||
* **[Campaign]** Do not allow aircraft from a captured control point to retreat if the captured control point has a damaged runway.
|
||||
* **[Campaign]** Do not allow ground units to be transferred to LHAs, CVNs or off map spawns.
|
||||
|
||||
|
||||
@ -8,6 +8,9 @@ from .atdeparture import AtDeparture
|
||||
from .taxi import Taxi
|
||||
from ..starttype import StartType
|
||||
|
||||
from game.settings.settings import FastForwardStopCondition
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.ato.flight import Flight
|
||||
from game.settings import Settings
|
||||
@ -37,7 +40,8 @@ class StartUp(AtDeparture):
|
||||
def should_halt_sim(self) -> bool:
|
||||
if (
|
||||
self.flight.client_count > 0
|
||||
and self.settings.player_mission_interrupts_sim_at is StartType.COLD
|
||||
and self.settings.fast_forward_stop_condition
|
||||
== FastForwardStopCondition.PLAYER_STARTUP
|
||||
):
|
||||
logging.info(
|
||||
f"Interrupting simulation because {self.flight} has players and has "
|
||||
|
||||
@ -9,6 +9,8 @@ from .navigating import Navigating
|
||||
from ..starttype import StartType
|
||||
from ...utils import LBS_TO_KG
|
||||
|
||||
from game.settings.settings import FastForwardStopCondition
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.ato.flight import Flight
|
||||
from game.settings import Settings
|
||||
@ -45,7 +47,8 @@ class Takeoff(AtDeparture):
|
||||
def should_halt_sim(self) -> bool:
|
||||
if (
|
||||
self.flight.client_count > 0
|
||||
and self.settings.player_mission_interrupts_sim_at is StartType.RUNWAY
|
||||
and self.settings.fast_forward_stop_condition
|
||||
== FastForwardStopCondition.PLAYER_TAKEOFF
|
||||
):
|
||||
logging.info(
|
||||
f"Interrupting simulation because {self.flight} has players and has "
|
||||
|
||||
@ -8,6 +8,8 @@ from .atdeparture import AtDeparture
|
||||
from .takeoff import Takeoff
|
||||
from ..starttype import StartType
|
||||
|
||||
from game.settings.settings import FastForwardStopCondition
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.ato.flight import Flight
|
||||
from game.settings import Settings
|
||||
@ -37,7 +39,8 @@ class Taxi(AtDeparture):
|
||||
def should_halt_sim(self) -> bool:
|
||||
if (
|
||||
self.flight.client_count > 0
|
||||
and self.settings.player_mission_interrupts_sim_at is StartType.WARM
|
||||
and self.settings.fast_forward_stop_condition
|
||||
== FastForwardStopCondition.PLAYER_TAXI
|
||||
):
|
||||
logging.info(
|
||||
f"Interrupting simulation because {self.flight} has players and has "
|
||||
|
||||
@ -28,6 +28,23 @@ class AutoAtoBehavior(Enum):
|
||||
Prefer = "Prefer player pilots"
|
||||
|
||||
|
||||
@unique
|
||||
class FastForwardStopCondition(Enum):
|
||||
DISABLED = "Fast forward disabled"
|
||||
FIRST_CONTACT = "First contact"
|
||||
PLAYER_TAKEOFF = "Player takeoff time"
|
||||
PLAYER_TAXI = "Player taxi time"
|
||||
PLAYER_STARTUP = "Player startup time"
|
||||
MANUAL = "Manual fast forward control"
|
||||
|
||||
|
||||
@unique
|
||||
class CombatResolutionMethod(Enum):
|
||||
PAUSE = "Pause simulation"
|
||||
RESOLVE = "Resolve combat"
|
||||
SKIP = "Skip combat"
|
||||
|
||||
|
||||
DIFFICULTY_PAGE = "Difficulty"
|
||||
|
||||
AI_DIFFICULTY_SECTION = "AI Difficulty"
|
||||
@ -293,7 +310,7 @@ class Settings:
|
||||
"Tactical commander",
|
||||
page=MISSION_GENERATOR_PAGE,
|
||||
section=COMMANDERS_SECTION,
|
||||
default=1,
|
||||
default=0,
|
||||
min=0,
|
||||
max=100,
|
||||
)
|
||||
@ -309,7 +326,7 @@ class Settings:
|
||||
"Observer",
|
||||
page=MISSION_GENERATOR_PAGE,
|
||||
section=COMMANDERS_SECTION,
|
||||
default=1,
|
||||
default=0,
|
||||
min=0,
|
||||
max=100,
|
||||
)
|
||||
@ -327,19 +344,6 @@ class Settings:
|
||||
"run out of fuel when players would not."
|
||||
),
|
||||
)
|
||||
fast_forward_to_first_contact: bool = boolean_option(
|
||||
"Fast forward mission to first contact (WIP)",
|
||||
page=MISSION_GENERATOR_PAGE,
|
||||
section=GAMEPLAY_SECTION,
|
||||
default=False,
|
||||
detail=(
|
||||
"If enabled, the mission will be generated at the point of first contact. "
|
||||
"If you enable this option, you will not be able to create new flights "
|
||||
'after pressing "TAKE OFF". Doing so will create an error the next time '
|
||||
'you press "TAKE OFF". Save your game first if you want to make '
|
||||
"modifications."
|
||||
),
|
||||
)
|
||||
reload_pre_sim_checkpoint_on_abort: bool = boolean_option(
|
||||
"Reset mission to pre-take off conditions on abort",
|
||||
page=MISSION_GENERATOR_PAGE,
|
||||
@ -351,37 +355,44 @@ class Settings:
|
||||
"your game after aborting take off."
|
||||
),
|
||||
)
|
||||
player_mission_interrupts_sim_at: Optional[StartType] = choices_option(
|
||||
"Player missions interrupt fast forward",
|
||||
fast_forward_stop_condition: FastForwardStopCondition = choices_option(
|
||||
"Fast forward until",
|
||||
page=MISSION_GENERATOR_PAGE,
|
||||
section=GAMEPLAY_SECTION,
|
||||
default=None,
|
||||
default=FastForwardStopCondition.DISABLED,
|
||||
choices={
|
||||
"Never": None,
|
||||
"At startup time": StartType.COLD,
|
||||
"At taxi time": StartType.WARM,
|
||||
"At takeoff time": StartType.RUNWAY,
|
||||
"No fast forward": FastForwardStopCondition.DISABLED,
|
||||
"Player startup time": FastForwardStopCondition.PLAYER_STARTUP,
|
||||
"Player taxi time": FastForwardStopCondition.PLAYER_TAXI,
|
||||
"Player takeoff time": FastForwardStopCondition.PLAYER_TAKEOFF,
|
||||
"First contact": FastForwardStopCondition.FIRST_CONTACT,
|
||||
"Manual": FastForwardStopCondition.MANUAL,
|
||||
},
|
||||
detail=(
|
||||
"Determines what player mission states will interrupt fast-forwarding to "
|
||||
"first contact, if enabled. If never is selected player missions will not "
|
||||
"impact simulation and player missions may be generated mid-flight. The "
|
||||
"other options will cause the mission to be generated as soon as a player "
|
||||
"mission reaches the set state or at first contact, whichever comes first."
|
||||
"Determines when fast forwarding stops: "
|
||||
"No fast forward: disables fast forward. "
|
||||
"Player startup time: fast forward until player startup time. "
|
||||
"Player taxi time: fast forward until player taxi time. "
|
||||
"Player takeoff time: fast forward until player takeoff time. "
|
||||
"First contact: fast forward until first contact between blue and red units. "
|
||||
"Manual: manually control fast forward. Show manual controls with --show-sim-speed-controls."
|
||||
),
|
||||
)
|
||||
auto_resolve_combat: bool = boolean_option(
|
||||
"Auto-resolve combat during fast-forward (WIP)",
|
||||
combat_resolution_method: CombatResolutionMethod = choices_option(
|
||||
"Resolve combat when fast forwarding by",
|
||||
page=MISSION_GENERATOR_PAGE,
|
||||
section=GAMEPLAY_SECTION,
|
||||
default=False,
|
||||
default=CombatResolutionMethod.PAUSE,
|
||||
choices={
|
||||
"Pause": CombatResolutionMethod.PAUSE,
|
||||
"Resolving combat (WIP)": CombatResolutionMethod.RESOLVE,
|
||||
"Skipping combat": CombatResolutionMethod.SKIP,
|
||||
},
|
||||
detail=(
|
||||
'Requires a "Player missions interrupt fast forward" setting other than '
|
||||
'"Never" If enabled, aircraft entering combat during fast forward will have'
|
||||
"their combat auto-resolved after a period of time. This allows the "
|
||||
"simulation to advance further into the mission before requiring mission "
|
||||
"generation, but simulation is currently very rudimentary so may result in "
|
||||
"huge losses."
|
||||
"Determines what happens when combat occurs when fast forwarding. "
|
||||
"Pause: pause fast forward and generate mission. Fast forwarding may stop before the condition specified in the above setting. "
|
||||
"Resolving combat (WIP): auto resolve combat. This method is very rudimentary and will result in large losses. "
|
||||
"Skipping combat: skip combat as if it did not occur."
|
||||
),
|
||||
)
|
||||
supercarrier: bool = boolean_option(
|
||||
|
||||
@ -10,6 +10,7 @@ from typing_extensions import TYPE_CHECKING
|
||||
from game.ato.flightstate import (
|
||||
Uninitialized,
|
||||
)
|
||||
from game.settings.settings import FastForwardStopCondition, CombatResolutionMethod
|
||||
from .combat import CombatInitiator, FrozenCombat
|
||||
from .gameupdateevents import GameUpdateEvents
|
||||
from .simulationresults import SimulationResults
|
||||
@ -32,7 +33,7 @@ class AircraftSimulation:
|
||||
def on_game_tick(
|
||||
self, events: GameUpdateEvents, time: datetime, duration: timedelta
|
||||
) -> None:
|
||||
if not self.game.settings.auto_resolve_combat and self.combats:
|
||||
if not self._auto_resolve_combat() and self.combats:
|
||||
logging.error(
|
||||
"Cannot resume simulation because aircraft are in combat and "
|
||||
"auto-resolve is disabled"
|
||||
@ -42,7 +43,13 @@ class AircraftSimulation:
|
||||
|
||||
still_active = []
|
||||
for combat in self.combats:
|
||||
if combat.on_game_tick(time, duration, self.results, events):
|
||||
if combat.on_game_tick(
|
||||
time,
|
||||
duration,
|
||||
self.results,
|
||||
events,
|
||||
self.game.settings.combat_resolution_method,
|
||||
):
|
||||
events.end_combat(combat)
|
||||
else:
|
||||
still_active.append(combat)
|
||||
@ -61,7 +68,7 @@ class AircraftSimulation:
|
||||
events.complete_simulation()
|
||||
return
|
||||
|
||||
if not self.game.settings.auto_resolve_combat and self.combats:
|
||||
if not self._auto_resolve_combat() and self.combats:
|
||||
events.complete_simulation()
|
||||
|
||||
def set_initial_flight_states(self) -> None:
|
||||
@ -80,3 +87,11 @@ class AircraftSimulation:
|
||||
)
|
||||
for package in packages:
|
||||
yield from package.flights
|
||||
|
||||
def _auto_resolve_combat(self) -> bool:
|
||||
return (
|
||||
self.game.settings.fast_forward_stop_condition
|
||||
!= FastForwardStopCondition.DISABLED
|
||||
and self.game.settings.combat_resolution_method
|
||||
!= CombatResolutionMethod.PAUSE
|
||||
)
|
||||
|
||||
@ -8,6 +8,7 @@ from typing import TYPE_CHECKING
|
||||
from shapely.ops import unary_union
|
||||
|
||||
from game.ato.flightstate import InCombat, InFlight
|
||||
from game.settings.settings import CombatResolutionMethod
|
||||
from game.utils import dcs_to_shapely_point
|
||||
from .joinablecombat import JoinableCombat
|
||||
from .. import GameUpdateEvents
|
||||
@ -67,7 +68,15 @@ class AirCombat(JoinableCombat):
|
||||
events: GameUpdateEvents,
|
||||
time: datetime,
|
||||
elapsed_time: timedelta,
|
||||
resolution_method: CombatResolutionMethod,
|
||||
) -> None:
|
||||
|
||||
if resolution_method is CombatResolutionMethod.SKIP:
|
||||
for flight in self.flights:
|
||||
assert isinstance(flight.state, InCombat)
|
||||
flight.state.exit_combat(events, time, elapsed_time)
|
||||
return
|
||||
|
||||
blue = []
|
||||
red = []
|
||||
for flight in self.flights:
|
||||
|
||||
@ -8,6 +8,7 @@ from typing import TYPE_CHECKING
|
||||
from .frozencombat import FrozenCombat
|
||||
from .. import GameUpdateEvents
|
||||
from ...ato.flightstate import InCombat
|
||||
from game.settings.settings import CombatResolutionMethod
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.ato import Flight
|
||||
@ -34,6 +35,7 @@ class AtIp(FrozenCombat):
|
||||
events: GameUpdateEvents,
|
||||
time: datetime,
|
||||
elapsed_time: timedelta,
|
||||
resolution_method: CombatResolutionMethod,
|
||||
) -> None:
|
||||
logging.debug(
|
||||
f"{self.flight} attack on {self.flight.package.target} auto-resolved with "
|
||||
|
||||
@ -7,6 +7,7 @@ from datetime import datetime, timedelta
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from game.ato.flightstate import InCombat
|
||||
from game.settings.settings import CombatResolutionMethod
|
||||
from .frozencombat import FrozenCombat
|
||||
from .. import GameUpdateEvents
|
||||
|
||||
@ -43,8 +44,14 @@ class DefendingSam(FrozenCombat):
|
||||
events: GameUpdateEvents,
|
||||
time: datetime,
|
||||
elapsed_time: timedelta,
|
||||
resolution_method: CombatResolutionMethod,
|
||||
) -> None:
|
||||
assert isinstance(self.flight.state, InCombat)
|
||||
|
||||
if resolution_method is CombatResolutionMethod.SKIP:
|
||||
self.flight.state.exit_combat(events, time, elapsed_time)
|
||||
return
|
||||
|
||||
if random.random() >= 0.5:
|
||||
logging.debug(f"Air defense combat auto-resolved with {self.flight} lost")
|
||||
self.flight.kill(results, events)
|
||||
|
||||
@ -7,6 +7,7 @@ from datetime import datetime, timedelta
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from game.ato.flightstate import InCombat, InFlight
|
||||
from game.settings.settings import CombatResolutionMethod
|
||||
from .. import GameUpdateEvents
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -26,10 +27,11 @@ class FrozenCombat(ABC):
|
||||
duration: timedelta,
|
||||
results: SimulationResults,
|
||||
events: GameUpdateEvents,
|
||||
resolution_method: CombatResolutionMethod,
|
||||
) -> bool:
|
||||
self.elapsed_time += duration
|
||||
if self.elapsed_time >= self.freeze_duration:
|
||||
self.resolve(results, events, time, self.elapsed_time)
|
||||
self.resolve(results, events, time, self.elapsed_time, resolution_method)
|
||||
return True
|
||||
return False
|
||||
|
||||
@ -40,6 +42,7 @@ class FrozenCombat(ABC):
|
||||
events: GameUpdateEvents,
|
||||
time: datetime,
|
||||
elapsed_time: timedelta,
|
||||
resolution_method: CombatResolutionMethod,
|
||||
) -> None: ...
|
||||
|
||||
@abstractmethod
|
||||
|
||||
@ -65,7 +65,7 @@ class GameLoop:
|
||||
self.start()
|
||||
logging.info("Running sim to first contact")
|
||||
while not self.completed:
|
||||
self.tick(suppress_events=True)
|
||||
self.tick(suppress_events=False)
|
||||
|
||||
def pause_and_generate_miz(self, output: Path) -> None:
|
||||
self.pause()
|
||||
|
||||
@ -16,6 +16,7 @@ from game import Game, persistence
|
||||
from game.ato.package import Package
|
||||
from game.ato.traveltime import TotEstimator
|
||||
from game.profiling import logged_duration
|
||||
from game.settings.settings import FastForwardStopCondition
|
||||
from game.utils import meters
|
||||
from qt_ui.models import GameModel
|
||||
from qt_ui.simcontroller import SimController
|
||||
@ -248,50 +249,6 @@ class QTopPanel(QFrame):
|
||||
mbox.exec_()
|
||||
return True
|
||||
|
||||
def check_valid_autoresolve_settings(self) -> bool:
|
||||
if not self.game.settings.fast_forward_to_first_contact:
|
||||
return True
|
||||
|
||||
if not self.game.settings.auto_resolve_combat:
|
||||
return True
|
||||
|
||||
has_clients = self.ato_has_clients()
|
||||
if (
|
||||
has_clients
|
||||
and self.game.settings.player_mission_interrupts_sim_at is not None
|
||||
):
|
||||
return True
|
||||
|
||||
if has_clients:
|
||||
message = textwrap.dedent(
|
||||
"""\
|
||||
You have enabled settings to fast forward and to auto-resolve combat,
|
||||
but have not selected any interrupt condition. Fast forward will never
|
||||
stop with your current settings. To use auto- resolve, you must choose a
|
||||
"Player missions interrupt fast forward" setting other than "Never".
|
||||
"""
|
||||
)
|
||||
else:
|
||||
message = textwrap.dedent(
|
||||
"""\
|
||||
You have enabled settings to fast forward and to auto-resolve combat,
|
||||
but have no players. Fast forward will never stop with your current
|
||||
settings. Auto-resolve and fast forward cannot be used without player
|
||||
flights and a "Player missions interrupt fast forward" setting other
|
||||
than "Never".
|
||||
"""
|
||||
)
|
||||
|
||||
mbox = QMessageBox(
|
||||
QMessageBox.Icon.Critical,
|
||||
"Incompatible fast-forward settings",
|
||||
message,
|
||||
parent=self,
|
||||
)
|
||||
mbox.setEscapeButton(mbox.addButton(QMessageBox.StandardButton.Close))
|
||||
mbox.exec()
|
||||
return False
|
||||
|
||||
def launch_mission(self):
|
||||
"""Finishes planning and waits for mission completion."""
|
||||
if not self.ato_has_clients() and not self.confirm_no_client_launch():
|
||||
@ -307,10 +264,10 @@ class QTopPanel(QFrame):
|
||||
if not self.confirm_negative_start_time(negative_starts):
|
||||
return
|
||||
|
||||
if not self.check_valid_autoresolve_settings():
|
||||
return
|
||||
|
||||
if self.game.settings.fast_forward_to_first_contact:
|
||||
if self.game.settings.fast_forward_stop_condition not in [
|
||||
FastForwardStopCondition.DISABLED,
|
||||
FastForwardStopCondition.MANUAL,
|
||||
]:
|
||||
with logged_duration("Simulating to first contact"):
|
||||
self.sim_controller.run_to_first_contact()
|
||||
self.sim_controller.generate_miz(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user