Add option for setting desired mission length.

This commit is contained in:
Schneefl0cke 2021-05-11 12:13:15 +02:00 committed by GitHub
parent 747683e9e8
commit 56abd0bb7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 114 additions and 44 deletions

View File

@ -7,6 +7,7 @@ Saves from 2.5 are not compatible with 3.0.
* **[Campaign]** Ground units can now be transferred by road, airlift, and cargo ship. See https://github.com/dcs-liberation/dcs_liberation/wiki/Unit-Transfers for more information. * **[Campaign]** Ground units can now be transferred by road, airlift, and cargo ship. See https://github.com/dcs-liberation/dcs_liberation/wiki/Unit-Transfers for more information.
* **[Campaign]** Ground units can no longer be sold. To move units to a new location, transfer them. * **[Campaign]** Ground units can no longer be sold. To move units to a new location, transfer them.
* **[Campaign]** Ground units must now be recruited at a base with a factory and transferred to their destination. When buying units in the UI, the purchase will automatically be fulfilled at the closest factory, and a transfer will be created on the next turn. * **[Campaign]** Ground units must now be recruited at a base with a factory and transferred to their destination. When buying units in the UI, the purchase will automatically be fulfilled at the closest factory, and a transfer will be created on the next turn.
* **[Campaign AI]** Every 30 minutes the AI will plan a CAP, so players can customize their mission better.
* **[UI]** Campaigns generated for an older or newer version of the game will now be marked as incompatible. They can still be played, but bugs may be present. * **[UI]** Campaigns generated for an older or newer version of the game will now be marked as incompatible. They can still be played, but bugs may be present.
* **[Modding]** Campaigns now choose locations for factories to spawn. * **[Modding]** Campaigns now choose locations for factories to spawn.
* **[Modding]** Can now install custom factions to <DCS saved games>/Liberation/Factions instead of the Liberation install directory. * **[Modding]** Can now install custom factions to <DCS saved games>/Liberation/Factions instead of the Liberation install directory.

View File

@ -1,4 +1,5 @@
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import timedelta
from typing import Dict, Optional from typing import Dict, Optional
from dcs.forcedoptions import ForcedOptions from dcs.forcedoptions import ForcedOptions
@ -25,6 +26,9 @@ class Settings:
default_start_type: str = "Cold" default_start_type: str = "Cold"
# Mission specific
desired_player_mission_duration: timedelta = timedelta(minutes=90)
# Campaign management # Campaign management
automate_runway_repair: bool = False automate_runway_repair: bool = False
automate_front_line_reinforcements: bool = False automate_front_line_reinforcements: bool = False

View File

@ -583,26 +583,18 @@ class CoalitionMissionPlanner:
# Find friendly CPs within 100 nmi from an enemy airfield, plan CAP. # Find friendly CPs within 100 nmi from an enemy airfield, plan CAP.
for cp in self.objective_finder.vulnerable_control_points(): for cp in self.objective_finder.vulnerable_control_points():
# Plan three rounds of CAP to give ~90 minutes coverage. Spacing # Plan CAP in such a way, that it is established during the whole desired mission length
# these out appropriately is done in stagger_missions. for _ in range(
yield ProposedMission( 0,
cp, int(self.game.settings.desired_player_mission_duration.total_seconds()),
[ int(self.faction.doctrine.cap_duration.total_seconds()),
ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE), ):
], yield ProposedMission(
) cp,
yield ProposedMission( [
cp, ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE),
[ ],
ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE), )
],
)
yield ProposedMission(
cp,
[
ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE),
],
)
# Find front lines, plan CAS. # Find front lines, plan CAS.
for front_line in self.objective_finder.front_lines(): for front_line in self.objective_finder.front_lines():

View File

@ -1,7 +1,9 @@
from PySide2 import QtWidgets
from PySide2.QtCore import Qt from PySide2.QtCore import Qt
from PySide2.QtWidgets import QGridLayout, QLabel, QSlider from PySide2.QtWidgets import QGridLayout, QLabel, QSlider
from typing import Optional
from qt_ui.widgets.floatspinners import TenthsSpinner from qt_ui.widgets.floatspinners import TenthsSpinner
from datetime import timedelta
class TenthsSpinSlider(QGridLayout): class TenthsSpinSlider(QGridLayout):
@ -23,3 +25,68 @@ class TenthsSpinSlider(QGridLayout):
@property @property
def value(self) -> float: def value(self) -> float:
return self.spinner.value() / 10 return self.spinner.value() / 10
class TimeInputs(QtWidgets.QGridLayout):
def __init__(self, label: str, initial: timedelta) -> None:
super().__init__()
self.addWidget(QtWidgets.QLabel(label), 0, 0)
minimum_minutes = 30
maximum_minutes = 150
initial_minutes = int(initial.total_seconds() / 60)
slider = QtWidgets.QSlider(Qt.Horizontal)
slider.setMinimum(minimum_minutes)
slider.setMaximum(maximum_minutes)
slider.setValue(initial_minutes)
self.spinner = TimeSpinner(minimum_minutes, maximum_minutes, initial_minutes)
slider.valueChanged.connect(lambda x: self.spinner.setValue(x))
self.spinner.valueChanged.connect(lambda x: slider.setValue(x))
self.addWidget(slider, 1, 0)
self.addWidget(self.spinner, 1, 1)
@property
def value(self) -> timedelta:
return timedelta(minutes=self.spinner.value())
class TimeSpinner(QtWidgets.QSpinBox):
def __init__(
self,
minimum: Optional[int] = None,
maximum: Optional[int] = None,
initial: Optional[int] = None,
) -> None:
super().__init__()
if minimum is not None:
self.setMinimum(minimum)
if maximum is not None:
self.setMaximum(maximum)
if initial is not None:
self.setValue(initial)
def textFromValue(self, val: int) -> str:
return f"{val} minutes"
class CurrencySpinner(QtWidgets.QSpinBox):
def __init__(
self,
minimum: Optional[int] = None,
maximum: Optional[int] = None,
initial: Optional[int] = None,
) -> None:
super().__init__()
if minimum is not None:
self.setMinimum(minimum)
if maximum is not None:
self.setMaximum(maximum)
if initial is not None:
self.setValue(initial)
def textFromValue(self, val: int) -> str:
return f"${val}"

View File

@ -7,12 +7,13 @@ from PySide2 import QtGui, QtWidgets
from PySide2.QtCore import QItemSelectionModel, QPoint, Qt, QDate from PySide2.QtCore import QItemSelectionModel, QPoint, Qt, QDate
from PySide2.QtWidgets import QVBoxLayout, QTextEdit, QLabel from PySide2.QtWidgets import QVBoxLayout, QTextEdit, QLabel
from jinja2 import Environment, FileSystemLoader, select_autoescape from jinja2 import Environment, FileSystemLoader, select_autoescape
from datetime import timedelta
from game import db from game import db
from game.settings import Settings from game.settings import Settings
from game.theater.start_generator import GameGenerator, GeneratorSettings from game.theater.start_generator import GameGenerator, GeneratorSettings
from qt_ui.widgets.QLiberationCalendar import QLiberationCalendar from qt_ui.widgets.QLiberationCalendar import QLiberationCalendar
from qt_ui.widgets.spinsliders import TenthsSpinSlider from qt_ui.widgets.spinsliders import TenthsSpinSlider, TimeInputs, CurrencySpinner
from qt_ui.windows.newgame.QCampaignList import ( from qt_ui.windows.newgame.QCampaignList import (
Campaign, Campaign,
QCampaignList, QCampaignList,
@ -33,8 +34,8 @@ jinja_env = Environment(
lstrip_blocks=True, lstrip_blocks=True,
) )
DEFAULT_BUDGET = 2000 DEFAULT_BUDGET = 2000
DEFAULT_MISSION_LENGTH: timedelta = timedelta(minutes=90)
class NewGameWizard(QtWidgets.QWizard): class NewGameWizard(QtWidgets.QWizard):
@ -85,6 +86,9 @@ class NewGameWizard(QtWidgets.QWizard):
automate_front_line_reinforcements=self.field( automate_front_line_reinforcements=self.field(
"automate_front_line_purchases" "automate_front_line_purchases"
), ),
desired_player_mission_duration=timedelta(
minutes=self.field("desired_player_mission_duration")
),
automate_aircraft_reinforcements=self.field("automate_aircraft_purchases"), automate_aircraft_reinforcements=self.field("automate_aircraft_purchases"),
supercarrier=self.field("supercarrier"), supercarrier=self.field("supercarrier"),
enable_new_ground_unit_recruitment=self.field( enable_new_ground_unit_recruitment=self.field(
@ -428,26 +432,6 @@ class TheaterConfiguration(QtWidgets.QWizardPage):
self.setLayout(layout) self.setLayout(layout)
class CurrencySpinner(QtWidgets.QSpinBox):
def __init__(
self,
minimum: Optional[int] = None,
maximum: Optional[int] = None,
initial: Optional[int] = None,
) -> None:
super().__init__()
if minimum is not None:
self.setMinimum(minimum)
if maximum is not None:
self.setMaximum(maximum)
if initial is not None:
self.setValue(initial)
def textFromValue(self, val: int) -> str:
return f"${val}"
class BudgetInputs(QtWidgets.QGridLayout): class BudgetInputs(QtWidgets.QGridLayout):
def __init__(self, label: str) -> None: def __init__(self, label: str) -> None:
super().__init__() super().__init__()
@ -565,6 +549,12 @@ class GeneratorOptions(QtWidgets.QWizardPage):
self.registerField("no_player_navy", no_player_navy) self.registerField("no_player_navy", no_player_navy)
no_enemy_navy = QtWidgets.QCheckBox() no_enemy_navy = QtWidgets.QCheckBox()
self.registerField("no_enemy_navy", no_enemy_navy) self.registerField("no_enemy_navy", no_enemy_navy)
desired_player_mission_duration = TimeInputs(
"Desired mission duration", DEFAULT_MISSION_LENGTH
)
self.registerField(
"desired_player_mission_duration", desired_player_mission_duration.spinner
)
generatorLayout = QtWidgets.QGridLayout() generatorLayout = QtWidgets.QGridLayout()
generatorLayout.addWidget(QtWidgets.QLabel("No Aircraft Carriers"), 1, 0) generatorLayout.addWidget(QtWidgets.QLabel("No Aircraft Carriers"), 1, 0)
@ -577,6 +567,7 @@ class GeneratorOptions(QtWidgets.QWizardPage):
generatorLayout.addWidget(no_player_navy, 4, 1) generatorLayout.addWidget(no_player_navy, 4, 1)
generatorLayout.addWidget(QtWidgets.QLabel("No Enemy Navy"), 5, 0) generatorLayout.addWidget(QtWidgets.QLabel("No Enemy Navy"), 5, 0)
generatorLayout.addWidget(no_enemy_navy, 5, 1) generatorLayout.addWidget(no_enemy_navy, 5, 1)
generatorLayout.addLayout(desired_player_mission_duration, 6, 0)
generatorSettingsGroup.setLayout(generatorLayout) generatorSettingsGroup.setLayout(generatorLayout)
mlayout = QVBoxLayout() mlayout = QVBoxLayout()

View File

@ -26,7 +26,7 @@ from game.game import Game
from game.infos.information import Information from game.infos.information import Information
from game.settings import Settings from game.settings import Settings
from qt_ui.widgets.QLabeledWidget import QLabeledWidget from qt_ui.widgets.QLabeledWidget import QLabeledWidget
from qt_ui.widgets.spinsliders import TenthsSpinSlider from qt_ui.widgets.spinsliders import TenthsSpinSlider, TimeInputs
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.finances.QFinancesMenu import QHorizontalSeparationLine from qt_ui.windows.finances.QFinancesMenu import QHorizontalSeparationLine
from qt_ui.windows.settings.plugins import PluginOptionsPage, PluginsPage from qt_ui.windows.settings.plugins import PluginOptionsPage, PluginsPage
@ -471,6 +471,14 @@ class QSettingsWindow(QDialog):
"spawned immediately. AI wingmen may begin startup immediately." "spawned immediately. AI wingmen may begin startup immediately."
) )
self.desired_player_mission_duration = TimeInputs(
"Desired mission duration",
self.game.settings.desired_player_mission_duration,
)
self.desired_player_mission_duration.spinner.valueChanged.connect(
self.applySettings
)
self.gameplayLayout.addWidget(QLabel("Use Supercarrier Module"), 0, 0) self.gameplayLayout.addWidget(QLabel("Use Supercarrier Module"), 0, 0)
self.gameplayLayout.addWidget(self.supercarrier, 0, 1, Qt.AlignRight) self.gameplayLayout.addWidget(self.supercarrier, 0, 1, Qt.AlignRight)
self.gameplayLayout.addWidget(QLabel("Put Objective Markers on Map"), 1, 0) self.gameplayLayout.addWidget(QLabel("Put Objective Markers on Map"), 1, 0)
@ -483,6 +491,9 @@ class QSettingsWindow(QDialog):
) )
self.gameplayLayout.addWidget(dark_kneeboard_label, 2, 0) self.gameplayLayout.addWidget(dark_kneeboard_label, 2, 0)
self.gameplayLayout.addWidget(self.generate_dark_kneeboard, 2, 1, Qt.AlignRight) self.gameplayLayout.addWidget(self.generate_dark_kneeboard, 2, 1, Qt.AlignRight)
self.gameplayLayout.addLayout(
self.desired_player_mission_duration, 5, 0, Qt.AlignRight
)
spawn_players_immediately_tooltip = ( spawn_players_immediately_tooltip = (
"Always spawns player aircraft immediately, even if their start time is " "Always spawns player aircraft immediately, even if their start time is "
@ -695,6 +706,10 @@ class QSettingsWindow(QDialog):
self.generate_dark_kneeboard.isChecked() self.generate_dark_kneeboard.isChecked()
) )
self.game.settings.desired_player_mission_duration = (
self.desired_player_mission_duration.value
)
self.game.settings.perf_red_alert_state = self.red_alert.isChecked() self.game.settings.perf_red_alert_state = self.red_alert.isChecked()
self.game.settings.perf_smoke_gen = self.smoke.isChecked() self.game.settings.perf_smoke_gen = self.smoke.isChecked()
self.game.settings.perf_smoke_spacing = self.smoke_spacing.value() self.game.settings.perf_smoke_spacing = self.smoke_spacing.value()