mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Fix unit variants to actually allow variance.
This was always the intent, but apparently it wasn't implemented correctly. All properties of the unit type can now be overridden per variant.
This commit is contained in:
parent
1f3eee90f1
commit
58d8203c83
@ -24,6 +24,7 @@ Saves from 8.x are not compatible with 9.0.0.
|
||||
* **[Mission Generation]** Restored previous AI behavior for anti-ship missions. A DCS update caused only a single aircraft in a flight to attack. The full flight will now attack like they used to.
|
||||
* **[Mission Generation]** Fix generation of OCA Runway missions to allow LGBs to be used.
|
||||
* **[Mission Generation]** Fixed AI flights flying far too slowly toward NAV points.
|
||||
* **[Modding]** Unit variants can now actually override base unit type properties.
|
||||
* **[New Game Wizard]** Factions are reset to default after clicking "Back" to Theater Configuration screen.
|
||||
* **[Plugins]** Fixed Lua errors in Skynet plugin that would occur whenever one coalition had no IADS nodes.
|
||||
* **[UI]** Fixed deleting waypoints in custom flight plans deleting the wrong waypoint.
|
||||
|
||||
@ -7,7 +7,6 @@ from functools import cache, cached_property
|
||||
from pathlib import Path
|
||||
from typing import Any, ClassVar, Dict, Iterator, Optional, TYPE_CHECKING, Type
|
||||
|
||||
import yaml
|
||||
from dcs.helicopters import helicopter_map
|
||||
from dcs.planes import plane_map
|
||||
from dcs.unitpropertydescription import UnitPropertyDescription
|
||||
@ -395,11 +394,11 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
||||
|
||||
@staticmethod
|
||||
def _set_props_overrides(
|
||||
config: Dict[str, Any], aircraft: Type[FlyingType], data_path: Path
|
||||
config: Dict[str, Any], aircraft: Type[FlyingType]
|
||||
) -> None:
|
||||
if aircraft.property_defaults is None:
|
||||
logging.warning(
|
||||
f"'{data_path.name}' attempted to set default prop that does not exist."
|
||||
f"'{aircraft.id}' attempted to set default prop that does not exist."
|
||||
)
|
||||
else:
|
||||
for k in config:
|
||||
@ -407,25 +406,23 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
||||
aircraft.property_defaults[k] = config[k]
|
||||
else:
|
||||
logging.warning(
|
||||
f"'{data_path.name}' attempted to set default prop '{k}' that does not exist"
|
||||
f"'{aircraft.id}' attempted to set default prop '{k}' that does not exist"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _each_variant_of(cls, aircraft: Type[FlyingType]) -> Iterator[AircraftType]:
|
||||
def _data_directory(cls) -> Path:
|
||||
return Path("resources/units/aircraft")
|
||||
|
||||
@classmethod
|
||||
def _variant_from_dict(
|
||||
cls, aircraft: Type[FlyingType], variant_id: str, data: dict[str, Any]
|
||||
) -> AircraftType:
|
||||
from game.ato.flighttype import FlightType
|
||||
|
||||
data_path = Path("resources/units/aircraft") / f"{aircraft.id}.yaml"
|
||||
if not data_path.exists():
|
||||
logging.warning(f"No data for {aircraft.id}; it will not be available")
|
||||
return
|
||||
|
||||
with data_path.open(encoding="utf-8") as data_file:
|
||||
data = yaml.safe_load(data_file)
|
||||
|
||||
try:
|
||||
price = data["price"]
|
||||
except KeyError as ex:
|
||||
raise KeyError(f"Missing required price field: {data_path}") from ex
|
||||
raise KeyError(f"Missing required price field") from ex
|
||||
|
||||
radio_config = RadioConfig.from_data(data.get("radios", {}))
|
||||
patrol_config = PatrolConfig.from_data(data.get("patrol", {}))
|
||||
@ -468,50 +465,49 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
||||
|
||||
prop_overrides = data.get("default_overrides")
|
||||
if prop_overrides is not None:
|
||||
cls._set_props_overrides(prop_overrides, aircraft, data_path)
|
||||
cls._set_props_overrides(prop_overrides, aircraft)
|
||||
|
||||
task_priorities: dict[FlightType, int] = {}
|
||||
for task_name, priority in data.get("tasks", {}).items():
|
||||
task_priorities[FlightType(task_name)] = priority
|
||||
|
||||
for variant in data.get("variants", [aircraft.id]):
|
||||
yield AircraftType(
|
||||
dcs_unit_type=aircraft,
|
||||
variant_id=variant,
|
||||
description=data.get(
|
||||
"description",
|
||||
f"No data. <a href=\"https://google.com/search?q=DCS+{variant.replace(' ', '+')}\"><span style=\"color:#FFFFFF\">Google {variant}</span></a>",
|
||||
),
|
||||
year_introduced=introduction,
|
||||
country_of_origin=data.get("origin", "No data."),
|
||||
manufacturer=data.get("manufacturer", "No data."),
|
||||
role=data.get("role", "No data."),
|
||||
price=price,
|
||||
carrier_capable=data.get("carrier_capable", False),
|
||||
lha_capable=data.get("lha_capable", False),
|
||||
always_keeps_gun=data.get("always_keeps_gun", False),
|
||||
gunfighter=data.get("gunfighter", False),
|
||||
max_group_size=data.get("max_group_size", aircraft.group_size_max),
|
||||
patrol_altitude=patrol_config.altitude,
|
||||
patrol_speed=patrol_config.speed,
|
||||
max_mission_range=mission_range,
|
||||
fuel_consumption=fuel_consumption,
|
||||
default_livery=data.get("default_livery"),
|
||||
intra_flight_radio=radio_config.intra_flight,
|
||||
channel_allocator=radio_config.channel_allocator,
|
||||
channel_namer=radio_config.channel_namer,
|
||||
kneeboard_units=units,
|
||||
utc_kneeboard=data.get("utc_kneeboard", False),
|
||||
unit_class=unit_class,
|
||||
cabin_size=data.get("cabin_size", 10 if aircraft.helicopter else 0),
|
||||
can_carry_crates=data.get("can_carry_crates", aircraft.helicopter),
|
||||
task_priorities=task_priorities,
|
||||
has_built_in_target_pod=data.get("has_built_in_target_pod", False),
|
||||
laser_code_configs=[
|
||||
LaserCodeConfig.from_yaml(d) for d in data.get("laser_codes", [])
|
||||
],
|
||||
use_f15e_waypoint_names=data.get("use_f15e_waypoint_names", False),
|
||||
)
|
||||
return AircraftType(
|
||||
dcs_unit_type=aircraft,
|
||||
variant_id=variant_id,
|
||||
description=data.get(
|
||||
"description",
|
||||
f"No data. <a href=\"https://google.com/search?q=DCS+{variant_id.replace(' ', '+')}\"><span style=\"color:#FFFFFF\">Google {variant_id}</span></a>",
|
||||
),
|
||||
year_introduced=introduction,
|
||||
country_of_origin=data.get("origin", "No data."),
|
||||
manufacturer=data.get("manufacturer", "No data."),
|
||||
role=data.get("role", "No data."),
|
||||
price=price,
|
||||
carrier_capable=data.get("carrier_capable", False),
|
||||
lha_capable=data.get("lha_capable", False),
|
||||
always_keeps_gun=data.get("always_keeps_gun", False),
|
||||
gunfighter=data.get("gunfighter", False),
|
||||
max_group_size=data.get("max_group_size", aircraft.group_size_max),
|
||||
patrol_altitude=patrol_config.altitude,
|
||||
patrol_speed=patrol_config.speed,
|
||||
max_mission_range=mission_range,
|
||||
fuel_consumption=fuel_consumption,
|
||||
default_livery=data.get("default_livery"),
|
||||
intra_flight_radio=radio_config.intra_flight,
|
||||
channel_allocator=radio_config.channel_allocator,
|
||||
channel_namer=radio_config.channel_namer,
|
||||
kneeboard_units=units,
|
||||
utc_kneeboard=data.get("utc_kneeboard", False),
|
||||
unit_class=unit_class,
|
||||
cabin_size=data.get("cabin_size", 10 if aircraft.helicopter else 0),
|
||||
can_carry_crates=data.get("can_carry_crates", aircraft.helicopter),
|
||||
task_priorities=task_priorities,
|
||||
has_built_in_target_pod=data.get("has_built_in_target_pod", False),
|
||||
laser_code_configs=[
|
||||
LaserCodeConfig.from_yaml(d) for d in data.get("laser_codes", [])
|
||||
],
|
||||
use_f15e_waypoint_names=data.get("use_f15e_waypoint_names", False),
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.variant_id)
|
||||
|
||||
@ -6,7 +6,6 @@ from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, ClassVar, Iterator, Optional, Type
|
||||
|
||||
import yaml
|
||||
from dcs.unittype import VehicleType
|
||||
from dcs.vehicles import vehicle_map
|
||||
|
||||
@ -99,15 +98,13 @@ class GroundUnitType(UnitType[Type[VehicleType]]):
|
||||
yield from vehicle_map.values()
|
||||
|
||||
@classmethod
|
||||
def _each_variant_of(cls, vehicle: Type[VehicleType]) -> Iterator[GroundUnitType]:
|
||||
data_path = Path("resources/units/ground_units") / f"{vehicle.id}.yaml"
|
||||
if not data_path.exists():
|
||||
logging.warning(f"No data for {vehicle.id}; it will not be available")
|
||||
return
|
||||
|
||||
with data_path.open(encoding="utf-8") as data_file:
|
||||
data = yaml.safe_load(data_file)
|
||||
def _data_directory(cls) -> Path:
|
||||
return Path("resources/units/ground_units")
|
||||
|
||||
@classmethod
|
||||
def _variant_from_dict(
|
||||
cls, vehicle: Type[VehicleType], variant_id: str, data: dict[str, Any]
|
||||
) -> GroundUnitType:
|
||||
try:
|
||||
introduction = data["introduced"]
|
||||
if introduction is None:
|
||||
@ -122,23 +119,22 @@ class GroundUnitType(UnitType[Type[VehicleType]]):
|
||||
else:
|
||||
unit_class = UnitClass(class_name)
|
||||
|
||||
for variant in data.get("variants", [vehicle.id]):
|
||||
yield GroundUnitType(
|
||||
dcs_unit_type=vehicle,
|
||||
unit_class=unit_class,
|
||||
spawn_weight=data.get("spawn_weight", 0),
|
||||
variant_id=variant,
|
||||
description=data.get(
|
||||
"description",
|
||||
f"No data. <a href=\"https://google.com/search?q=DCS+{variant.replace(' ', '+')}\"><span style=\"color:#FFFFFF\">Google {variant}</span></a>",
|
||||
),
|
||||
year_introduced=introduction,
|
||||
country_of_origin=data.get("origin", "No data."),
|
||||
manufacturer=data.get("manufacturer", "No data."),
|
||||
role=data.get("role", "No data."),
|
||||
price=data.get("price", 1),
|
||||
skynet_properties=SkynetProperties.from_data(
|
||||
data.get("skynet_properties", {})
|
||||
),
|
||||
reversed_heading=data.get("reversed_heading", False),
|
||||
)
|
||||
return GroundUnitType(
|
||||
dcs_unit_type=vehicle,
|
||||
unit_class=unit_class,
|
||||
spawn_weight=data.get("spawn_weight", 0),
|
||||
variant_id=variant_id,
|
||||
description=data.get(
|
||||
"description",
|
||||
f"No data. <a href=\"https://google.com/search?q=DCS+{variant_id.replace(' ', '+')}\"><span style=\"color:#FFFFFF\">Google {variant_id}</span></a>",
|
||||
),
|
||||
year_introduced=introduction,
|
||||
country_of_origin=data.get("origin", "No data."),
|
||||
manufacturer=data.get("manufacturer", "No data."),
|
||||
role=data.get("role", "No data."),
|
||||
price=data.get("price", 1),
|
||||
skynet_properties=SkynetProperties.from_data(
|
||||
data.get("skynet_properties", {})
|
||||
),
|
||||
reversed_heading=data.get("reversed_heading", False),
|
||||
)
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import ClassVar, Iterator, Type, Any
|
||||
|
||||
import yaml
|
||||
from dcs.ships import ship_map
|
||||
from dcs.unittype import ShipType
|
||||
|
||||
@ -55,15 +53,13 @@ class ShipUnitType(UnitType[Type[ShipType]]):
|
||||
yield from ship_map.values()
|
||||
|
||||
@classmethod
|
||||
def _each_variant_of(cls, ship: Type[ShipType]) -> Iterator[ShipUnitType]:
|
||||
data_path = Path("resources/units/ships") / f"{ship.id}.yaml"
|
||||
if not data_path.exists():
|
||||
logging.warning(f"No data for {ship.id}; it will not be available")
|
||||
return
|
||||
|
||||
with data_path.open(encoding="utf-8") as data_file:
|
||||
data = yaml.safe_load(data_file)
|
||||
def _data_directory(cls) -> Path:
|
||||
return Path("resources/units/ships")
|
||||
|
||||
@classmethod
|
||||
def _variant_from_dict(
|
||||
cls, ship: Type[ShipType], variant_id: str, data: dict[str, Any]
|
||||
) -> ShipUnitType:
|
||||
try:
|
||||
introduction = data["introduced"]
|
||||
if introduction is None:
|
||||
@ -74,18 +70,17 @@ class ShipUnitType(UnitType[Type[ShipType]]):
|
||||
class_name = data.get("class")
|
||||
unit_class = UnitClass(class_name)
|
||||
|
||||
for variant in data.get("variants", [ship.id]):
|
||||
yield ShipUnitType(
|
||||
dcs_unit_type=ship,
|
||||
unit_class=unit_class,
|
||||
variant_id=variant,
|
||||
description=data.get(
|
||||
"description",
|
||||
f"No data. <a href=\"https://google.com/search?q=DCS+{variant.replace(' ', '+')}\"><span style=\"color:#FFFFFF\">Google {variant}</span></a>",
|
||||
),
|
||||
year_introduced=introduction,
|
||||
country_of_origin=data.get("origin", "No data."),
|
||||
manufacturer=data.get("manufacturer", "No data."),
|
||||
role=data.get("role", "No data."),
|
||||
price=data.get("price"),
|
||||
)
|
||||
return ShipUnitType(
|
||||
dcs_unit_type=ship,
|
||||
unit_class=unit_class,
|
||||
variant_id=variant_id,
|
||||
description=data.get(
|
||||
"description",
|
||||
f"No data. <a href=\"https://google.com/search?q=DCS+{variant_id.replace(' ', '+')}\"><span style=\"color:#FFFFFF\">Google {variant_id}</span></a>",
|
||||
),
|
||||
year_introduced=introduction,
|
||||
country_of_origin=data.get("origin", "No data."),
|
||||
manufacturer=data.get("manufacturer", "No data."),
|
||||
role=data.get("role", "No data."),
|
||||
price=data["price"],
|
||||
)
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from abc import ABC
|
||||
from dataclasses import dataclass
|
||||
from functools import cached_property
|
||||
from typing import ClassVar, Generic, Iterator, Self, Type, TypeVar
|
||||
from pathlib import Path
|
||||
from typing import ClassVar, Generic, Iterator, Self, Type, TypeVar, Any
|
||||
|
||||
import yaml
|
||||
from dcs.unittype import UnitType as DcsUnitType
|
||||
|
||||
from game.data.units import UnitClass
|
||||
@ -49,8 +52,29 @@ class UnitType(ABC, Generic[DcsUnitTypeT]):
|
||||
def each_dcs_type() -> Iterator[DcsUnitTypeT]:
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def _data_directory(cls) -> Path:
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def _each_variant_of(cls, unit: DcsUnitTypeT) -> Iterator[Self]:
|
||||
data_path = cls._data_directory() / f"{unit.id}.yaml"
|
||||
if not data_path.exists():
|
||||
logging.warning(f"No data for {unit.id}; it will not be available")
|
||||
return
|
||||
|
||||
with data_path.open(encoding="utf-8") as data_file:
|
||||
data = yaml.safe_load(data_file)
|
||||
|
||||
for variant_id, variant_data in data.get("variants", {unit.id: {}}).items():
|
||||
if variant_data is None:
|
||||
variant_data = {}
|
||||
yield cls._variant_from_dict(unit, variant_id, data | variant_data)
|
||||
|
||||
@classmethod
|
||||
def _variant_from_dict(
|
||||
cls, dcs_unit_type: DcsUnitTypeT, variant_id: str, data: dict[str, Any]
|
||||
) -> Self:
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user