mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Introduce support for "rebel-zones"
This commit is contained in:
parent
942faeeaa3
commit
431492fa77
@ -46,6 +46,7 @@
|
||||
* **[UX]** Reduce size of save-file by loading landmap data on the fly, which also implies no new campaign needs to be started to benefit from an updated landmap
|
||||
* **[New Game Wizard]** Ability to save an edited faction during new game creation
|
||||
* **[Options]** New option to make AI helicopters prefer vertical takeoff and landing
|
||||
* **[Campaign Design/Mission Generation]** Introduction of "rebel zones" which randomly spawn units according to the campaign's definitions.
|
||||
|
||||
## Fixes
|
||||
* **[UI/UX]** A-10A flights can be edited again
|
||||
|
||||
@ -595,9 +595,18 @@ class MizCampaignLoader:
|
||||
self.add_preset_locations()
|
||||
self.add_supply_routes()
|
||||
self.add_shipping_lanes()
|
||||
self.add_rebel_zones()
|
||||
|
||||
def get_ctld_zones(self, prefix: str) -> List[Tuple[Point, float]]:
|
||||
zones = [t for t in self.mission.triggers.zones() if prefix + " CTLD" in t.name]
|
||||
for z in zones:
|
||||
self.mission.triggers.zones().remove(z)
|
||||
return [(z.position, z.radius) for z in zones]
|
||||
|
||||
def add_rebel_zones(self) -> None:
|
||||
zones = [
|
||||
t for t in self.mission.triggers.zones() if t.name.startswith("Rebels")
|
||||
]
|
||||
for z in zones:
|
||||
self.mission.triggers.zones().remove(z)
|
||||
self.theater.add_rebel_zones(zones)
|
||||
|
||||
@ -42,6 +42,7 @@ class Migrator:
|
||||
self._update_weather()
|
||||
self._update_tgos()
|
||||
self._reload_terrain()
|
||||
self._update_theather()
|
||||
|
||||
# TODO: remove in due time as this is supposedly fixed
|
||||
self.game.settings.nevatim_parking_fix = False
|
||||
@ -257,3 +258,7 @@ class Migrator:
|
||||
t = self.game.theater.terrain
|
||||
if issubclass(t.__class__, Terrain):
|
||||
self.game.theater.terrain = type(t)() # type: ignore
|
||||
|
||||
def _update_theather(self) -> None:
|
||||
if not hasattr(self.game.theater, "rebel_zones"):
|
||||
self.game.theater.rebel_zones = []
|
||||
|
||||
@ -36,6 +36,7 @@ from .frontlineconflictdescription import FrontLineConflictDescription
|
||||
from .kneeboard import KneeboardGenerator
|
||||
from .luagenerator import LuaGenerator
|
||||
from .missiondata import MissionData
|
||||
from .rebelliongenerator import RebellionGenerator
|
||||
from .tgogenerator import TgoGenerator
|
||||
from .triggergenerator import TriggerGenerator
|
||||
from .visualsgenerator import VisualsGenerator
|
||||
@ -111,6 +112,7 @@ class MissionGenerator:
|
||||
self.generate_ground_conflicts()
|
||||
self.generate_air_units(tgo_generator)
|
||||
|
||||
RebellionGenerator(self.mission, self.game).generate()
|
||||
TriggerGenerator(self.mission, self.game).generate()
|
||||
ForcedOptionsGenerator(self.mission, self.game).generate()
|
||||
VisualsGenerator(self.mission, self.game).generate()
|
||||
|
||||
101
game/missiongenerator/rebelliongenerator.py
Normal file
101
game/missiongenerator/rebelliongenerator.py
Normal file
@ -0,0 +1,101 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import random
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import numpy as np
|
||||
import shapely.geometry
|
||||
from dcs import Mission, Point
|
||||
from dcs.country import Country
|
||||
from dcs.triggers import TriggerZone, TriggerZoneCircular, TriggerZoneQuadPoint
|
||||
from dcs.vehicles import vehicle_map
|
||||
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from game.naming import namegen
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
|
||||
|
||||
class RebellionGenerator:
|
||||
def __init__(self, mission: Mission, game: Game) -> None:
|
||||
self.mission = mission
|
||||
self.game = game
|
||||
|
||||
def generate(self) -> None:
|
||||
ownfor_country = self.mission.country(
|
||||
self.game.coalition_for(player=True).faction.country.name
|
||||
)
|
||||
for rz in self.game.theater.ownfor_rebel_zones:
|
||||
self._generate_rebel_zone(ownfor_country, rz)
|
||||
opfor_country = self.mission.country(
|
||||
self.game.coalition_for(player=False).faction.country.name
|
||||
)
|
||||
for rz in self.game.theater.opfor_rebel_zones:
|
||||
self._generate_rebel_zone(opfor_country, rz)
|
||||
|
||||
def _generate_rebel_zone(self, ownfor_country: Country, rz: TriggerZone) -> None:
|
||||
for i, key_value_dict in rz.properties.items():
|
||||
unit_id = key_value_dict["key"]
|
||||
count_range = key_value_dict["value"]
|
||||
if unit_id not in vehicle_map:
|
||||
logging.warning(
|
||||
f"Invalid unit_id '{unit_id}' in rebel zone '{rz.name}'"
|
||||
)
|
||||
continue
|
||||
|
||||
count, success = self._get_random_count_for_type(count_range)
|
||||
if not success:
|
||||
logging.warning(
|
||||
f"Invalid count/range ({count_range}) for '{unit_id}' in rebel-zone '{rz.name}'"
|
||||
)
|
||||
continue
|
||||
unit_type = vehicle_map[unit_id]
|
||||
for _ in range(count):
|
||||
location = self.get_random_point_in_zone(rz)
|
||||
group = self.mission.vehicle_group(
|
||||
ownfor_country,
|
||||
namegen.next_unit_name(
|
||||
ownfor_country, next(GroundUnitType.for_dcs_type(unit_type))
|
||||
),
|
||||
unit_type,
|
||||
location,
|
||||
heading=random.random() * 360,
|
||||
)
|
||||
group.hidden_on_mfd = True
|
||||
group.hidden_on_planner = True
|
||||
|
||||
def get_random_point_in_zone(self, zone: TriggerZone) -> Point:
|
||||
if isinstance(zone, TriggerZoneCircular):
|
||||
shape = shapely.geometry.Point(zone.position.x, zone.position.y).buffer(
|
||||
zone.radius
|
||||
)
|
||||
elif isinstance(zone, TriggerZoneQuadPoint):
|
||||
shape = shapely.geometry.Polygon([[p.x, p.y] for p in zone.verticies])
|
||||
else:
|
||||
raise RuntimeError("Incompatible trigger-zone")
|
||||
minx, miny, maxx, maxy = shape.bounds
|
||||
p = self._random_shapely_point(maxx, maxy, minx, miny)
|
||||
while not shape.contains(p):
|
||||
p = self._random_shapely_point(maxx, maxy, minx, miny)
|
||||
return zone.position.new_in_same_map(p.x, p.y)
|
||||
|
||||
@staticmethod
|
||||
def _random_shapely_point(
|
||||
maxx: float, maxy: float, minx: float, miny: float
|
||||
) -> shapely.geometry.Point:
|
||||
x = np.random.uniform(minx, maxx)
|
||||
y = np.random.uniform(miny, maxy)
|
||||
p = shapely.geometry.Point(x, y)
|
||||
return p
|
||||
|
||||
@staticmethod
|
||||
def _get_random_count_for_type(bounds: str) -> tuple[int, bool]:
|
||||
parts = bounds.split("-")
|
||||
if len(parts) == 1 and parts[0].isdigit():
|
||||
return int(parts[0]), True
|
||||
elif len(parts) == 2 and parts[0].isdigit() and parts[1].isdigit():
|
||||
return random.randint(int(parts[0]), int(parts[1])), True
|
||||
else:
|
||||
return 0, False
|
||||
@ -8,6 +8,7 @@ from uuid import UUID
|
||||
|
||||
from dcs.mapping import Point
|
||||
from dcs.terrain.terrain import Terrain
|
||||
from dcs.triggers import TriggerZone
|
||||
from shapely import geometry, ops
|
||||
|
||||
from .daytimemap import DaytimeMap
|
||||
@ -43,6 +44,7 @@ class ConflictTheater:
|
||||
self.seasonal_conditions = seasonal_conditions
|
||||
self.daytime_map = daytime_map
|
||||
self.controlpoints: list[ControlPoint] = []
|
||||
self.rebel_zones: list[TriggerZone] = []
|
||||
|
||||
def __setstate__(self, state: dict[str, Any]) -> None:
|
||||
if "landmap_path" not in state:
|
||||
@ -66,6 +68,25 @@ class ConflictTheater:
|
||||
return theater_dir / "landmap.p"
|
||||
raise RuntimeError(f"Could not determine landmap path for {terrain_name}")
|
||||
|
||||
def add_rebel_zones(self, zones: List[TriggerZone]) -> None:
|
||||
self.rebel_zones.extend(zones)
|
||||
|
||||
@property
|
||||
def opfor_rebel_zones(self) -> Iterator[TriggerZone]:
|
||||
for rz in self.rebel_zones:
|
||||
if {1: 1, 2: 0, 3: 0} == {
|
||||
k: v for k, v in rz.color.items() if k in [1, 2, 3]
|
||||
}:
|
||||
yield rz
|
||||
|
||||
@property
|
||||
def ownfor_rebel_zones(self) -> Iterator[TriggerZone]:
|
||||
for rz in self.rebel_zones:
|
||||
if {1: 0, 2: 0, 3: 1} == {
|
||||
k: v for k, v in rz.color.items() if k in [1, 2, 3]
|
||||
}:
|
||||
yield rz
|
||||
|
||||
def add_controlpoint(self, point: ControlPoint) -> None:
|
||||
self.controlpoints.append(point)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user