From f5c7935993f43fad209a1a162e08163ac6ea5484 Mon Sep 17 00:00:00 2001 From: MetalStormGhost <89945461+MetalStormGhost@users.noreply.github.com> Date: Sat, 3 Dec 2022 17:11:22 +0200 Subject: [PATCH] GroundForcePainter for applying custom liveries to ground units and ships (#26) See #26 for more info --- changelog.md | 1 + game/factions/faction.py | 13 ++++ game/missiongenerator/convoygenerator.py | 3 + game/missiongenerator/flotgenerator.py | 17 ++++- game/missiongenerator/groundforcepainter.py | 82 +++++++++++++++++++++ game/missiongenerator/tgogenerator.py | 10 +++ 6 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 game/missiongenerator/groundforcepainter.py diff --git a/changelog.md b/changelog.md index 07ce7b54..bee9c7d8 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,7 @@ * **[Engine]** Support for DCS v2.8.0.32066. * **[Briefing]** Add tanker info to mission briefing * **[Campaign]** Add 3 new campaigns by Oscar Juliet from WRL +* **[Campaign]** Add ability to define livery overrides also for ground/naval units * **[Data]** Added data to support C-47 Skytrain. * **[Data]** Added data to support KS-19 & SON-9, including support for "AAA Site" layout. * **[Mission Generation]** Add option to configure the maximum front-line length in settings diff --git a/game/factions/faction.py b/game/factions/faction.py index bde8f2be..ea7dafec 100644 --- a/game/factions/faction.py +++ b/game/factions/faction.py @@ -116,6 +116,9 @@ class Faction: # List of default livery overrides liveries_overrides: Dict[AircraftType, List[str]] = field(default_factory=dict) + # List of default livery overrides for ground vehicles + liveries_overrides_ground_forces: Dict[str, List[str]] = field(default_factory=dict) + #: Set to True if the faction should force the "Unrestricted satnav" option #: for the mission. This option enables GPS for capable aircraft regardless #: of the time period or operator. For example, the CJTF "countries" don't @@ -284,6 +287,16 @@ class Faction: aircraft = AircraftType.named(name) faction.liveries_overrides[aircraft] = [s.lower() for s in livery] + # Load liveries override for ground forces + faction.liveries_overrides_ground_forces = {} + liveries_overrides_ground_forces = json.get( + "liveries_overrides_ground_forces", {} + ) + for vehicle_type, livery in liveries_overrides_ground_forces.items(): + faction.liveries_overrides_ground_forces[vehicle_type] = [ + s.lower() for s in livery + ] + faction.unrestricted_satnav = json.get("unrestricted_satnav", False) return faction diff --git a/game/missiongenerator/convoygenerator.py b/game/missiongenerator/convoygenerator.py index 2f30586d..2abf0795 100644 --- a/game/missiongenerator/convoygenerator.py +++ b/game/missiongenerator/convoygenerator.py @@ -10,6 +10,7 @@ from dcs.unit import Vehicle from dcs.unitgroup import VehicleGroup from game.dcs.groundunittype import GroundUnitType +from game.missiongenerator.groundforcepainter import GroundForcePainter from game.transfers import Convoy from game.unitmap import UnitMap from game.utils import kph @@ -72,6 +73,7 @@ class ConvoyGenerator: for_player: bool, ) -> VehicleGroup: country = self.mission.country(self.game.coalition_for(for_player).country_name) + faction = self.game.faction_for(for_player) unit_types = list(units.items()) main_unit_type, main_unit_count = unit_types[0] @@ -97,6 +99,7 @@ class ConvoyGenerator: v.position.x = position.x v.position.y = next(y) v.heading = 0 + GroundForcePainter(faction, v).apply_livery() group.add_unit(v) return group diff --git a/game/missiongenerator/flotgenerator.py b/game/missiongenerator/flotgenerator.py index cfc4ef99..62766bd7 100644 --- a/game/missiongenerator/flotgenerator.py +++ b/game/missiongenerator/flotgenerator.py @@ -1,5 +1,6 @@ from __future__ import annotations +import logging import math import random from typing import List, Optional, TYPE_CHECKING, Tuple @@ -43,6 +44,7 @@ from game.theater.controlpoint import ControlPoint from game.unitmap import UnitMap from game.utils import Heading from .frontlineconflictdescription import FrontLineConflictDescription +from .groundforcepainter import GroundForcePainter from .lasercoderegistry import LaserCodeRegistry from .missiondata import JtacInfo, MissionData @@ -221,7 +223,7 @@ class FlotGenerator: u = random.choices( manpads, weights=[m.spawn_weight for m in manpads] )[0] - self.mission.vehicle_group( + vg = self.mission.vehicle_group( side, namegen.next_infantry_name(side, u), u.dcs_unit_type, @@ -230,6 +232,8 @@ class FlotGenerator: heading=forward_heading.degrees, move_formation=PointAction.OffRoad, ) + vehicle = vg.units[0] + GroundForcePainter(faction, vehicle).apply_livery() return possible_infantry_units = set(faction.infantry_with_class(UnitClass.INFANTRY)) @@ -246,7 +250,7 @@ class FlotGenerator: weights=[u.spawn_weight for u in infantry_choices], k=INFANTRY_GROUP_SIZE, ) - self.mission.vehicle_group( + vg = self.mission.vehicle_group( side, namegen.next_infantry_name(side, units[0]), units[0].dcs_unit_type, @@ -255,10 +259,12 @@ class FlotGenerator: heading=forward_heading.degrees, move_formation=PointAction.OffRoad, ) + vehicle = vg.units[0] + GroundForcePainter(faction, vehicle).apply_livery() for unit in units[1:]: position = infantry_position.random_point_within(55, 5) - self.mission.vehicle_group( + vg = self.mission.vehicle_group( side, namegen.next_infantry_name(side, unit), unit.dcs_unit_type, @@ -267,6 +273,8 @@ class FlotGenerator: heading=forward_heading.degrees, move_formation=PointAction.OffRoad, ) + vehicle = vg.units[0] + GroundForcePainter(faction, vehicle).apply_livery() def _set_reform_waypoint( self, dcs_group: VehicleGroup, forward_heading: Heading @@ -769,6 +777,8 @@ class FlotGenerator: heading: Heading, ) -> VehicleGroup: cp = self.conflict.front_line.control_point_friendly_to(player) + faction = self.game.faction_for(player) + group = self.mission.vehicle_group( side, namegen.next_unit_name(side, unit_type), @@ -783,5 +793,6 @@ class FlotGenerator: for c in range(count): vehicle: Vehicle = group.units[c] vehicle.player_can_drive = True + GroundForcePainter(faction, vehicle).apply_livery() return group diff --git a/game/missiongenerator/groundforcepainter.py b/game/missiongenerator/groundforcepainter.py new file mode 100644 index 00000000..8631ee40 --- /dev/null +++ b/game/missiongenerator/groundforcepainter.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +import logging +import random +from typing import Any, Optional + +from dcs.unit import Ship +from dcs.unitgroup import Vehicle + +from game.factions.faction import Faction + + +class GroundForcePainter: + def __init__(self, faction: Faction, vehicle: Vehicle) -> None: + self.faction = faction + self.vehicle = vehicle + + def livery_from_faction(self) -> Optional[str]: + faction = self.faction + try: + if ( + choices := faction.liveries_overrides_ground_forces.get( + self.vehicle.type + ) + ) is not None: + return random.choice(choices) + except AttributeError: + logging.warning( + f"Faction {self.faction.name} is missing livery for ground unit {self.vehicle.type}" + ) + return None + logging.warning( + f"Faction {self.faction.name} is missing livery for ground unit {self.vehicle.type}" + ) + return None + + def determine_livery(self) -> Optional[str]: + if (livery := self.livery_from_faction()) is not None: + return livery + return None + + def apply_livery(self) -> None: + livery = self.determine_livery() + if livery is None: + return + self.vehicle.livery_id = livery + + +class NavalForcePainter: + def __init__(self, faction: Faction, vessel: Ship) -> None: + self.faction = faction + self.vessel = vessel + + def livery_from_faction(self) -> Optional[str]: + faction = self.faction + try: + if ( + choices := faction.liveries_overrides_ground_forces.get( + self.vessel.type + ) + ) is not None: + return random.choice(choices) + except AttributeError: + logging.warning( + f"Faction {self.faction.name} is missing livery for naval unit {self.vessel.type}" + ) + return None + logging.warning( + f"Faction {self.faction.name} is missing livery for naval unit {self.vessel.type}" + ) + return None + + def determine_livery(self) -> Optional[str]: + if (livery := self.livery_from_faction()) is not None: + return livery + return None + + def apply_livery(self) -> None: + livery = self.determine_livery() + if livery is None: + return + self.vessel.livery_id = livery diff --git a/game/missiongenerator/tgogenerator.py b/game/missiongenerator/tgogenerator.py index 939a6ac9..2352d12c 100644 --- a/game/missiongenerator/tgogenerator.py +++ b/game/missiongenerator/tgogenerator.py @@ -40,6 +40,10 @@ from dcs.unitgroup import MovingGroup, ShipGroup, StaticGroup, VehicleGroup from dcs.unittype import ShipType, VehicleType from dcs.vehicles import vehicle_map +from game.missiongenerator.groundforcepainter import ( + NavalForcePainter, + GroundForcePainter, +) from game.missiongenerator.missiondata import CarrierInfo, MissionData from game.radio.radios import RadioFrequency, RadioRegistry from game.radio.tacan import TacanBand, TacanChannel, TacanRegistry, TacanUsage @@ -122,6 +126,7 @@ class GroundObjectGenerator: vehicle_group: Optional[VehicleGroup] = None for unit in units: assert issubclass(unit.type, VehicleType) + faction = unit.ground_object.control_point.coalition.faction if vehicle_group is None: vehicle_group = self.m.vehicle_group( self.country, @@ -134,11 +139,13 @@ class GroundObjectGenerator: self.enable_eplrs(vehicle_group, unit.type) vehicle_group.units[0].name = unit.unit_name self.set_alarm_state(vehicle_group) + GroundForcePainter(faction, vehicle_group.units[0]).apply_livery() else: vehicle_unit = self.m.vehicle(unit.unit_name, unit.type) vehicle_unit.player_can_drive = True vehicle_unit.position = unit.position vehicle_unit.heading = unit.position.heading.degrees + GroundForcePainter(faction, vehicle_unit).apply_livery() vehicle_group.add_unit(vehicle_unit) self._register_theater_unit(unit, vehicle_group.units[-1]) if vehicle_group is None: @@ -154,6 +161,7 @@ class GroundObjectGenerator: ship_group: Optional[ShipGroup] = None for unit in units: assert issubclass(unit.type, ShipType) + faction = unit.ground_object.control_point.coalition.faction if ship_group is None: ship_group = self.m.ship_group( self.country, @@ -166,12 +174,14 @@ class GroundObjectGenerator: ship_group.set_frequency(frequency.hertz) ship_group.units[0].name = unit.unit_name self.set_alarm_state(ship_group) + NavalForcePainter(faction, ship_group.units[0]).apply_livery() else: ship_unit = self.m.ship(unit.unit_name, unit.type) if frequency: ship_unit.set_frequency(frequency.hertz) ship_unit.position = unit.position ship_unit.heading = unit.position.heading.degrees + NavalForcePainter(faction, ship_unit).apply_livery() ship_group.add_unit(ship_unit) self._register_theater_unit(unit, ship_group.units[-1]) if ship_group is None: