mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Add campaign property for campaign start time.
This field is optional. Omitting the field (or using only a date instead of a full timestamp) will use the old behavior of picking a random daylight hour to start the campaign. This doesn't include any UI in the new game wizard yet. This is only a campaign yaml option. https://github.com/dcs-liberation/dcs_liberation/issues/2400
This commit is contained in:
parent
b6da2d8e62
commit
c5b50ceeae
@ -1,12 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
from typing import Any, Dict, Tuple
|
||||
|
||||
import yaml
|
||||
from packaging.version import Version
|
||||
@ -49,7 +48,8 @@ class Campaign:
|
||||
|
||||
recommended_player_faction: str
|
||||
recommended_enemy_faction: str
|
||||
recommended_start_date: Optional[datetime.date]
|
||||
recommended_start_date: datetime.date | None
|
||||
recommended_start_time: datetime.time | None
|
||||
|
||||
recommended_player_money: int
|
||||
recommended_enemy_money: int
|
||||
@ -64,10 +64,7 @@ class Campaign:
|
||||
@classmethod
|
||||
def from_file(cls, path: Path) -> Campaign:
|
||||
with path.open() as campaign_file:
|
||||
if path.suffix.lower() == ".yaml":
|
||||
data = yaml.safe_load(campaign_file)
|
||||
else:
|
||||
data = json.load(campaign_file)
|
||||
data = yaml.safe_load(campaign_file)
|
||||
|
||||
sanitized_theater = data["theater"].replace(" ", "")
|
||||
version_field = data.get("version", "0")
|
||||
@ -80,14 +77,15 @@ class Campaign:
|
||||
version = Version(str(version_field))
|
||||
|
||||
start_date_raw = data.get("recommended_start_date")
|
||||
|
||||
# YAML automatically parses dates, but while we still support JSON campaigns we
|
||||
# need to be able to handle parsing dates from strings ourselves as well.
|
||||
start_date: Optional[datetime.date]
|
||||
if isinstance(start_date_raw, str):
|
||||
start_date = datetime.date.fromisoformat(start_date_raw)
|
||||
# YAML automatically parses dates.
|
||||
start_date: datetime.date | None
|
||||
start_time: datetime.time | None = None
|
||||
if isinstance(start_date_raw, datetime.datetime):
|
||||
start_date = start_date_raw.date()
|
||||
start_time = start_date_raw.time()
|
||||
elif isinstance(start_date_raw, datetime.date):
|
||||
start_date = start_date_raw
|
||||
start_time = None
|
||||
elif start_date_raw is None:
|
||||
start_date = None
|
||||
else:
|
||||
@ -104,6 +102,7 @@ class Campaign:
|
||||
data.get("recommended_player_faction", "USA 2005"),
|
||||
data.get("recommended_enemy_faction", "Russia 1990"),
|
||||
start_date,
|
||||
start_time,
|
||||
data.get("recommended_player_money", DEFAULT_BUDGET),
|
||||
data.get("recommended_enemy_money", DEFAULT_BUDGET),
|
||||
data.get("recommended_player_income_multiplier", 1.0),
|
||||
|
||||
27
game/game.py
27
game/game.py
@ -4,7 +4,7 @@ import itertools
|
||||
import logging
|
||||
import math
|
||||
from collections.abc import Iterator
|
||||
from datetime import date, datetime, timedelta
|
||||
from datetime import date, datetime, time, timedelta
|
||||
from enum import Enum
|
||||
from typing import Any, List, TYPE_CHECKING, Type, Union, cast
|
||||
from uuid import UUID
|
||||
@ -95,6 +95,7 @@ class Game:
|
||||
theater: ConflictTheater,
|
||||
air_wing_config: CampaignAirWingConfig,
|
||||
start_date: datetime,
|
||||
start_time: time | None,
|
||||
settings: Settings,
|
||||
player_budget: float,
|
||||
enemy_budget: float,
|
||||
@ -119,7 +120,15 @@ class Game:
|
||||
|
||||
self.db = GameDb()
|
||||
|
||||
self.conditions = self.generate_conditions()
|
||||
if start_time is None:
|
||||
self.time_of_day_offset_for_start_time = list(TimeOfDay).index(
|
||||
TimeOfDay.Day
|
||||
)
|
||||
else:
|
||||
self.time_of_day_offset_for_start_time = list(TimeOfDay).index(
|
||||
self.theater.daytime_map.best_guess_time_of_day_at(start_time)
|
||||
)
|
||||
self.conditions = self.generate_conditions(forced_time=start_time)
|
||||
|
||||
self.sanitize_sides(player_faction, enemy_faction)
|
||||
self.blue = Coalition(self, player_faction, player_budget, player=True)
|
||||
@ -154,9 +163,13 @@ class Game:
|
||||
def transit_network_for(self, player: bool) -> TransitNetwork:
|
||||
return self.coalition_for(player).transit_network
|
||||
|
||||
def generate_conditions(self) -> Conditions:
|
||||
def generate_conditions(self, forced_time: time | None = None) -> Conditions:
|
||||
return Conditions.generate(
|
||||
self.theater, self.current_day, self.current_turn_time_of_day, self.settings
|
||||
self.theater,
|
||||
self.current_day,
|
||||
self.current_turn_time_of_day,
|
||||
self.settings,
|
||||
forced_time=forced_time,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@ -428,11 +441,7 @@ class Game:
|
||||
|
||||
@property
|
||||
def current_turn_time_of_day(self) -> TimeOfDay:
|
||||
# We don't actually advance time between turn 0 and turn 1. Clamp the turn value
|
||||
# to 1 so we get the same answer for 0 and 1. We clamp to 1 rather than 0
|
||||
# because historically we've started campaigns in day rather than in dawn. We
|
||||
# can either start at 1, or we could re-order the enum.
|
||||
tod_turn = max(1, self.turn)
|
||||
tod_turn = max(0, self.turn - 1) + self.time_of_day_offset_for_start_time
|
||||
return list(TimeOfDay)[tod_turn % 4]
|
||||
|
||||
@property
|
||||
|
||||
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from datetime import datetime, time
|
||||
from typing import List
|
||||
|
||||
import dcs.statics
|
||||
@ -17,7 +17,6 @@ from game.theater.theatergroundobject import (
|
||||
BuildingGroundObject,
|
||||
IadsBuildingGroundObject,
|
||||
)
|
||||
from .theatergroup import SceneryUnit, TheaterGroup, IadsGroundGroup, IadsRole
|
||||
from game.utils import Heading, escape_string_for_lua
|
||||
from game.version import VERSION
|
||||
from . import (
|
||||
@ -27,10 +26,7 @@ from . import (
|
||||
Fob,
|
||||
OffMapSpawn,
|
||||
)
|
||||
from ..campaignloader.campaignairwingconfig import CampaignAirWingConfig
|
||||
from ..data.building_data import IADS_BUILDINGS
|
||||
from ..data.groups import GroupTask
|
||||
from ..armedforces.forcegroup import ForceGroup
|
||||
from .theatergroup import IadsGroundGroup, IadsRole, SceneryUnit, TheaterGroup
|
||||
from ..armedforces.armedforces import ArmedForces
|
||||
from ..armedforces.forcegroup import ForceGroup
|
||||
from ..campaignloader.campaignairwingconfig import CampaignAirWingConfig
|
||||
@ -42,6 +38,7 @@ from ..settings import Settings
|
||||
@dataclass(frozen=True)
|
||||
class GeneratorSettings:
|
||||
start_date: datetime
|
||||
start_time: time | None
|
||||
player_budget: int
|
||||
enemy_budget: int
|
||||
inverted: bool
|
||||
@ -98,6 +95,7 @@ class GameGenerator:
|
||||
theater=self.theater,
|
||||
air_wing_config=self.air_wing_config,
|
||||
start_date=self.generator_settings.start_date,
|
||||
start_time=self.generator_settings.start_time,
|
||||
settings=self.settings,
|
||||
player_budget=self.generator_settings.player_budget,
|
||||
enemy_budget=self.generator_settings.enemy_budget,
|
||||
|
||||
@ -150,4 +150,9 @@ VERSION = _build_version_string()
|
||||
#: * Campaign files can optionally define the iads configuration
|
||||
#: It is possible to define if the campaign supports advanced iads
|
||||
#:
|
||||
CAMPAIGN_FORMAT_VERSION = (10, 2)
|
||||
#: Version 10.3
|
||||
#: * Campaign files can optionally include a start time in their recommended_start_date
|
||||
#: field. For example, `recommended_start_data: 2022-08-31 13:30:00` will have the
|
||||
#: first turn start at 13:30. If omitted, or if only a date is given, the mission will
|
||||
#: start at a random hour in the middle of the day as before.
|
||||
CAMPAIGN_FORMAT_VERSION = (10, 3)
|
||||
|
||||
@ -298,10 +298,16 @@ class Conditions:
|
||||
day: datetime.date,
|
||||
time_of_day: TimeOfDay,
|
||||
settings: Settings,
|
||||
forced_time: datetime.time | None = None,
|
||||
) -> Conditions:
|
||||
_start_time = cls.generate_start_time(
|
||||
theater, day, time_of_day, settings.night_disabled
|
||||
)
|
||||
# The time might be forced by the campaign for the first turn.
|
||||
if forced_time is not None:
|
||||
_start_time = datetime.datetime.combine(day, forced_time)
|
||||
else:
|
||||
_start_time = cls.generate_start_time(
|
||||
theater, day, time_of_day, settings.night_disabled
|
||||
)
|
||||
|
||||
return cls(
|
||||
time_of_day=time_of_day,
|
||||
start_time=_start_time,
|
||||
|
||||
@ -286,6 +286,7 @@ def create_game(
|
||||
),
|
||||
GeneratorSettings(
|
||||
start_date=start_date,
|
||||
start_time=campaign.recommended_start_time,
|
||||
player_budget=DEFAULT_BUDGET,
|
||||
enemy_budget=DEFAULT_BUDGET,
|
||||
inverted=inverted,
|
||||
|
||||
@ -145,6 +145,7 @@ class NewGameWizard(QtWidgets.QWizard):
|
||||
)
|
||||
generator_settings = GeneratorSettings(
|
||||
start_date=start_date,
|
||||
start_time=campaign.recommended_start_time,
|
||||
player_budget=int(self.field("starting_money")),
|
||||
enemy_budget=int(self.field("enemy_starting_money")),
|
||||
# QSlider forces integers, so we use 1 to 50 and divide by 10 to
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user