dcs-retribution/game/missiongenerator/rebelliongenerator.py
Eclipse/Druss99 31c80dfd02 refactor of previous commits
refactor to enum

typing and many other fixes

fix tests

attempt to fix some typescript

more typescript fixes

more typescript test fixes

revert all API changes

update to pydcs

mypy fixes

Use properties to check if player is blue/red/neutral

update requirements.txt

black -_-

bump pydcs and fix mypy

add opponent property

bump pydcs
2025-10-19 19:34:38 +02:00

103 lines
3.8 KiB
Python

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
from game.theater import Player
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=Player.BLUE).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=Player.RED).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