mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +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
82939a446b
commit
c630226e2d
@ -1,12 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, Optional, Tuple
|
from typing import Any, Dict, Tuple
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from packaging.version import Version
|
from packaging.version import Version
|
||||||
@ -49,7 +48,8 @@ class Campaign:
|
|||||||
|
|
||||||
recommended_player_faction: str
|
recommended_player_faction: str
|
||||||
recommended_enemy_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_player_money: int
|
||||||
recommended_enemy_money: int
|
recommended_enemy_money: int
|
||||||
@ -64,10 +64,7 @@ class Campaign:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def from_file(cls, path: Path) -> Campaign:
|
def from_file(cls, path: Path) -> Campaign:
|
||||||
with path.open() as campaign_file:
|
with path.open() as campaign_file:
|
||||||
if path.suffix == ".yaml":
|
|
||||||
data = yaml.safe_load(campaign_file)
|
data = yaml.safe_load(campaign_file)
|
||||||
else:
|
|
||||||
data = json.load(campaign_file)
|
|
||||||
|
|
||||||
sanitized_theater = data["theater"].replace(" ", "")
|
sanitized_theater = data["theater"].replace(" ", "")
|
||||||
version_field = data.get("version", "0")
|
version_field = data.get("version", "0")
|
||||||
@ -80,14 +77,15 @@ class Campaign:
|
|||||||
version = Version(str(version_field))
|
version = Version(str(version_field))
|
||||||
|
|
||||||
start_date_raw = data.get("recommended_start_date")
|
start_date_raw = data.get("recommended_start_date")
|
||||||
|
# YAML automatically parses dates.
|
||||||
# YAML automatically parses dates, but while we still support JSON campaigns we
|
start_date: datetime.date | None
|
||||||
# need to be able to handle parsing dates from strings ourselves as well.
|
start_time: datetime.time | None = None
|
||||||
start_date: Optional[datetime.date]
|
if isinstance(start_date_raw, datetime.datetime):
|
||||||
if isinstance(start_date_raw, str):
|
start_date = start_date_raw.date()
|
||||||
start_date = datetime.date.fromisoformat(start_date_raw)
|
start_time = start_date_raw.time()
|
||||||
elif isinstance(start_date_raw, datetime.date):
|
elif isinstance(start_date_raw, datetime.date):
|
||||||
start_date = start_date_raw
|
start_date = start_date_raw
|
||||||
|
start_time = None
|
||||||
elif start_date_raw is None:
|
elif start_date_raw is None:
|
||||||
start_date = None
|
start_date = None
|
||||||
else:
|
else:
|
||||||
@ -104,6 +102,7 @@ class Campaign:
|
|||||||
data.get("recommended_player_faction", "USA 2005"),
|
data.get("recommended_player_faction", "USA 2005"),
|
||||||
data.get("recommended_enemy_faction", "Russia 1990"),
|
data.get("recommended_enemy_faction", "Russia 1990"),
|
||||||
start_date,
|
start_date,
|
||||||
|
start_time,
|
||||||
data.get("recommended_player_money", DEFAULT_BUDGET),
|
data.get("recommended_player_money", DEFAULT_BUDGET),
|
||||||
data.get("recommended_enemy_money", DEFAULT_BUDGET),
|
data.get("recommended_enemy_money", DEFAULT_BUDGET),
|
||||||
data.get("recommended_player_income_multiplier", 1.0),
|
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 logging
|
||||||
import math
|
import math
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from datetime import date, datetime, timedelta
|
from datetime import date, datetime, time, timedelta
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, List, TYPE_CHECKING, Type, Union, cast
|
from typing import Any, List, TYPE_CHECKING, Type, Union, cast
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
@ -95,6 +95,7 @@ class Game:
|
|||||||
theater: ConflictTheater,
|
theater: ConflictTheater,
|
||||||
air_wing_config: CampaignAirWingConfig,
|
air_wing_config: CampaignAirWingConfig,
|
||||||
start_date: datetime,
|
start_date: datetime,
|
||||||
|
start_time: time | None,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
player_budget: float,
|
player_budget: float,
|
||||||
enemy_budget: float,
|
enemy_budget: float,
|
||||||
@ -119,7 +120,15 @@ class Game:
|
|||||||
|
|
||||||
self.db = GameDb()
|
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.sanitize_sides(player_faction, enemy_faction)
|
||||||
self.blue = Coalition(self, player_faction, player_budget, player=True)
|
self.blue = Coalition(self, player_faction, player_budget, player=True)
|
||||||
@ -154,9 +163,13 @@ class Game:
|
|||||||
def transit_network_for(self, player: bool) -> TransitNetwork:
|
def transit_network_for(self, player: bool) -> TransitNetwork:
|
||||||
return self.coalition_for(player).transit_network
|
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(
|
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
|
@staticmethod
|
||||||
@ -427,11 +440,7 @@ class Game:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def current_turn_time_of_day(self) -> TimeOfDay:
|
def current_turn_time_of_day(self) -> TimeOfDay:
|
||||||
# We don't actually advance time between turn 0 and turn 1. Clamp the turn value
|
tod_turn = max(0, self.turn - 1) + self.time_of_day_offset_for_start_time
|
||||||
# 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)
|
|
||||||
return list(TimeOfDay)[tod_turn % 4]
|
return list(TimeOfDay)[tod_turn % 4]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime, time
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
import dcs.statics
|
import dcs.statics
|
||||||
@ -17,7 +17,6 @@ from game.theater.theatergroundobject import (
|
|||||||
BuildingGroundObject,
|
BuildingGroundObject,
|
||||||
IadsBuildingGroundObject,
|
IadsBuildingGroundObject,
|
||||||
)
|
)
|
||||||
from .theatergroup import SceneryUnit, TheaterGroup, IadsGroundGroup, IadsRole
|
|
||||||
from game.utils import Heading, escape_string_for_lua
|
from game.utils import Heading, escape_string_for_lua
|
||||||
from game.version import VERSION
|
from game.version import VERSION
|
||||||
from . import (
|
from . import (
|
||||||
@ -27,10 +26,7 @@ from . import (
|
|||||||
Fob,
|
Fob,
|
||||||
OffMapSpawn,
|
OffMapSpawn,
|
||||||
)
|
)
|
||||||
from ..campaignloader.campaignairwingconfig import CampaignAirWingConfig
|
from .theatergroup import IadsGroundGroup, IadsRole, SceneryUnit, TheaterGroup
|
||||||
from ..data.building_data import IADS_BUILDINGS
|
|
||||||
from ..data.groups import GroupTask
|
|
||||||
from ..armedforces.forcegroup import ForceGroup
|
|
||||||
from ..armedforces.armedforces import ArmedForces
|
from ..armedforces.armedforces import ArmedForces
|
||||||
from ..armedforces.forcegroup import ForceGroup
|
from ..armedforces.forcegroup import ForceGroup
|
||||||
from ..campaignloader.campaignairwingconfig import CampaignAirWingConfig
|
from ..campaignloader.campaignairwingconfig import CampaignAirWingConfig
|
||||||
@ -42,6 +38,7 @@ from ..settings import Settings
|
|||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class GeneratorSettings:
|
class GeneratorSettings:
|
||||||
start_date: datetime
|
start_date: datetime
|
||||||
|
start_time: time | None
|
||||||
player_budget: int
|
player_budget: int
|
||||||
enemy_budget: int
|
enemy_budget: int
|
||||||
inverted: bool
|
inverted: bool
|
||||||
@ -96,6 +93,7 @@ class GameGenerator:
|
|||||||
theater=self.theater,
|
theater=self.theater,
|
||||||
air_wing_config=self.air_wing_config,
|
air_wing_config=self.air_wing_config,
|
||||||
start_date=self.generator_settings.start_date,
|
start_date=self.generator_settings.start_date,
|
||||||
|
start_time=self.generator_settings.start_time,
|
||||||
settings=self.settings,
|
settings=self.settings,
|
||||||
player_budget=self.generator_settings.player_budget,
|
player_budget=self.generator_settings.player_budget,
|
||||||
enemy_budget=self.generator_settings.enemy_budget,
|
enemy_budget=self.generator_settings.enemy_budget,
|
||||||
|
|||||||
@ -150,4 +150,9 @@ VERSION = _build_version_string()
|
|||||||
#: * Campaign files can optionally define the iads configuration
|
#: * Campaign files can optionally define the iads configuration
|
||||||
#: It is possible to define if the campaign supports advanced iads
|
#: 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,
|
day: datetime.date,
|
||||||
time_of_day: TimeOfDay,
|
time_of_day: TimeOfDay,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
|
forced_time: datetime.time | None = None,
|
||||||
) -> Conditions:
|
) -> Conditions:
|
||||||
|
# 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(
|
_start_time = cls.generate_start_time(
|
||||||
theater, day, time_of_day, settings.night_disabled
|
theater, day, time_of_day, settings.night_disabled
|
||||||
)
|
)
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
time_of_day=time_of_day,
|
time_of_day=time_of_day,
|
||||||
start_time=_start_time,
|
start_time=_start_time,
|
||||||
|
|||||||
@ -286,6 +286,7 @@ def create_game(
|
|||||||
),
|
),
|
||||||
GeneratorSettings(
|
GeneratorSettings(
|
||||||
start_date=start_date,
|
start_date=start_date,
|
||||||
|
start_time=campaign.recommended_start_time,
|
||||||
player_budget=DEFAULT_BUDGET,
|
player_budget=DEFAULT_BUDGET,
|
||||||
enemy_budget=DEFAULT_BUDGET,
|
enemy_budget=DEFAULT_BUDGET,
|
||||||
inverted=inverted,
|
inverted=inverted,
|
||||||
|
|||||||
@ -145,6 +145,7 @@ class NewGameWizard(QtWidgets.QWizard):
|
|||||||
)
|
)
|
||||||
generator_settings = GeneratorSettings(
|
generator_settings = GeneratorSettings(
|
||||||
start_date=start_date,
|
start_date=start_date,
|
||||||
|
start_time=campaign.recommended_start_time,
|
||||||
player_budget=int(self.field("starting_money")),
|
player_budget=int(self.field("starting_money")),
|
||||||
enemy_budget=int(self.field("enemy_starting_money")),
|
enemy_budget=int(self.field("enemy_starting_money")),
|
||||||
# QSlider forces integers, so we use 1 to 50 and divide by 10 to
|
# QSlider forces integers, so we use 1 to 50 and divide by 10 to
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user