mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Improvement for factions and templates which will allow decoupling of the templates from the actual units - Implement UnitGroup class which matches unit_types and possible templates as the needed abstraction layer for decoupling. - Refactor UnitType, Add ShipUnitType and all ships we currently use - Remove serialized template.json and migrated to multiple yaml templates (one for each template) and multiple .miz - Reorganized a lot of templates and started with generalization of many types (AAA, Flak, SHORAD, Navy) - Fixed a lot of bugs from the previous reworks (group name generation, strike targets...) - Reorganized the faction file completly. removed redundant lists, added presets for complex groups / families of units like sams - Reworked the building template handling. Some templates are unused like "village" - Reworked how groups from templates can be merged again for the dcs group creation (e.g. the skynet plugin requires them to be in the same group) - Allow to define alternative tasks
163 lines
5.3 KiB
Python
163 lines
5.3 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
import random
|
|
from enum import Enum
|
|
from typing import Dict, List, TYPE_CHECKING
|
|
|
|
from game.data.units import UnitClass
|
|
from game.dcs.groundunittype import GroundUnitType
|
|
from game.theater import ControlPoint
|
|
from gen.ground_forces.combat_stance import CombatStance
|
|
|
|
if TYPE_CHECKING:
|
|
from game import Game
|
|
|
|
MAX_COMBAT_GROUP_PER_CP = 10
|
|
|
|
|
|
class CombatGroupRole(Enum):
|
|
TANK = 1
|
|
APC = 2
|
|
IFV = 3
|
|
ARTILLERY = 4
|
|
SHORAD = 5
|
|
LOGI = 6
|
|
INFANTRY = 7
|
|
ATGM = 8
|
|
RECON = 9
|
|
|
|
|
|
DISTANCE_FROM_FRONTLINE = {
|
|
CombatGroupRole.TANK: (2200, 3200),
|
|
CombatGroupRole.APC: (2700, 3700),
|
|
CombatGroupRole.IFV: (2700, 3700),
|
|
CombatGroupRole.ARTILLERY: (16000, 18000),
|
|
CombatGroupRole.SHORAD: (5000, 8000),
|
|
CombatGroupRole.LOGI: (18000, 20000),
|
|
CombatGroupRole.INFANTRY: (2800, 3300),
|
|
CombatGroupRole.ATGM: (5200, 6200),
|
|
CombatGroupRole.RECON: (2000, 3000),
|
|
}
|
|
|
|
GROUP_SIZES_BY_COMBAT_STANCE = {
|
|
CombatStance.DEFENSIVE: [2, 4, 6],
|
|
CombatStance.AGGRESSIVE: [2, 4, 6],
|
|
CombatStance.RETREAT: [2, 4, 6, 8],
|
|
CombatStance.BREAKTHROUGH: [4, 6, 6, 8],
|
|
CombatStance.ELIMINATION: [2, 4, 4, 4, 6],
|
|
CombatStance.AMBUSH: [1, 1, 2, 2, 2, 2, 4],
|
|
}
|
|
|
|
|
|
class CombatGroup:
|
|
def __init__(
|
|
self, role: CombatGroupRole, unit_type: GroundUnitType, size: int
|
|
) -> None:
|
|
self.unit_type = unit_type
|
|
self.size = size
|
|
self.role = role
|
|
self.start_position = None
|
|
|
|
def __str__(self) -> str:
|
|
s = f"ROLE : {self.role}\n"
|
|
if self.size:
|
|
s += f"UNITS {self.unit_type} * {self.size}"
|
|
return s
|
|
|
|
|
|
class GroundPlanner:
|
|
def __init__(self, cp: ControlPoint, game: Game) -> None:
|
|
self.cp = cp
|
|
self.game = game
|
|
self.connected_enemy_cp = [
|
|
cp for cp in self.cp.connected_points if cp.captured != self.cp.captured
|
|
]
|
|
self.tank_groups: List[CombatGroup] = []
|
|
self.apc_group: List[CombatGroup] = []
|
|
self.ifv_group: List[CombatGroup] = []
|
|
self.art_group: List[CombatGroup] = []
|
|
self.atgm_group: List[CombatGroup] = []
|
|
self.logi_groups: List[CombatGroup] = []
|
|
self.shorad_groups: List[CombatGroup] = []
|
|
self.recon_groups: List[CombatGroup] = []
|
|
|
|
self.units_per_cp: Dict[int, List[CombatGroup]] = {}
|
|
for cp in self.connected_enemy_cp:
|
|
self.units_per_cp[cp.id] = []
|
|
self.reserve: List[CombatGroup] = []
|
|
|
|
def plan_groundwar(self) -> None:
|
|
|
|
ground_unit_limit = self.cp.frontline_unit_count_limit
|
|
|
|
remaining_available_frontline_units = ground_unit_limit
|
|
|
|
# TODO: Fix to handle the per-front stances.
|
|
# https://github.com/dcs-liberation/dcs_liberation/issues/1417
|
|
group_size_choice = GROUP_SIZES_BY_COMBAT_STANCE[CombatStance.DEFENSIVE]
|
|
|
|
# Create combat groups and assign them randomly to each enemy CP
|
|
for unit_type in self.cp.base.armor:
|
|
unit_class = unit_type.unit_class
|
|
if unit_class is UnitClass.Tank:
|
|
collection = self.tank_groups
|
|
role = CombatGroupRole.TANK
|
|
elif unit_class is UnitClass.Apc:
|
|
collection = self.apc_group
|
|
role = CombatGroupRole.APC
|
|
elif unit_class is UnitClass.Artillery:
|
|
collection = self.art_group
|
|
role = CombatGroupRole.ARTILLERY
|
|
elif unit_class is UnitClass.Ifv:
|
|
collection = self.ifv_group
|
|
role = CombatGroupRole.IFV
|
|
elif unit_class is UnitClass.Logistics:
|
|
collection = self.logi_groups
|
|
role = CombatGroupRole.LOGI
|
|
elif unit_class is UnitClass.Atgm:
|
|
collection = self.atgm_group
|
|
role = CombatGroupRole.ATGM
|
|
elif unit_class is UnitClass.SHORAD:
|
|
collection = self.shorad_groups
|
|
role = CombatGroupRole.SHORAD
|
|
elif unit_class is UnitClass.Recon:
|
|
collection = self.recon_groups
|
|
role = CombatGroupRole.RECON
|
|
else:
|
|
logging.warning(
|
|
f"Unused front line vehicle at base {unit_type}: unknown unit class"
|
|
)
|
|
continue
|
|
|
|
available = self.cp.base.armor[unit_type]
|
|
|
|
if available > remaining_available_frontline_units:
|
|
available = remaining_available_frontline_units
|
|
|
|
remaining_available_frontline_units -= available
|
|
|
|
while available > 0:
|
|
|
|
if role == CombatGroupRole.SHORAD:
|
|
count = 1
|
|
else:
|
|
count = random.choice(group_size_choice)
|
|
if count > available:
|
|
if available >= 2:
|
|
count = 2
|
|
else:
|
|
count = 1
|
|
available -= count
|
|
|
|
group = CombatGroup(role, unit_type, count)
|
|
if len(self.connected_enemy_cp) > 0:
|
|
enemy_cp = random.choice(self.connected_enemy_cp).id
|
|
self.units_per_cp[enemy_cp].append(group)
|
|
else:
|
|
self.reserve.append(group)
|
|
collection.append(group)
|
|
|
|
if remaining_available_frontline_units == 0:
|
|
break
|