Improve Layout System and adopt to review

- adopted to review comments
- removed the role from layouts
- reworked the Groups within the layouts
- added more documentation
- rebased to latest changes
This commit is contained in:
RndName 2022-02-15 17:02:52 +01:00
parent 2c17a9a52e
commit 54e24dff39
93 changed files with 1606 additions and 1710 deletions

View File

@ -1,157 +1,120 @@
# ArmedForces and the Layout System
# The Layout System
Armed Forces and the Layout System is a complete rework of the generator-based logic to build theater-ground-objects (Liberation Objects which group ground units).
This will change underlying parts of the code base which will allow major improvements to the Ground Warfare in upcoming features.
The Layout System is a new way of defining how ground objects like SAM Sites or other Vehicle / Ship Groups will be generated (which type of units, how many units, alignment and orientation). It is a complete rework of the previous generator-based logic which was written in python code. The new system allows to define layouts with easy to write yaml code and the use of the DCS Mission Editor for easier placement of the units. The layout system also introduced a new logical grouping of Units and layouts for them, the Armed Forces, which will allow major improvements to the Ground Warfare in upcoming features.
**Armed Forces**\
TODO Describe the introduction of ArmedForces which are similar to the AirWing and Squadrons.
The armed forces of each coalition contain multiple ForceGroups. A ForceGroup is a logical set of units (Vehicles, Ships, Statics) and corresponding Layouts for these units.
TODO Picture / Example to describe what it is... for example with Hawk Battery or S-300 Battery
The Armed Forces is a new system introduced with the layout system which will allow to identitfy and group possible units from the faction together with available layouts for these groups. It is comparable to the AirWing and Squadron implementation but just for Ground and Naval Forces. All possible Force Groups (grouping of 1 or more units and and the available layouts for them) will be generated during campaign initialization and will be used later by many different systems. A Force Group can also include static objects which was not possible before the introduction of the layout system. It is also possible to define presets of these Force Groups within the faction file which is handy for more complex groups like a SA-10 Battery or similar. Example: [SA-10/S-300PS](/resources/groups/SA-10.yaml) which includes all the units like SR, TR, LN and has the layout of a [S-300 Battery](/resources/layouts/anti_air/S-300_Site.yaml)
**The Layout System**\
In the previous system the generator was written in python and generated a group with a defined and static logic, written in code.
The layout sytem will now decouple the alignment / positioning from units and the definition of theire actual type (like Ural-375).
The template system allows to define the layout and set which unit types or classes (All logistic units for example) are able to fit into the template.
Ultimately this will allow to have generalized templates which can be reused by multiple types of units. Best example is the definition of a SAM layout.
Previously we had a generator for every different SAM Site, now we can just reuse the alignemnt (e.g. 6 Launchers in a circle alignment) with more generalization.
In the previous system the generator which created the ground object was written in python which made modifications and reusability very complicated. To allow easier handling of the layouts and decoupling of alignment of units and the actual unit type (for example Ural-375) the layout system was introduced. Previously we had a generator for every different SAM Site, now we can just reuse the alignemnt (e.g. 6 Launchers in a circle alignment) for multiple SAM Systems and introduce more variety.
This new System allows Users and Designers to easily create or modify layouts as the new alginment and orientation of units is defined with the DCS Mission editor. An additional .yaml file allows the configuration of the layout with settings like allow unit types or random amounts of units. In total the new system reduces the complexity and allows to precisely align / orient units as needed and create realistic looking ground units.
This also allows Users and Designers to easily create or modify templates as the new templates are defined with the DCS Mission editor and an additional .yaml file which provides mapping information.
In total the new system reduces the complexity and allows to precisely align / orient units as needed and create realistic looking ground units.
As the whole ground unit generation and handling was reworked it is now also possible to add static units to a ground object, so even Fortifcation or similar can be added to templates in the future.
## General Concept
### General Concept
![Overview](layouts.png)
TODO: Describe the general flow of the Template system
TODO: Describe the serialization (Developer Tools: Import Templates)
All possible Force Groups will be generated during campaign initialization by checking all possible units for the specific faction and all available layouts. The code will automatically match general layouts with available units. It is also possible to define preset groups within the faction files which group many units and the prefered layouts for the group. This is especially handy for unique layouts which are not defined as `global`. For example complex sam sites like the S-300 or Patriot which have very specific alignment of the different units.
TODO Lifecycle:
The template will be automatically validated on campaign generation against the player and enemy factions.
If the factions support the template (based on the unit_types and unit_classes) then it will be added to the game.
If a faction does not support a group from the template it will be removed if optional == True otherwise the complete template will be marked as unsupported and will not be used for the game.
During campaign initialization the start_generator will request unit_groups for the preset locations defined by the campaign designer. The faction will then offer possible groups and the matching template.
The Liberation Group (TheaterGroundObject) is then being generated from this UnitGroup.
Layouts will be matched to units based on the special definition given in the corresponding yaml file. For example a layout which is defined as global and allows the unit_class SHORAD will automatically be used for all available SHORAD units which are defined in the faction file.
- GroundWar (Frontline) currently does **not** use the template system
- User can buy new SAM or ArmorGroup using this template system
- Campaign Designers can also define precicsly (if needed) which template or UnitGroup should be placed at a specific location by using TriggerZones with custom properties
TODO Describe the optional flag.
All these generated ForceGroups will be managed by the ArmedForces class of the specific coalition. This class will be used by other parts of the system like the start_generator or the BuyMenu. The ArmedForces class will then generate the TheaterGroundObject which will be used by liberation.
Example for a customized Ground Object Buy Menu which makes use of Templates and UnitGroups:
![ground_object_buy_menu.png](ground_object_buy_menu.png)
### The template miz
## How to modify or add layouts
*Important*: Every unit_type has to be in a separate Group for the template to work.
The template system merges the groups back together later with the group_id property (defaults to 1 which means that all groups in the template will be merged to group 1)
A layout consists of two special files:
The function of the miz is to have the positioning and alignment of all the units within the template. Coordinates and headings will be used for the generated group.
- layout.miz which defines the actual positioning and alignment of the groups / units
- layout.yaml which defines the necessary information like amount of units, possible types or classes.
Unit Count per group has to be the amount set with the unit_count property.
To add a new template a new yaml has to be created as every yaml can only define exact one template. Best practice is to copy paste an existing template which is similar to the one to be created as starting point. The next step is to create a new .miz file and align Units and statics to the wishes. Even if existing ones can be reused, best practice is to always create a fresh one to prevent side effects.
The most important part is to use a new Group for every different Unit Type. It is not possible to mix Unit Types in one group within a template. For example it is not possible to have a logistic truck and a AAA in the same group. The miz file will only be used to get the exact position and orientation of the units, therefore it is irrelevant which type of unit will be used. The unit type will be later defined inside the yaml file.
For the next step all Group names have to be added to the yaml file. Take care to that these names match exactly! Assign the unit_types or unit_classes properties to math the needs.
During template generation the system will go through all possible units and will assign the respective unit_type to the units up to the maximum allow unit_count from the mapping.
**Important**: Whenever changes were made to layouts they have to be re-imported into Liberation. See below.
### The Layout miz
The miz file is used to define the positioning and orientation of the different units for the template. The actual unit which is used is irrelevant. It is important to use a unique and meaningful name for the groups as this will be used in the yaml file as well. The information which will be extracted from the miz file are just the coordinates and heading of the units.
*Important*: Every different unit type has to be in a separate Group for the template to work. You can not add units of different types to the same group. They can get merged back together during generation by setting the group property. In the example below both groups `AAA Site 0` and `AAA Site 1` have the group = 1 which means that they will be in the same dcs group during generation.
TODO max amount of possible units is defined from the miz. Example if later the group should have 6 units than there have to be 6 defined in the miz.
![template_miz_example.png](layout_miz_example.png)
### The template yaml
### The Layout configuration file
TODO Description about the layout yaml file.\
Possible Information:
| Property | Type | Required | Description | Example |
|---------------|-----------------------|----------|----------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------|
| Name | (str) | Yes | A name to identify the template | `name: Armor Group` |
| Role | (GroupRole) | Yes | The role which the template should fit in | `role: AntiAir` or `role: GroundForce` |
| Tasks | (list of GroupTask) | Yes | A list of tasks which the template can fullfill | `tasks: - AAA - SHORAD` |
| Generic | (bool, default False) | No | If this is true this template will be used to create general unitGroups | |
| Description | (str) | No | Short description of the template | |
| category | (str) | No | Only used for building templates to identify the type of the building | `category: ammo` |
| Groups | (list of Groups) | Yes | see below for definition of a group | |
| template_file | (str) | No | the .miz file which has the template included. Only needed if the file has a different name than the yaml file | `template_file: resources/templates/anti_air/legacy_ground_templates.miz` |
| layout_file | (str) | No | the .miz file which has the groups / units of the layout included. Only needed if the file has a different name than the yaml file | `layout_file: resources/layouts/naval/legacy_naval_templates.miz` |
Groups within the template are defined as following:
TODO Group and SubGroup
A group has 1..N sub groups. The name of the Group will be used later within the DCS group name.
All SubGroups will be merged into one DCS Group
Every unit type has to be defined as a sub group as following:
| Property | Type | Required | Description | Example |
|--------------|------------------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
| name | (str) | Yes | The group name used in the .miz. Must match exactly! | |
| optional | (bool, default: False) | No | Defines wether the template can be used without this group if the faction has no access to the unit type or the user wants to disable this group | |
| group | (int, default: 1) | No | The ID of the group which the templategroup will be merged into | |
| unit_count | (list of int) | No | Amount of units to be generated for this group. Can be fixed or a range where it will be picked randomly | |
| unit_types | (list dcs unit types) | No | Specific unit_types for ground units. Complete list from pydcs: [Vehicles.py](https://github.com/pydcs/dcs/blob/master/dcs/vehicles.py). This list is extended by all supported mods! | |
| unit_classes | (list unit classes) | No | Unit_classes of supported units. Defined in [UnitClass](/game/data/units.py) | |
| statics | (list static types) | No | Specific unit_types of statics. Complete list from pydcs: [Statics.py](https://github.com/pydcs/dcs/blob/master/dcs/statics.py) | |
Complete example of a generic template for AAA Groups:
Complete example of a generic template for an Aircraft Carrier group:
```
name: AAA Site
description: A standard AAA template
name: Carrier Group
generic: true
role: AntiAir
tasks:
- AAA
- AircraftCarrier
groups:
- name: AAA Site 0
group: 1
unit_count:
- 2
- 6
unit_classes:
- AAA
- name: AAA Site 1
optional: true
group: 1
unit_count:
- 1
- 2
unit_classes:
- Logistics
template_file: resources/templates/anti_air/AAA.miz
- Carrier: # Group Name of the DCS Group
- name: Carrier Group 0 # Sub Group used in the layout.miz
unit_count:
- 1
unit_classes:
- AircraftCarrier
- Escort: # Group name of the 2nd Group
- name: Carrier Group 1
unit_count:
- 4
unit_classes:
- Destroyer
layout_file: resources/layouts/naval/legacy_naval_templates.miz
```
### Roles, Tasks and Classes
TODO Describe Role, Tasking and Classes
[GroupRole and GroupTask](/game/data/groups.py)
[UnitClass](/game/data/units.py)
## How to add / modify a template
A template consists of two special files:
- template.miz which defines the actual positioning and alignment of the groups / units
- template.yaml which defines the necessary information like amount of units, possible types or classes.
To add a new template a new yaml has to be created as every yaml can only define exact one template. Best practice is to copy paste an existing template which is similar to the one to be created as starting point. The next step is to create a new .miz file and align Units and statics to the wishes. Even if existing ones can be reused, best practice is to always create a fresh one to prevent side effects.
The most important part is to use a new Group for every different Unit Type. It is not possible to mix Unit Types in one group within a template. For example it is not possible to have a logistic truck and a AAA in the same group. The miz file will only be used to get the exact position and orientation of the units, therefore it is irrelevant which type of unit will be used. The unit type will be later defined inside the yaml file.
For the next step all Group names have to be added to the yaml file. Take care to that these names match exactly! Assign the unit_types or unit_classes properties to math the needs.
TODO Improve this with images and more detailed description
**IMPORTANT**: Due to performance increase the templates get serialized to a pickle file in the save dir as `templates.p`. When templates were modified a manual re-import of all templates has to be triggered.
This can be done by either deleting this file or using the Liberation UI. There is a special option in the ToolBar under Tools: Import Templates.
## Import Layouts into Liberation
TODO Describe the serialization and import.
For performance improvements all layouts are serialized to a so called pickle file. Every time changes are made to the layouts this file has to be recreated.
It will also be recreated after each Liberation update as it will check the Version Number and recreate it when changes are recognized.
This file is stored in the save folder
### Import Layouts into Liberation
For performance improvements all layouts are serialized to a so called pickle file inside the save folder defined in the liberation preferences. Every time changes are made to the layouts this file has to be recreated. It can be recreated by either deleting the layouts.p file manually or using the special option in the Liberation Toolbar (Developer Tools -> Import Layouts). It will also be recreated after each Liberation update as it will check the Version Number and recreate it when changes are recognized.
## Migration from Generators
- All generators removed and migrated to templates
- These templates will in the next step be generalized
The previous generators were migrated using a script which build a group using the generator. All of these groups were save into one .miz file [original_generator_layouts.miz](/resources/layouts/original_generator_layouts.miz).
This miz file can be used to verify the templates and to generalize similar templates to decouple the layout from the actual units. As this is a time-consuming and sphisticated task this will be done over time.
With the first step the technical requirements will be fulfilled so that the generalization can happen afterwards the technical pr gets merged.
@ -165,53 +128,39 @@ During migration all default factions were automatically updated, so they will w
What was changed:
- Removed the `ewrs` list. All EWRs are now defined in the list "air_defense_units".
- Added the `air_defense_units` list. All units with the Role AntiAir can be defined here as [GroundUnitType](/game/dcs/groundunittype.py). All possible units are defined in [/resources/units/ground_units](/resources/units/ground_units)
- Added `preset_groups`. This list allows to define Preset Groups (described above) like SAM Systems consisting of Launcher, SR, TR and so on instead of adding them each to "air_defense_units". The presets are defined in [/resources/units/unit_groups](/resources/units/groups)
- Added `preset_groups`. This list allows to define Preset Groups (described above) like SAM Systems consisting of Launcher, SR, TR and so on instead of adding them each to "air_defense_units". The presets are defined in [/resources/groups](/resources/groups)
- Migrated `air_defenses` to air_defense_units and preset_sets.
- `Missiles` are migrated to GroundUnitTypes instead of Generator names (see air_defense_units for how to use)
- Removed `cruisers`, `destroyers` and `naval_generators`. Migrated them to naval_units and preset_groups
- added `naval_units` with the correct ship name found here [/resources/units/ships](/resources/units/ships)
- `aircraft_carrier` and `helicopter_carrier` were moved to `naval_units` as well.
## Unit Groups
## Preset Groups
TODO Explain more
- Sum up groups of different units which are used together (like a sam site).
- UnitGroup allows to define this logical group and add this to the faction file.
- UnitGroups can have preferred templates
Instead of adding the exact name of the previous generator to add complex groups like SAM sites or similar to the faction it is now possible to add preset groups to the faction file. As described earlier such a preset group (Force Group) can be defined very easy with a yaml file. This file allows to define the name, tasking, units, statics and the prefered layouts. The first task defines the primary role of the ForceGroup which gets generated from the preset.
Example:
```
name: SA-10/S-300PS # The name which will be used in the faction file
role: AntiAir # The role of the Group
tasks:
- LORAD # The task the Group can fulfill
ground_units:
name: SA-10/S-300PS # The name of the group
tasks: # Define at least 1 task
- LORAD # The task(s) the Group can fulfill
units: # Define at least 1 unit
- SAM SA-10 S-300 "Grumble" Clam Shell SR
- SAM SA-10 S-300 "Grumble" Big Bird SR
- SAM SA-10 S-300 "Grumble" C2
- SAM SA-10 S-300 "Grumble" Flap Lid TR
- SAM SA-10 S-300 "Grumble" TEL D
- SAM SA-10 S-300 "Grumble" TEL C
ship_units:
- # Add some naval units here
statics:
statics: # Optional
- # Add some statics here
templates:
- S-300 Site # The template names which should be used by this group
layouts: # Define at least one layout
- S-300 Site # prefered layouts for these groups
```
A list of all available units is accessible here: [/resources/units/unit_groups](/resources/units/groups)
### Optional Tasks which can be done later
- [ ] Complex Presets which allow campaign designer to specify the exact forcegroup or layout which should be used.
- [ ] Generalize all layouts (Like MERAD or SHORAD templates)
- [ ] Reuse the layouts for the frontline
- [ ] Add UI Implementation to choose which templates should be used during new game wizard (like AirWing Config)
- [ ] Rework "Names_By_Category" to just use the Tasking instead of a string.
- [ ] Add remaining missing classes to the units which currently dont have a class
Resources:
- A list of all available preset groups can be found here: [/resources/groups](/resources/groups)
- All possible tasks can be found in the [/game/data/groups.py](/game/data/groups.py)
- Units are defined with the variant name found in [/resources/units](/resources/units)

View File

@ -2,21 +2,20 @@ from __future__ import annotations
import random
from typing import TYPE_CHECKING, Iterator, Optional
from game import db
from game.data.groups import GroupRole, GroupTask
from game.data.groups import GroupTask
from game.armedforces.forcegroup import ForceGroup
from game.layout import LAYOUTS
from game.profiling import logged_duration
if TYPE_CHECKING:
from game.factions.faction import Faction
# TODO More comments and rename
class ArmedForces:
"""TODO Description"""
# All available force groups for a specific Role
forces: dict[GroupRole, list[ForceGroup]]
forces: list[ForceGroup]
def __init__(self, faction: Faction):
with logged_duration(f"Loading armed forces for {faction.name}"):
@ -24,50 +23,36 @@ class ArmedForces:
def add_or_update_force_group(self, new_group: ForceGroup) -> None:
"""TODO Description"""
if new_group.role in self.forces:
# Check if a force group with the same units exists
for force_group in self.forces[new_group.role]:
if (
force_group.units == new_group.units
and force_group.tasks == new_group.tasks
):
# Update existing group if units and tasks are equal
force_group.update_group(new_group)
return
# Check if a force group with the same units exists
for force_group in self.forces:
if (
force_group.units == new_group.units
and force_group.tasks == new_group.tasks
):
# Update existing group if units and tasks are equal
force_group.update_group(new_group)
return
# Add a new force group
self.add_force_group(new_group)
def add_force_group(self, force_group: ForceGroup) -> None:
"""Adds a force group to the forces"""
if force_group.role in self.forces:
self.forces[force_group.role].append(force_group)
else:
self.forces[force_group.role] = [force_group]
self.forces.append(new_group)
def _load_forces(self, faction: Faction) -> None:
"""Initialize all armed_forces for the given faction"""
# This function will create a ForgeGroup for each global Layout and PresetGroup
self.forces = {}
"""Initialize the ArmedForces for the given faction.
This will create a ForceGroup for each generic Layout and PresetGroup"""
preset_layouts = [
layout
for preset_group in faction.preset_groups
for layout in preset_group.layouts
]
# Initialize with preset_groups from the faction
self.forces = [preset_group for preset_group in faction.preset_groups]
# Generate Troops for all generic layouts and presets
for layout in db.LAYOUTS.layouts:
if (
layout.generic or layout in preset_layouts
) and layout.usable_by_faction(faction):
# Generate ForceGroup for all generic layouts by iterating over
# all layouts which are usable by the given faction.
for layout in LAYOUTS.layouts:
if layout.generic and layout.usable_by_faction(faction):
# Creates a faction compatible GorceGroup
self.add_or_update_force_group(ForceGroup.for_layout(layout, faction))
def groups_for_task(self, group_task: GroupTask) -> Iterator[ForceGroup]:
for groups in self.forces.values():
for unit_group in groups:
if group_task in unit_group.tasks:
yield unit_group
for force_group in self.forces:
if group_task in force_group.tasks:
yield force_group
def groups_for_tasks(self, tasks: list[GroupTask]) -> list[ForceGroup]:
groups = []

View File

@ -9,18 +9,20 @@ from typing import ClassVar, TYPE_CHECKING, Type, Any, Iterator, Optional
import yaml
from dcs import Point
from game import db
from game.data.groups import GroupRole, GroupTask
from game.data.groups import GroupTask
from game.data.radar_db import UNITS_WITH_RADAR
from game.dcs.groundunittype import GroundUnitType
from game.dcs.helpers import static_type_from_name
from game.dcs.shipunittype import ShipUnitType
from game.dcs.unittype import UnitType
from game.point_with_heading import PointWithHeading
from game.layout.layout import TheaterLayout, AntiAirLayout, GroupLayout
from game.layout.layout import TgoLayout, AntiAirLayout, TgoLayoutGroup
from dcs.unittype import UnitType as DcsUnitType, VehicleType, ShipType, StaticType
from game.theater.theatergroup import TheaterGroup
from game.layout import LAYOUTS
if TYPE_CHECKING:
from game import Game
from game.factions.faction import Faction
@ -29,25 +31,40 @@ if TYPE_CHECKING:
@dataclass
class ForceGroup:
"""A logical group of multiple units and layouts which have a specific tasking"""
"""A logical group of multiple units and layouts which have a specific tasking.
ForceGroups will be generated during game and coalition initialization based on
generic layouts and preset forcegroups.
Every ForceGroup must have at least one unit, one task and one layout.
A preset ForceGroup can for example be a S-300 SAM Battery which used many
different unit types which all together handle a specific tasking (AirDefense)
For this example the ForceGroup would consist of SR, TR, LN and so on next to
statics. This group also has the Tasking LORAD and can have multiple (at least one)
layouts which will be used to generate the actual DCS Group from it.
"""
name: str
units: list[UnitType[Any]]
statics: list[Type[DcsUnitType]]
role: GroupRole
tasks: list[GroupTask] = field(default_factory=list)
layouts: list[TheaterLayout] = field(default_factory=list)
layouts: list[TgoLayout] = field(default_factory=list)
_by_name: ClassVar[dict[str, ForceGroup]] = {}
_by_role: ClassVar[dict[GroupRole, list[ForceGroup]]] = {}
_loaded: bool = False
@staticmethod
def for_layout(layout: TheaterLayout, faction: Faction) -> ForceGroup:
"""TODO Documentation"""
def for_layout(layout: TgoLayout, faction: Faction) -> ForceGroup:
"""Create a ForceGroup from the given TgoLayout which is usable by the faction
This will iterate through all possible TgoLayoutGroups and check if the
unit_types are accessible by the faction. All accessible units will be added to
the force group
"""
units: set[UnitType[Any]] = set()
statics: set[Type[DcsUnitType]] = set()
for group in layout.groups:
for group in layout.all_groups:
for unit_type in group.possible_types_for_faction(faction):
if issubclass(unit_type, VehicleType):
units.add(next(GroundUnitType.for_dcs_type(unit_type)))
@ -57,10 +74,9 @@ class ForceGroup:
statics.add(unit_type)
return ForceGroup(
f"{layout.role.value}: {', '.join([t.description for t in layout.tasks])}",
", ".join([t.description for t in layout.tasks]),
list(units),
list(statics),
layout.role,
layout.tasks,
[layout],
)
@ -80,33 +96,38 @@ class ForceGroup:
or type in self.statics
)
def dcs_unit_types_for_group(self, group: GroupLayout) -> list[Type[DcsUnitType]]:
"""TODO Description"""
def dcs_unit_types_for_group(
self, group: TgoLayoutGroup
) -> list[Type[DcsUnitType]]:
"""Return all available DCS Unit Types which can be used in the given
TgoLayoutGroup"""
unit_types = [t for t in group.unit_types if self.has_access_to_dcs_type(t)]
alternative_types = []
for accessible_unit in self.units:
if accessible_unit.unit_class in group.unit_classes:
unit_types.append(accessible_unit.dcs_unit_type)
if accessible_unit.unit_class in group.alternative_classes:
if accessible_unit.unit_class in group.fallback_classes:
alternative_types.append(accessible_unit.dcs_unit_type)
return unit_types or alternative_types
def unit_types_for_group(self, group: GroupLayout) -> Iterator[UnitType[Any]]:
def unit_types_for_group(self, group: TgoLayoutGroup) -> Iterator[UnitType[Any]]:
for dcs_type in self.dcs_unit_types_for_group(group):
if issubclass(dcs_type, VehicleType):
yield next(GroundUnitType.for_dcs_type(dcs_type))
elif issubclass(dcs_type, ShipType):
yield next(ShipUnitType.for_dcs_type(dcs_type))
def statics_for_group(self, group: GroupLayout) -> Iterator[Type[DcsUnitType]]:
def statics_for_group(self, group: TgoLayoutGroup) -> Iterator[Type[DcsUnitType]]:
for dcs_type in self.dcs_unit_types_for_group(group):
if issubclass(dcs_type, StaticType):
yield dcs_type
def random_dcs_unit_type_for_group(self, group: GroupLayout) -> Type[DcsUnitType]:
"""TODO Description"""
def random_dcs_unit_type_for_group(
self, group: TgoLayoutGroup
) -> Type[DcsUnitType]:
"""Return random DCS Unit Type which can be used in the given TgoLayoutGroup"""
return random.choice(self.dcs_unit_types_for_group(group))
def update_group(self, new_group: ForceGroup) -> None:
@ -130,7 +151,7 @@ class ForceGroup:
def create_ground_object_for_layout(
self,
layout: TheaterLayout,
layout: TgoLayout,
name: str,
position: PointWithHeading,
control_point: ControlPoint,
@ -139,25 +160,31 @@ class ForceGroup:
"""Create a TheaterGroundObject for the given template"""
go = layout.create_ground_object(name, position, control_point)
# Generate all groups using the randomization if it defined
for group in layout.groups:
# Choose a random unit_type for the group
try:
unit_type = self.random_dcs_unit_type_for_group(group)
except IndexError:
if group.optional:
# If group is optional it is ok when no unit_type is available
continue
# if non-optional this is a error
raise RuntimeError(f"No accessible unit for {self.name} - {group.name}")
self.create_theater_group_for_tgo(go, group, name, game, unit_type)
for group_name, groups in layout.groups.items():
for group in groups:
# Choose a random unit_type for the group
try:
unit_type = self.random_dcs_unit_type_for_group(group)
except IndexError:
if group.optional:
# If group is optional it is ok when no unit_type is available
continue
# if non-optional this is a error
raise RuntimeError(
f"No accessible unit for {self.name} - {group.name}"
)
tgo_group_name = f"{name} ({group_name})"
self.create_theater_group_for_tgo(
go, group, tgo_group_name, game, unit_type
)
return go
def create_theater_group_for_tgo(
self,
ground_object: TheaterGroundObject,
group: GroupLayout,
name: str,
group: TgoLayoutGroup,
group_name: str,
game: Game,
unit_type: Type[DcsUnitType],
unit_count: Optional[int] = None,
@ -165,25 +192,29 @@ class ForceGroup:
"""Create a TheaterGroup and add it to the given TGO"""
# Random UnitCounter if not forced
if unit_count is None:
unit_count = group.unit_counter
# Static and non Static groups have to be separated
group_id = group.group - 1
if len(ground_object.groups) <= group_id:
# Requested group was not yet created
ground_group = TheaterGroup.from_template(
game.next_group_id(), group, ground_object, unit_type, unit_count
)
# Set Group Name
ground_group.name = f"{name} {group_id}"
ground_object.groups.append(ground_group)
units = ground_group.units
else:
ground_group = ground_object.groups[group_id]
units = group.generate_units(ground_object, unit_type, unit_count)
unit_count = group.group_size
# Generate Units
units = group.generate_units(ground_object, unit_type, unit_count)
# Get or create the TheaterGroup
ground_group = ground_object.group_by_name(group_name)
if ground_group is not None:
# TheaterGroup with this name exists already. Extend it
ground_group.units.extend(units)
else:
# TheaterGroup with the name was not created yet
ground_object.groups.append(
TheaterGroup.from_template(
game.next_group_id(),
group_name,
units,
ground_object,
unit_type,
unit_count,
)
)
# Assign UniqueID, name and align relative to ground_object
for u_id, unit in enumerate(units):
for unit in units:
unit.id = game.next_unit_id()
unit.name = unit.unit_type.name if unit.unit_type else unit.type.name
unit.position = PointWithHeading.from_point(
@ -209,42 +240,46 @@ class ForceGroup:
@classmethod
def _load_all(cls) -> None:
for file in Path("resources/units/groups").glob("*.yaml"):
for file in Path("resources/groups").glob("*.yaml"):
if not file.is_file():
raise RuntimeError(f"{file.name} is not a valid ForceGroup")
with file.open(encoding="utf-8") as data_file:
data = yaml.safe_load(data_file)
group_role = GroupRole(data.get("role"))
name = data["name"]
group_tasks = [GroupTask.by_description(n) for n in data.get("tasks", [])]
group_tasks = [GroupTask.by_description(n) for n in data.get("tasks")]
if not group_tasks:
logging.error(f"ForceGroup {name} has no valid tasking")
continue
units = [UnitType.named(unit) for unit in data.get("units", [])]
units = [UnitType.named(unit) for unit in data.get("units")]
if not units:
logging.error(f"ForceGroup {name} has no valid units")
continue
statics = []
for static in data.get("statics", []):
static_type = db.static_type_from_name(static)
static_type = static_type_from_name(static)
if static_type is None:
logging.error(f"Static {static} for {file} is not valid")
else:
statics.append(static_type)
layouts = [next(db.LAYOUTS.by_name(n)) for n in data.get("layouts")]
layouts = [LAYOUTS.by_name(n) for n in data.get("layouts")]
if not layouts:
logging.error(f"ForceGroup {name} has no valid layouts")
continue
force_group = ForceGroup(
name=data.get("name"),
name=name,
units=units,
statics=statics,
role=group_role,
tasks=group_tasks,
layouts=layouts,
)
cls._by_name[force_group.name] = force_group
if group_role in cls._by_role:
cls._by_role[group_role].append(force_group)
else:
cls._by_role[group_role] = [force_group]
cls._loaded = True

View File

@ -11,6 +11,7 @@ from pydantic.dataclasses import dataclass
from game.ato.flightwaypointtype import FlightWaypointType
from game.theater import LatLon
from game.theater.theatergroup import TheaterUnit
from game.utils import Distance, meters
if TYPE_CHECKING:
@ -94,7 +95,7 @@ class FlightWaypoint(BaseFlightWaypoint):
# having three names. A short and long form is enough.
description: str = ""
targets: Sequence[MissionTarget | Unit] = []
targets: Sequence[MissionTarget | TheaterUnit] = []
obj_name: str = ""
pretty_name: str = ""
only_for_player: bool = False

View File

@ -1,4 +1,4 @@
from .faction import Faction
from .faction_loader import FactionLoader
from .factionloader import FactionLoader
FACTIONS = FactionLoader()

View File

@ -150,7 +150,7 @@ class Faction:
for unit in preset_group.units
),
)
return list(all_units)
return list(set(all_units))
@property
def air_defenses(self) -> list[str]:
@ -158,7 +158,11 @@ class Faction:
# This is used for the faction overview in NewGameWizard
air_defenses = [a.name for a in self.air_defense_units]
air_defenses.extend(
[pg.name for pg in self.preset_groups if pg.role == GroupRole.AIR_DEFENSE]
[
pg.name
for pg in self.preset_groups
if any(task.role == GroupRole.AIR_DEFENSE for task in pg.tasks)
]
)
return sorted(air_defenses)

View File

@ -1,4 +1,4 @@
from layout import TheaterLayout
from game.layout.layoutloader import LayoutLoader
from .layout import TgoLayout, TgoLayoutGroup
from .layoutloader import LayoutLoader
LAYOUTS = LayoutLoader()
LAYOUTS = LayoutLoader()

View File

@ -1,9 +1,10 @@
from __future__ import annotations
from collections import defaultdict
import logging
import random
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Type
from typing import TYPE_CHECKING, Iterator, Type
from dcs import Point
from dcs.unit import Unit
@ -50,98 +51,121 @@ class LayoutUnit:
"""Creates a LayoutUnit from a DCS Unit"""
return LayoutUnit(
unit.name,
Point(int(unit.position.x), int(unit.position.y)),
unit.position,
int(unit.heading),
)
@dataclass
class GroupLayout:
"""The Layout of a TheaterGroup"""
class TgoLayoutGroup:
"""The layout of a single type of unit within the TgoLayout
Each DCS group that is spawned in the mission is composed of one or more
TgoLayoutGroup. Each TgoLayoutGroup will generate only a single type of unit.
The merging of multiple TgoLayoutGroups to a single DCS group is defined in the
TgoLayout with a dict which uses the Dcs group name as key and the corresponding
TgoLayoutGroups as values.
Each TgoLayoutGroup will be filled with a single type of unit when generated. The
types compatible with the position can either be specified precisely (with
unit_types) or generically (with unit_classes). If neither list specifies units
that can be fulfilled by the faction, fallback_classes will be used. This allows
the early-warning radar template, which prefers units that are defined as early
warning radars like the 55G6, but to fall back to any radar usable by the faction
if EWRs are not available.
A TgoLayoutGroup may be optional. Factions or ForceGroups that are not able to
provide an actual unit for the TgoLayoutGroup will still be able to use the layout;
the optional TgoLayoutGroup will be omitted.
"""
name: str
units: list[LayoutUnit]
layout_units: list[LayoutUnit]
# The group this template will be merged into
group: int = 1
# Define the amount of random units to be created by the randomizer.
# This can be a fixed int or a random value from a range of two ints as tuple
# Define the amount of units to be created. This can be a fixed int or a random
# choice from a range of two ints. If the list is empty it will use the whole group
# size / all available LayoutUnits
unit_count: list[int] = field(default_factory=list)
# defintion which unit types are supported
unit_types: list[Type[DcsUnitType]] = field(default_factory=list)
unit_classes: list[UnitClass] = field(default_factory=list)
alternative_classes: list[UnitClass] = field(default_factory=list)
fallback_classes: list[UnitClass] = field(default_factory=list)
# Defines if this groupTemplate is required or not
optional: bool = False
# if enabled the specific group will be generated during generation
# Can only be set to False if Optional = True
enabled: bool = True
# TODO Caching for faction!
def possible_types_for_faction(self, faction: Faction) -> list[Type[DcsUnitType]]:
"""TODO Description"""
"""Determine the possible dcs unit types for the TgoLayoutGroup and the given faction"""
unit_types = [t for t in self.unit_types if faction.has_access_to_dcs_type(t)]
alternative_types = []
for accessible_unit in faction.accessible_units:
if accessible_unit.unit_class in self.unit_classes:
unit_types.append(accessible_unit.dcs_unit_type)
if accessible_unit.unit_class in self.alternative_classes:
if accessible_unit.unit_class in self.fallback_classes:
alternative_types.append(accessible_unit.dcs_unit_type)
if not unit_types and not alternative_types and not self.optional:
raise LayoutException
raise LayoutException(f"{self.name} not usable by faction {faction.name}")
return unit_types or alternative_types
@property
def unit_counter(self) -> int:
"""TODO Documentation"""
default = len(self.units)
def group_size(self) -> int:
"""The amount of units to be generated. If unit_count is defined in the layout this will be randomized accordingly. Otherwise this will be the maximum size."""
if self.unit_count:
if len(self.unit_count) == 1:
count = self.unit_count[0]
else:
count = random.choice(range(min(self.unit_count), max(self.unit_count)))
if count > default:
logging.error(
f"UnitCount for Group Layout {self.name} "
f"exceeds max available units for this group"
)
return default
return count
return default
return self.unit_count[0]
return random.choice(range(min(self.unit_count), max(self.unit_count)))
return self.max_size
@property
def max_size(self) -> int:
return len(self.units)
return len(self.layout_units)
def generate_units(
self, go: TheaterGroundObject, unit_type: Type[DcsUnitType], amount: int
) -> list[TheaterUnit]:
"""TODO Documentation"""
"""Generate units of the given unit type and amount for the TgoLayoutGroup"""
return [
TheaterUnit.from_template(i, unit_type, self.units[i], go)
TheaterUnit.from_template(i, unit_type, self.layout_units[i], go)
for i in range(amount)
]
class TheaterLayout:
"""TODO Documentation"""
class TgoLayout:
"""TgoLayout defines how a TheaterGroundObject will be generated from a ForceGroup. This defines the positioning, orientation, type and amount of the actual units
def __init__(self, name: str, role: GroupRole, description: str = "") -> None:
Each TgoLayout is defined in resources/layouts with a .yaml file which has all the
information about the Layout next to a .miz file which gives information about the
actual position (x, y) and orientation (heading) of the units. The layout file also
defines the structure of the DCS group (or groups) that will be spawned in the
mission. Complex groups like SAMs protected by point-defense require specific
grouping when used with plugins like Skynet. One group would define the main
battery (the search and track radars, launchers, C2 units, etc), another would
define PD units, and others could define SHORADs or resupply units.
Each group (representing a DCS group) is further divided into TgoLayoutGroups. As
a TgoLayoutGroup only represents a single dcs unit type the logical dcs group of multiple unit types will be created with the usage of a dict which has the DCS Group name as key and a list of TgoLayoutGroups which will be merged into this single dcs group.
As the TgoLayout will be used to create a TheaterGroundObject for a ForceGroup,
specialized classes inherit from this base class. For example there is a special
AiDefenseLayout which will be used to create the SamGroundObject from it.
"""
def __init__(self, name: str, description: str = "") -> None:
self.name = name
self.role = role
self.description = description
self.tasks: list[GroupTask] = [] # The supported tasks
self.groups: list[GroupLayout] = []
self.tasks: list[GroupTask] = [] # The supported
# If the template is generic it will be used the generate the general
# UnitGroups during faction initialization. Generic Groups allow to be mixed
# Mapping of group name and LayoutGroups for a specific TgoGroup
# A Group can have multiple TgoLayoutGroups which get merged together
self.groups: dict[str, list[TgoLayoutGroup]] = defaultdict(list)
# A generic layout will be used to create generic ForceGroups during the
# campaign initialization. For each generic layout a new Group will be created.
self.generic: bool = False
def usable_by_faction(self, faction: Faction) -> bool:
@ -156,7 +180,7 @@ class TheaterLayout:
try:
return all(
len(group.possible_types_for_faction(faction)) > 0
for group in self.groups
for group in self.all_groups
if not group.optional
)
except LayoutException:
@ -168,22 +192,20 @@ class TheaterLayout:
position: PointWithHeading,
control_point: ControlPoint,
) -> TheaterGroundObject:
"""TODO Documentation"""
"""Create the TheaterGroundObject for the TgoLayout
This function has to be implemented by the inheriting class to create
a specific TGO like SamGroundObject or BuildingGroundObject
"""
raise NotImplementedError
def add_group(self, new_group: GroupLayout, index: int = 0) -> None:
"""Adds a group in the correct order to the template"""
if len(self.groups) > index:
self.groups.insert(index, new_group)
else:
self.groups.append(new_group)
@property
def size(self) -> int:
return sum([len(group.units) for group in self.groups])
def all_groups(self) -> Iterator[TgoLayoutGroup]:
for groups in self.groups.values():
yield from groups
class AntiAirLayout(TheaterLayout):
class AntiAirLayout(TgoLayout):
def create_ground_object(
self,
name: str,
@ -200,7 +222,7 @@ class AntiAirLayout(TheaterLayout):
)
class BuildingLayout(TheaterLayout):
class BuildingLayout(TgoLayout):
def create_ground_object(
self,
name: str,
@ -224,7 +246,7 @@ class BuildingLayout(TheaterLayout):
raise RuntimeError(f"Building Template {self.name} has no building category")
class NavalLayout(TheaterLayout):
class NavalLayout(TgoLayout):
def create_ground_object(
self,
name: str,
@ -240,7 +262,7 @@ class NavalLayout(TheaterLayout):
raise NotImplementedError
class DefensesLayout(TheaterLayout):
class DefensesLayout(TgoLayout):
def create_ground_object(
self,
name: str,
@ -258,7 +280,7 @@ class DefensesLayout(TheaterLayout):
raise NotImplementedError
class GroundForceLayout(TheaterLayout):
class GroundForceLayout(TgoLayout):
def create_ground_object(
self,
name: str,

View File

@ -1,4 +1,5 @@
from __future__ import annotations
from collections import defaultdict
import itertools
import logging
@ -13,10 +14,10 @@ from dcs import Point
from dcs.unitgroup import StaticGroup
from game import persistency
from game.data.groups import GroupRole, GroupTask
from game.data.groups import GroupRole
from game.layout.layout import (
TheaterLayout,
GroupLayout,
TgoLayout,
TgoLayoutGroup,
LayoutUnit,
AntiAirLayout,
BuildingLayout,
@ -24,7 +25,7 @@ from game.layout.layout import (
GroundForceLayout,
DefensesLayout,
)
from game.layout.layoutmapping import GroupLayoutMapping, LayoutMapping
from game.layout.layoutmapping import LayoutMapping
from game.profiling import logged_duration
from game.version import VERSION
@ -41,21 +42,21 @@ TEMPLATE_TYPES = {
class LayoutLoader:
# list of layouts per category. e.g. AA or similar
_templates: dict[str, TheaterLayout] = {}
# Map of all available layouts indexed by name
_layouts: dict[str, TgoLayout] = {}
def __init__(self) -> None:
self._templates = {}
self._layouts = {}
def initialize(self) -> None:
if not self._templates:
if not self._layouts:
with logged_duration("Loading layouts"):
self.load_templates()
@property
def layouts(self) -> Iterator[TheaterLayout]:
def layouts(self) -> Iterator[TgoLayout]:
self.initialize()
yield from self._templates.values()
yield from self._layouts.values()
def load_templates(self) -> None:
"""This will load all pre-loaded layouts from a pickle file.
@ -66,60 +67,43 @@ class LayoutLoader:
# Load from pickle if existing
with file.open("rb") as f:
try:
version, self._templates = pickle.load(f)
version, self._layouts = pickle.load(f)
# Check if the game version of the dump is identical to the current
if version == VERSION:
return
except Exception as e:
logging.error(f"Error {e} reading layouts dump. Recreating.")
logging.exception(f"Error {e} reading layouts dump. Recreating.")
# If no dump is available or game version is different create a new dump
self.import_templates()
def import_templates(self) -> None:
"""This will import all layouts from the template folder
and dumps them to a pickle"""
mappings: dict[str, list[LayoutMapping]] = {}
self._layouts = {}
mappings: dict[str, list[LayoutMapping]] = defaultdict(list)
with logged_duration("Parsing mapping yamls"):
for file in Path(TEMPLATE_DIR).rglob("*.yaml"):
if not file.is_file():
continue
raise RuntimeError(f"{file.name} is not a file")
with file.open("r", encoding="utf-8") as f:
mapping_dict = yaml.safe_load(f)
template_map = LayoutMapping.from_dict(mapping_dict, f.name)
if template_map.layout_file in mappings:
mappings[template_map.layout_file].append(template_map)
else:
mappings[template_map.layout_file] = [template_map]
mappings[template_map.layout_file].append(template_map)
with logged_duration(f"Parsing all layout miz multithreaded"):
with ThreadPoolExecutor() as exe:
for miz, maps in mappings.items():
exe.submit(self._load_from_miz, miz, maps)
exe.map(self._load_from_miz, mappings.keys(), mappings.values())
logging.info(f"Imported {len(self._templates)} layouts")
logging.info(f"Imported {len(self._layouts)} layouts")
self._dump_templates()
def _dump_templates(self) -> None:
file = Path(persistency.base_path()) / TEMPLATE_DUMP
dump = (VERSION, self._templates)
dump = (VERSION, self._layouts)
with file.open("wb") as fdata:
pickle.dump(dump, fdata)
@staticmethod
def mapping_for_group(
mappings: list[LayoutMapping], group_name: str
) -> tuple[LayoutMapping, int, GroupLayoutMapping]:
for mapping in mappings:
for g_id, group_mapping in enumerate(mapping.groups):
if (
group_mapping.name == group_name
or group_name in group_mapping.statics
):
return mapping, g_id, group_mapping
raise KeyError
def _load_from_miz(self, miz: str, mappings: list[LayoutMapping]) -> None:
template_position: dict[str, Point] = {}
temp_mis = dcs.Mission()
@ -130,74 +114,68 @@ class LayoutLoader:
# the .load_file() method: 0:00:00.920409
temp_mis.load_file(miz)
for country in itertools.chain(
temp_mis.coalition["red"].countries.values(),
temp_mis.coalition["blue"].countries.values(),
):
for dcs_group in itertools.chain(
temp_mis.country(country.name).vehicle_group,
temp_mis.country(country.name).ship_group,
temp_mis.country(country.name).static_group,
for mapping in mappings:
# Find the group from the mapping in any coalition
for country in itertools.chain(
temp_mis.coalition["red"].countries.values(),
temp_mis.coalition["blue"].countries.values(),
):
try:
mapping, group_id, group_mapping = self.mapping_for_group(
mappings, dcs_group.name
)
except KeyError:
logging.warning(f"No mapping for dcs group {dcs_group.name}")
continue
for dcs_group in itertools.chain(
temp_mis.country(country.name).vehicle_group,
temp_mis.country(country.name).ship_group,
temp_mis.country(country.name).static_group,
):
template = self._templates.get(mapping.name, None)
if template is None:
# Create a new template
template = TEMPLATE_TYPES[mapping.role](
mapping.name, mapping.role, mapping.description
)
template.generic = mapping.generic
template.tasks = mapping.tasks
self._templates[template.name] = template
for i, unit in enumerate(dcs_group.units):
group_template = None
for group in template.groups:
if group.name == group_mapping.name:
# We already have a layoutgroup for this dcs_group
group_template = group
if not group_template:
group_template = GroupLayout(
group_mapping.name,
[],
group_mapping.group,
group_mapping.unit_count,
group_mapping.unit_types,
group_mapping.unit_classes,
group_mapping.alternative_classes,
try:
group_name, group_mapping = mapping.group_for_name(
dcs_group.name
)
group_template.optional = group_mapping.optional
# Add the group at the correct position
template.add_group(group_template, group_id)
unit_template = LayoutUnit.from_unit(unit)
if i == 0 and template.name not in template_position:
template_position[template.name] = unit.position
unit_template.position = (
unit_template.position - template_position[template.name]
)
group_template.units.append(unit_template)
except KeyError:
continue
def by_name(self, template_name: str) -> Iterator[TheaterLayout]:
for template in self.layouts:
if template.name == template_name:
yield template
if not isinstance(dcs_group, StaticGroup) and max(
group_mapping.unit_count
) > len(dcs_group.units):
logging.error(
f"Incorrect unit_count found in Layout {mapping.name}-{group_mapping.name}"
)
def by_task(self, group_task: GroupTask) -> Iterator[TheaterLayout]:
for template in self.layouts:
if not group_task or group_task in template.tasks:
yield template
layout = self._layouts.get(mapping.name, None)
if layout is None:
# Create a new template
layout = TEMPLATE_TYPES[mapping.primary_role](
mapping.name, mapping.description
)
layout.generic = mapping.generic
layout.tasks = mapping.tasks
self._layouts[layout.name] = layout
def by_tasks(self, group_tasks: list[GroupTask]) -> Iterator[TheaterLayout]:
unique_templates = []
for group_task in group_tasks:
for template in self.by_task(group_task):
if template not in unique_templates:
unique_templates.append(template)
yield from unique_templates
for i, unit in enumerate(dcs_group.units):
group_layout = None
for group in layout.all_groups:
if group.name == group_mapping.name:
# We already have a layoutgroup for this dcs_group
group_layout = group
if not group_layout:
group_layout = TgoLayoutGroup(
group_mapping.name,
[],
group_mapping.unit_count,
group_mapping.unit_types,
group_mapping.unit_classes,
group_mapping.fallback_classes,
)
group_layout.optional = group_mapping.optional
# Add the group at the correct position
layout.groups[group_name].append(group_layout)
layout_unit = LayoutUnit.from_unit(unit)
if i == 0 and layout.name not in template_position:
template_position[layout.name] = unit.position
layout_unit.position = (
layout_unit.position - template_position[layout.name]
)
group_layout.layout_units.append(layout_unit)
def by_name(self, name: str) -> TgoLayout:
self.initialize()
return self._layouts[name]

View File

@ -1,13 +1,14 @@
from __future__ import annotations
from collections import defaultdict
from dataclasses import dataclass, field
from typing import Any, Type
from dcs.unittype import UnitType as DcsUnitType
from game import db
from game.data.groups import GroupRole, GroupTask
from game.data.units import UnitClass
from game.dcs.helpers import unit_type_from_name
@dataclass
@ -21,10 +22,6 @@ class GroupLayoutMapping:
# All static units for the group
statics: list[str] = field(default_factory=list)
# Defines to which tgo group the groupTemplate will be added
# This allows to merge groups back together. Default: Merge all to group 1
group: int = field(default=1)
# How many units should be generated from the grouplayout. If only one value is
# added this will be an exact amount. If 2 values are used it will be a random
# amount between these values.
@ -36,31 +33,9 @@ class GroupLayoutMapping:
# All unit classes the template supports.
unit_classes: list[UnitClass] = field(default_factory=list)
# TODO Clarify if this is required. Only used for EWRs to also Use SR when no
# Fallback Classes which are used when the unit_classes and unit_types do not fit any accessible unit from the faction. Only used for EWRs to also Use SR when no
# dedicated EWRs are available to the faction
alternative_classes: list[UnitClass] = field(default_factory=list)
def to_dict(self) -> dict[str, Any]:
d = self.__dict__
if not self.optional:
d.pop("optional")
if not self.statics:
d.pop("statics")
if not self.unit_types:
d.pop("unit_types")
if not self.unit_classes:
d.pop("unit_classes")
else:
d["unit_classes"] = [unit_class.value for unit_class in self.unit_classes]
if not self.alternative_classes:
d.pop("alternative_classes")
else:
d["alternative_classes"] = [
unit_class.value for unit_class in self.alternative_classes
]
if not self.unit_count:
d.pop("unit_count")
return d
fallback_classes: list[UnitClass] = field(default_factory=list)
@staticmethod
def from_dict(d: dict[str, Any]) -> GroupLayoutMapping:
@ -70,27 +45,25 @@ class GroupLayoutMapping:
unit_types = []
if "unit_types" in d:
for u in d["unit_types"]:
unit_type = db.unit_type_from_name(u)
unit_type = unit_type_from_name(u)
if unit_type:
unit_types.append(unit_type)
group = d["group"] if "group" in d else 1
unit_classes = (
[UnitClass(u) for u in d["unit_classes"]] if "unit_classes" in d else []
)
alternative_classes = (
[UnitClass(u) for u in d["alternative_classes"]]
if "alternative_classes" in d
fallback_classes = (
[UnitClass(u) for u in d["fallback_classes"]]
if "fallback_classes" in d
else []
)
return GroupLayoutMapping(
d["name"],
optional,
statics,
group,
unit_count,
unit_types,
unit_classes,
alternative_classes,
fallback_classes,
)
@ -105,40 +78,31 @@ class LayoutMapping:
# Optional field to define if the template can be used to create generic groups
generic: bool
# The role the template can be used for
role: GroupRole
# All taskings the template can be used for
tasks: list[GroupTask]
# All Groups the template has
groups: list[GroupLayoutMapping]
groups: dict[str, list[GroupLayoutMapping]]
# Define the miz file for the template. Optional. If empty use the mapping name
layout_file: str
def to_dict(self) -> dict[str, Any]:
d = {
"name": self.name,
"description": self.description,
"generic": self.generic,
"role": self.role.value,
"tasks": [task.description for task in self.tasks],
"groups": [group.to_dict() for group in self.groups],
"layout_file": self.layout_file,
}
if not self.description:
d.pop("description")
if not self.generic:
# Only save if true
d.pop("generic")
if not self.layout_file:
d.pop("layout_file")
return d
@property
def primary_role(self) -> GroupRole:
return self.tasks[0].role
@staticmethod
def from_dict(d: dict[str, Any], file_name: str) -> LayoutMapping:
groups = [GroupLayoutMapping.from_dict(group) for group in d["groups"]]
groups: dict[str, list[GroupLayoutMapping]] = defaultdict(list)
for group in d["groups"]:
for group_name, group_layouts in group.items():
groups[group_name].extend(
[
GroupLayoutMapping.from_dict(group_layout)
for group_layout in group_layouts
]
)
description = d["description"] if "description" in d else ""
generic = d["generic"] if "generic" in d else False
layout_file = (
@ -149,8 +113,14 @@ class LayoutMapping:
d["name"],
description,
generic,
GroupRole(d["role"]),
tasks,
groups,
layout_file,
)
def group_for_name(self, name: str) -> tuple[str, GroupLayoutMapping]:
for group_name, group_mappings in self.groups.items():
for group_mapping in group_mappings:
if group_mapping.name == name or name in group_mapping.statics:
return group_name, group_mapping
raise KeyError

View File

@ -153,11 +153,6 @@ class ControlPointGroundObjectGenerator:
self.generate_navy()
return True
def generate_random_ground_object(
self, unit_groups: list[ForceGroup], position: PointWithHeading
) -> None:
self.generate_ground_object_from_group(random.choice(unit_groups), position)
def generate_ground_object_from_group(
self, unit_group: ForceGroup, position: PointWithHeading
) -> None:

View File

@ -3,7 +3,7 @@ from __future__ import annotations
import itertools
import logging
from abc import ABC
from typing import Iterator, List, TYPE_CHECKING
from typing import Iterator, List, TYPE_CHECKING, Optional
from dcs.unittype import VehicleType
from dcs.vehicles import vehicle_map
@ -205,6 +205,12 @@ class TheaterGroundObject(MissionTarget):
def purchasable(self) -> bool:
raise NotImplementedError
def group_by_name(self, name: str) -> Optional[TheaterGroup]:
for group in self.groups:
if group.name == name:
return group
return None
class BuildingGroundObject(TheaterGroundObject):
def __init__(

View File

@ -16,7 +16,7 @@ from game.point_with_heading import PointWithHeading
from game.utils import Heading
if TYPE_CHECKING:
from game.layout.layout import LayoutUnit, GroupLayout
from game.layout.layout import LayoutUnit, TgoLayoutGroup
from game.theater import TheaterGroundObject
@ -141,16 +141,17 @@ class TheaterGroup:
@staticmethod
def from_template(
id: int,
g: GroupLayout,
name: str,
units: list[TheaterUnit],
go: TheaterGroundObject,
unit_type: Type[DcsUnitType],
unit_count: int,
) -> TheaterGroup:
return TheaterGroup(
id,
g.name,
name,
PointWithHeading.from_point(go.position, go.heading),
g.generate_units(go, unit_type, unit_count),
units,
go,
)

View File

@ -18,10 +18,11 @@ from PySide2.QtWidgets import (
)
import qt_ui.uiconstants as CONST
from game import Game, VERSION, persistency, db
from game import Game, VERSION, persistency
from game.debriefing import Debriefing
from game.server import EventStream, GameContext
from game.server.security import ApiKeyManager
from game.layout import LAYOUTS
from qt_ui import liberation_install
from qt_ui.dialogs import Dialog
from qt_ui.models import GameModel
@ -400,7 +401,7 @@ class QLiberationWindow(QMainWindow):
self.dialog.show()
def import_templates(self):
db.LAYOUTS.import_templates()
LAYOUTS.import_templates()
def showLogsDialog(self):
self.dialog = QLogsWindow()

View File

@ -1,3 +1,4 @@
from collections import defaultdict
import logging
from dataclasses import dataclass, field
from typing import Type
@ -14,6 +15,7 @@ from PySide2.QtWidgets import (
QSpinBox,
QVBoxLayout,
QCheckBox,
QWidget,
)
from dcs.unittype import UnitType
@ -29,16 +31,17 @@ from game.theater.theatergroundobject import (
)
from game.theater.theatergroup import TheaterGroup
from game.layout.layout import (
TheaterLayout,
GroupLayout,
LayoutException,
TgoLayout,
TgoLayoutGroup,
)
from qt_ui.uiconstants import EVENT_ICONS
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
@dataclass
class QGroupLayout:
layout: GroupLayout
class QTgoLayoutGroup:
layout: TgoLayoutGroup
dcs_unit_type: Type[UnitType]
amount: int
unit_price: int
@ -50,41 +53,45 @@ class QGroupLayout:
@dataclass
class QLayout:
layout: TheaterLayout
class QTgoLayout:
layout: TgoLayout
force_group: ForceGroup
group_layouts: list[QGroupLayout] = field(default_factory=list)
groups: dict[str, list[QTgoLayoutGroup]] = field(default_factory=dict)
@property
def price(self) -> int:
return sum(group.price for group in self.group_layouts)
return sum(group.price for groups in self.groups.values() for group in groups)
class QGroundObjectGroupTemplate(QGroupBox):
class QTgoLayoutGroupRow(QWidget):
group_template_changed = Signal()
def __init__(
self, group_id: int, force_group: ForceGroup, group_layout: GroupLayout
) -> None:
super().__init__(f"{group_id + 1}: {group_layout.name}")
def __init__(self, force_group: ForceGroup, group: TgoLayoutGroup) -> None:
super().__init__()
self.grid_layout = QGridLayout()
self.setLayout(self.grid_layout)
self.grid_layout.setColumnStretch(0, 100)
self.amount_selector = QSpinBox()
self.unit_selector = QComboBox()
self.unit_selector.setMinimumWidth(250)
self.group_selector = QCheckBox()
# Add all possible units with the price
for unit_type in force_group.unit_types_for_group(group_layout):
for unit_type in force_group.unit_types_for_group(group):
self.unit_selector.addItem(
f"{unit_type.name} [${unit_type.price}M]",
userData=(unit_type.dcs_unit_type, unit_type.price),
)
# Add all possible statics with price = 0
for static_type in force_group.statics_for_group(group_layout):
for static_type in force_group.statics_for_group(group):
self.unit_selector.addItem(
f"{static_type} (Static)", userData=(static_type, 0)
)
if self.unit_selector.count() == 0:
raise LayoutException("No units available for the TgoLayoutGroup")
self.unit_selector.adjustSize()
self.unit_selector.setEnabled(self.unit_selector.count() > 1)
self.grid_layout.addWidget(self.unit_selector, 0, 0, alignment=Qt.AlignRight)
self.grid_layout.addWidget(self.amount_selector, 0, 1, alignment=Qt.AlignRight)
@ -93,9 +100,7 @@ class QGroundObjectGroupTemplate(QGroupBox):
self.unit_selector.currentIndex()
)
self.group_layout = QGroupLayout(
group_layout, unit_type, group_layout.unit_counter, price
)
self.group_layout = QTgoLayoutGroup(group, unit_type, group.group_size, price)
self.group_selector.setChecked(self.group_layout.enabled)
self.group_selector.setEnabled(self.group_layout.layout.optional)
@ -127,11 +132,11 @@ class QGroundObjectTemplateLayout(QGroupBox):
self,
game: Game,
ground_object: TheaterGroundObject,
layout: QLayout,
layout_changed_signal: Signal(QLayout),
layout: QTgoLayout,
layout_changed_signal: Signal(QTgoLayout),
current_group_value: int,
):
super().__init__("Groups:")
super().__init__()
# Connect to the signal to handle template updates
self.game = game
self.ground_object = ground_object
@ -158,21 +163,32 @@ class QGroundObjectTemplateLayout(QGroupBox):
# Load Layout
self.load_for_layout(self.layout_model)
def load_for_layout(self, layout: QLayout) -> None:
def load_for_layout(self, layout: QTgoLayout) -> None:
self.layout_model = layout
# Clean the current grid
self.layout_model.groups = defaultdict(list)
for id in range(self.template_grid.count()):
self.template_grid.itemAt(id).widget().deleteLater()
for g_id, layout_group in enumerate(self.layout_model.layout.groups):
group_row = QGroundObjectGroupTemplate(
g_id, self.layout_model.force_group, layout_group
)
self.layout_model.group_layouts.append(group_row.group_layout)
group_row.group_template_changed.connect(self.group_template_changed)
self.template_grid.addWidget(group_row)
for group_name, groups in self.layout_model.layout.groups.items():
self.add_theater_group(group_name, self.layout_model.force_group, groups)
self.group_template_changed()
def add_theater_group(
self, group_name: str, force_group: ForceGroup, groups: list[TgoLayoutGroup]
) -> None:
group_box = QGroupBox(group_name)
vbox_layout = QVBoxLayout()
for group in groups:
try:
group_row = QTgoLayoutGroupRow(force_group, group)
except LayoutException:
continue
self.layout_model.groups[group_name].append(group_row.group_layout)
group_row.group_template_changed.connect(self.group_template_changed)
vbox_layout.addWidget(group_row)
group_box.setLayout(vbox_layout)
self.template_grid.addWidget(group_box)
def group_template_changed(self) -> None:
price = self.layout_model.price
self.buy_button.setText(f"Buy [${price}M][-${self.current_group_value}M]")
@ -182,54 +198,40 @@ class QGroundObjectTemplateLayout(QGroupBox):
else:
self.buy_button.setToolTip("Not enough money to buy this group")
def buy_group(self):
if not self.layout:
raise RuntimeError("No template selected. GroundObject can not be bought.")
def buy_group(self) -> None:
price = self.layout_model.price
if price > self.game.blue.budget:
# Somethin went wrong. Buy button should be disabled!
# Something went wrong. Buy button should be disabled!
logging.error("Not enough money to buy the group")
return
self.game.blue.budget -= price - self.current_group_value
self.ground_object.groups = self.generate_groups()
self.ground_object.groups = []
for group_name, groups in self.layout_model.groups.items():
for group in groups:
self.layout_model.force_group.create_theater_group_for_tgo(
self.ground_object,
group.layout,
f"{self.ground_object.name} ({group_name})",
self.game,
group.dcs_unit_type, # Forced Type
group.amount, # Forced Amount
)
# Replan redfor missions
self.game.initialize_turn(for_red=True, for_blue=False)
GameUpdateSignal.get_instance().updateGame(self.game)
def generate_groups(self) -> list[TheaterGroup]:
go = self.layout_model.layout.create_ground_object(
self.ground_object.name,
PointWithHeading.from_point(
self.ground_object.position, self.ground_object.heading
),
self.ground_object.control_point,
)
for group in self.layout_model.group_layouts:
self.layout_model.force_group.create_theater_group_for_tgo(
go,
group.layout,
self.ground_object.name,
self.game,
group.dcs_unit_type, # Forced Type
group.amount, # Forced Amount
)
return go.groups
class QGroundObjectBuyMenu(QDialog):
layout_changed_signal = Signal(QLayout)
layout_changed_signal = Signal(QTgoLayout)
def __init__(
self,
parent,
parent: QWidget,
ground_object: TheaterGroundObject,
game: Game,
current_group_value: int,
):
) -> None:
super().__init__(parent)
self.setMinimumWidth(350)
@ -241,7 +243,9 @@ class QGroundObjectBuyMenu(QDialog):
self.setLayout(self.mainLayout)
self.force_group_selector = QComboBox()
self.force_group_selector.setMinimumWidth(250)
self.layout_selector = QComboBox()
self.layout_selector.setMinimumWidth(250)
self.layout_selector.setEnabled(False)
# Get the layouts and fill the combobox
@ -262,19 +266,19 @@ class QGroundObjectBuyMenu(QDialog):
for group in game.blue.armed_forces.groups_for_tasks(tasks):
self.force_group_selector.addItem(group.name, userData=group)
self.force_group_selector.setEnabled(self.force_group_selector.count() > 1)
self.force_group_selector.adjustSize()
force_group = self.force_group_selector.itemData(
self.force_group_selector.currentIndex()
)
for layout in force_group.layouts:
self.layout_selector.addItem(layout.name, userData=layout)
self.layout_selector.adjustSize()
selected_template = self.layout_selector.itemData(
self.layout_selector.currentIndex()
)
self.layout_model = QLayout(selected_template, force_group)
self.theater_layout = QTgoLayout(selected_template, force_group)
self.layout_selector.currentIndexChanged.connect(self.layout_changed)
self.force_group_selector.currentIndexChanged.connect(self.force_group_changed)
@ -295,7 +299,7 @@ class QGroundObjectBuyMenu(QDialog):
self.template_layout = QGroundObjectTemplateLayout(
game,
ground_object,
self.layout_model,
self.theater_layout,
self.layout_changed_signal,
current_group_value,
)
@ -311,19 +315,19 @@ class QGroundObjectBuyMenu(QDialog):
self.layout_selector.clear()
for layout in unit_group.layouts:
self.layout_selector.addItem(layout.name, userData=layout)
self.layout_selector.adjustSize()
# Enable if more than one template is available
self.layout_selector.setEnabled(len(unit_group.layouts) > 1)
# Enable Combobox Signals again
self.layout_selector.blockSignals(False)
self.layout_changed()
def layout_changed(self):
def layout_changed(self) -> None:
self.layout()
self.layout_model.layout = self.layout_selector.itemData(
self.theater_layout.layout = self.layout_selector.itemData(
self.layout_selector.currentIndex()
)
self.layout_model.force_group = self.force_group_selector.itemData(
self.theater_layout.force_group = self.force_group_selector.itemData(
self.force_group_selector.currentIndex()
)
self.layout_model.group_layouts = []
self.layout_changed_signal.emit(self.layout_model)
self.layout_changed_signal.emit(self.theater_layout)

View File

@ -1,5 +1,4 @@
name: Ally Flak
role: AntiAir
tasks:
- AAA
units:

View File

@ -1,5 +1,4 @@
name: Carrier Strike Group 8
role: Naval
tasks:
- Navy
units:

View File

@ -1,5 +1,4 @@
name: Chinese Navy
role: Naval
tasks:
- Navy
units:

View File

@ -1,5 +1,4 @@
name: Cold-War-Flak
role: AntiAir
tasks:
- AAA
units:

View File

@ -1,5 +1,4 @@
name: Flak
role: AntiAir
tasks:
- AAA
units:

View File

@ -1,5 +1,4 @@
name: Freya
role: AntiAir
tasks:
- SHORAD
units:

View File

@ -1,5 +1,4 @@
name: HQ-7
role: AntiAir
tasks:
- SHORAD
units:

View File

@ -1,5 +1,4 @@
name: Hawk
role: AntiAir
tasks:
- MERAD
units:

View File

@ -1,5 +1,4 @@
name: KS-19
role: AntiAir
tasks:
- AAA
units:

View File

@ -1,5 +1,4 @@
name: NASAMS AIM-120B
role: AntiAir
tasks:
- MERAD
units:

View File

@ -1,5 +1,4 @@
name: NASAMS AIM-120C
role: AntiAir
tasks:
- MERAD
units:

View File

@ -1,5 +1,4 @@
name: Patriot
role: AntiAir
tasks:
- LORAD
units:

View File

@ -1,5 +1,4 @@
name: Rapier
role: AntiAir
tasks:
- SHORAD
units:

View File

@ -1,5 +1,4 @@
name: Roland
role: AntiAir
tasks:
- SHORAD
units:

View File

@ -1,5 +1,4 @@
name: Russian Navy
role: Naval
tasks:
- Navy
units:

View File

@ -1,5 +1,4 @@
name: SA-10/S-300PS
role: AntiAir
tasks:
- LORAD
units:

View File

@ -1,5 +1,4 @@
name: SA-10B/S-300PS
role: AntiAir
tasks:
- LORAD
units:

View File

@ -1,5 +1,4 @@
name: SA-11
role: AntiAir
tasks:
- MERAD
units:

View File

@ -1,5 +1,4 @@
name: SA-12/S-300V
role: AntiAir
tasks:
- LORAD
units:

View File

@ -1,5 +1,4 @@
name: SA-17
role: AntiAir
tasks:
- MERAD
units:

View File

@ -1,5 +1,4 @@
name: SA-2/S-75
role: AntiAir
tasks:
- MERAD
units:

View File

@ -1,5 +1,4 @@
name: SA-20/S-300PMU-1
role: AntiAir
tasks:
- LORAD
units:

View File

@ -1,5 +1,4 @@
name: SA-20B/S-300PMU-2
role: AntiAir
tasks:
- LORAD
units:

View File

@ -1,5 +1,4 @@
name: SA-23/S-300VM
role: AntiAir
tasks:
- LORAD
units:

View File

@ -1,5 +1,4 @@
name: SA-3/S-125
role: AntiAir
tasks:
- MERAD
units:

View File

@ -1,5 +1,4 @@
name: SA-5/S-200
role: AntiAir
tasks:
- LORAD
units:

View File

@ -1,5 +1,4 @@
name: SA-6
role: AntiAir
tasks:
- MERAD
units:

View File

@ -1,5 +1,4 @@
name: Silkworm
role: Defenses
tasks:
- Coastal
units:

View File

@ -1,5 +1,4 @@
name: WW2LST
role: Naval
tasks:
- Navy
units:

View File

@ -1,23 +1,21 @@
name: AAA Mobile
description: A standard AAA template
generic: true
role: AntiAir
generic: true # This Layout will be used to generate ForceGroups
tasks:
- AAA
groups:
- name: AAA Mobile 0
group: 1
unit_count:
- 2
- 6
unit_classes:
- AAA
- name: AAA Mobile 1
optional: true
group: 1
unit_count:
- 1
- 2
unit_classes:
- Logistics
- AAA: # Group Name
- name: AAA Mobile 0 # Sub group which will be merged into the group
unit_count:
- 2
- 6
unit_classes:
- AAA
- name: AAA Mobile 1 # Sub group which will be merged into the group
optional: true
unit_count:
- 1
- 2
unit_classes:
- Logistics
layout_file: resources/layouts/anti_air/AAA.miz

View File

@ -1,29 +1,26 @@
name: AAA Radar Site
description: AAA Template with a Radar
generic: false
role: AntiAir
tasks:
- AAA
groups:
- name: AAA Radar Site 0
group: 1
unit_count:
- 1
unit_classes:
- SearchRadar
- name: AAA Radar Site 1
group: 1
unit_count:
- 2
- 6
unit_classes:
- AAA
- name: AAA Radar Site 2
optional: true
group: 1
unit_count:
- 1
- 2
unit_classes:
- Logistics
- AAA:
- name: AAA Radar Site 0
unit_count:
- 1
unit_classes:
- SearchRadar
- name: AAA Radar Site 1
unit_count:
- 2
- 6
unit_classes:
- AAA
- name: AAA Radar Site 2
optional: true
unit_count:
- 1
- 2
unit_classes:
- Logistics
layout_file: resources/layouts/anti_air/AAA.miz

View File

@ -1,23 +1,21 @@
name: AAA Site
description: A standard AAA template
generic: true
role: AntiAir
tasks:
- AAA
groups:
- name: AAA Site 0
group: 1
unit_count:
- 2
- 6
unit_classes:
- AAA
- name: AAA Site 1
optional: true
group: 1
unit_count:
- 1
- 2
unit_classes:
- Logistics
- AAA:
- name: AAA Site 0
unit_count:
- 2
- 6
unit_classes:
- AAA
- name: AAA Site 1
optional: true
unit_count:
- 1
- 2
unit_classes:
- Logistics
layout_file: resources/layouts/anti_air/AAA.miz

View File

@ -1,36 +1,36 @@
name: Cold War Flak Site
role: AntiAir
tasks:
- AAA
groups:
- name: Cold War Flak Site Radar
optional: true # Only available to Late Cold War
unit_count:
- 1
unit_classes:
- SearchRadar
- name: Cold War Flak Site Flak
unit_count:
- 4
- 6
unit_types:
- flak18
- name: Cold War Flak Site S-60
unit_count:
- 2
unit_types:
- S-60_Type59_Artillery
- name: Cold War Flak Site AAA
optional: true
unit_count:
- 2
unit_classes:
- AAA
- name: Cold War Flak Site Logistics
optional: true
unit_count:
- 1
- 2
unit_classes:
- Logistics
- AAA:
- name: Cold War Flak Site Radar
optional: true # Only available to Late Cold War
unit_count:
- 1
unit_classes:
- SearchRadar
- name: Cold War Flak Site Flak
unit_count:
- 4
- 6
unit_types:
- flak18
- name: Cold War Flak Site S-60
unit_count:
- 2
unit_types:
- S-60_Type59_Artillery
- name: Cold War Flak Site AAA
optional: true
unit_count:
- 2
unit_classes:
- AAA
- name: Cold War Flak Site Logistics
optional: true
unit_count:
- 1
- 2
unit_classes:
- Logistics
layout_file: resources/layouts/anti_air/flak.miz

View File

@ -1,15 +1,15 @@
name: Early-Warning Radar
generic: true
role: AntiAir
tasks:
- EarlyWarningRadar
groups:
- name: Early-Warning Radar 0
unit_count:
- 1
unit_classes:
- EarlyWarningRadar
alternative_classes:
- SearchRadar
- SearchTrackRadar
- EWR:
- name: Early-Warning Radar 0
unit_count:
- 1
unit_classes:
- EarlyWarningRadar
alternative_classes:
- SearchRadar
- SearchTrackRadar
layout_file: resources/layouts/anti_air/legacy_ground_templates.miz

View File

@ -1,51 +1,51 @@
name: Flak Site
role: AntiAir
tasks:
- AAA
groups:
- name: Flak Site 0
unit_count:
- 4
unit_types:
- flak38
- flak18
- flak36
- flak37
- flak41
- flak30
- name: Flak Site 1
unit_count:
- 1
unit_types:
- flak38
- name: Flak Site 2
unit_count:
- 1
unit_types:
- flak36
- name: Flak Site 3
unit_count:
- 2
unit_types:
- Flakscheinwerfer_37
- name: Flak Site 4
unit_count:
- 1
unit_types:
- Maschinensatz_33
- name: Flak Site 5
unit_count:
- 1
unit_types:
- KDO_Mod40
- name: Flak Site 6
unit_count:
- 1
unit_types:
- Kubelwagen_82
- name: Flak Site 7
unit_count:
- 4
unit_types:
- Blitz_36-6700A
- Flak:
- name: Flak Site 0
unit_count:
- 4
unit_types:
- flak38
- flak18
- flak36
- flak37
- flak41
- flak30
- name: Flak Site 1
unit_count:
- 1
unit_types:
- flak38
- name: Flak Site 2
unit_count:
- 1
unit_types:
- flak36
- name: Flak Site 3
unit_count:
- 2
unit_types:
- Flakscheinwerfer_37
- name: Flak Site 4
unit_count:
- 1
unit_types:
- Maschinensatz_33
- name: Flak Site 5
unit_count:
- 1
unit_types:
- KDO_Mod40
- name: Flak Site 6
unit_count:
- 1
unit_types:
- Kubelwagen_82
- name: Flak Site 7
unit_count:
- 4
unit_types:
- Blitz_36-6700A
layout_file: resources/layouts/anti_air/legacy_ground_templates.miz

View File

@ -1,51 +1,51 @@
name: Freya EWR Site
role: AntiAir
tasks:
- SHORAD
groups:
- name: Freya EWR Site 0
unit_count:
- 1
unit_types:
- FuMG-401
- name: Freya EWR Site 1
unit_count:
- 4
unit_types:
- flak38
- name: Freya EWR Site 2
unit_count:
- 4
unit_types:
- flak18
- name: Freya EWR Site 3
unit_count:
- 1
unit_types:
- Kubelwagen_82
- name: Freya EWR Site 4
unit_count:
- 1
unit_types:
- Sd_Kfz_7
- name: Freya EWR Site 5
unit_count:
- 1
unit_types:
- Sd_Kfz_2
- name: Freya EWR Site 6
unit_count:
- 1
unit_types:
- Maschinensatz_33
- name: Freya EWR Site 7
unit_count:
- 1
unit_types:
- KDO_Mod40
- name: Freya EWR Site 8
unit_count:
- 3
unit_types:
- soldier_mauser98
- Freya:
- name: Freya EWR Site 0
unit_count:
- 1
unit_types:
- FuMG-401
- name: Freya EWR Site 1
unit_count:
- 4
unit_types:
- flak38
- name: Freya EWR Site 2
unit_count:
- 4
unit_types:
- flak18
- name: Freya EWR Site 3
unit_count:
- 1
unit_types:
- Kubelwagen_82
- name: Freya EWR Site 4
unit_count:
- 1
unit_types:
- Sd_Kfz_7
- name: Freya EWR Site 5
unit_count:
- 1
unit_types:
- Sd_Kfz_2
- name: Freya EWR Site 6
unit_count:
- 1
unit_types:
- Maschinensatz_33
- name: Freya EWR Site 7
unit_count:
- 1
unit_types:
- KDO_Mod40
- name: Freya EWR Site 8
unit_count:
- 3
unit_types:
- soldier_mauser98
layout_file: resources/layouts/anti_air/legacy_ground_templates.miz

View File

@ -1,24 +1,21 @@
name: HQ-7 Site
role: AntiAir
tasks:
- SHORAD
groups:
- name: HQ-7 Site 0
group: 1
unit_count:
- 1
unit_types:
- HQ-7_STR_SP
- name: HQ-7 Site 1
group: 1
unit_count:
- 2
unit_types:
- HQ-7_LN_SP
- name: HQ-7 Site 2
group: 2
unit_count:
- 2
unit_types:
- Ural-375 ZU-23
- HQ-7:
- name: HQ-7 Site 0
unit_count:
- 1
unit_types:
- HQ-7_STR_SP
- name: HQ-7 Site 1
unit_count:
- 2
unit_types:
- HQ-7_LN_SP
- name: HQ-7 Site 2
unit_count:
- 2
unit_types:
- Ural-375 ZU-23
layout_file: resources/layouts/anti_air/legacy_ground_templates.miz

View File

@ -1,36 +1,33 @@
name: Hawk Site
role: AntiAir
tasks:
- MERAD
groups:
- name: Hawk Site 0
group: 1
unit_count:
- 1
unit_types:
- Hawk sr
- name: Hawk Site 1
group: 1
unit_count:
- 1
unit_types:
- Hawk pcp
- name: Hawk Site 2
group: 1
unit_count:
- 1
unit_types:
- Hawk tr
- name: Hawk Site 3
group: 1
unit_count:
- 6
unit_types:
- Hawk ln
- name: Hawk Site 4
group: 2
unit_count:
- 1
unit_types:
- Vulcan
- Hawk: # Main Battery as one group
- name: Hawk Site 0
unit_count:
- 1
unit_types:
- Hawk sr
- name: Hawk Site 1
unit_count:
- 1
unit_types:
- Hawk pcp
- name: Hawk Site 2
unit_count:
- 1
unit_types:
- Hawk tr
- name: Hawk Site 3
unit_count:
- 6
unit_types:
- Hawk ln
- PD: # Point Defense as separate group
- name: Hawk Site 4
optional: true
unit_count:
- 1
unit_types:
- Vulcan
layout_file: resources/layouts/anti_air/legacy_ground_templates.miz

View File

@ -1,21 +1,21 @@
name: NASAMS AIM-120B
role: AntiAir
tasks:
- MERAD
groups:
- name: NASAMS AIM-120B 1
unit_count:
- 1
unit_types:
- NASAMS_Radar_MPQ64F1
- name: NASAMS AIM-120B 0
unit_count:
- 1
unit_types:
- NASAMS_Command_Post
- name: NASAMS AIM-120B 2
unit_count:
- 4
unit_types:
- NASAMS_LN_B
- NASAMS:
- name: NASAMS AIM-120B 1
unit_count:
- 1
unit_types:
- NASAMS_Radar_MPQ64F1
- name: NASAMS AIM-120B 0
unit_count:
- 1
unit_types:
- NASAMS_Command_Post
- name: NASAMS AIM-120B 2
unit_count:
- 4
unit_types:
- NASAMS_LN_B
layout_file: resources/layouts/anti_air/legacy_ground_templates.miz

View File

@ -1,21 +1,21 @@
name: NASAMS AIM-120C
role: AntiAir
tasks:
- MERAD
groups:
- name: NASAMS AIM-120C 1
unit_count:
- 1
unit_types:
- NASAMS_Radar_MPQ64F1
- name: NASAMS AIM-120C 0
unit_count:
- 1
unit_types:
- NASAMS_Command_Post
- name: NASAMS AIM-120C 2
unit_count:
- 4
unit_types:
- NASAMS_LN_C
- NASAMS:
- name: NASAMS AIM-120C 1
unit_count:
- 1
unit_types:
- NASAMS_Radar_MPQ64F1
- name: NASAMS AIM-120C 0
unit_count:
- 1
unit_types:
- NASAMS_Command_Post
- name: NASAMS AIM-120C 2
unit_count:
- 4
unit_types:
- NASAMS_LN_C
layout_file: resources/layouts/anti_air/legacy_ground_templates.miz

View File

@ -1,56 +1,50 @@
name: Patriot Battery
role: AntiAir
tasks:
- LORAD
groups:
- name: Patriot Battery 0
group: 1
unit_count:
- 1
unit_types:
- Patriot str
- name: Patriot Battery 1
group: 1
unit_count:
- 1
unit_types:
- Patriot AMG
- name: Patriot Battery 2
group: 1
unit_count:
- 1
unit_types:
- Patriot ECS
- name: Patriot Battery 3
group: 1
unit_count:
- 1
unit_types:
- Patriot cp
- name: Patriot Battery 4
group: 1
unit_count:
- 1
unit_types:
- Patriot EPP
- name: Patriot Battery 5
group: 1
unit_count:
- 8
unit_types:
- Patriot ln
- name: Patriot Battery 6
optional: true
group: 2
unit_count:
- 2
unit_classes:
- AAA
- name: Patriot Battery 7
optional: true
group: 2
unit_count:
- 2
unit_classes:
- SHORAD
- Patriot:
- name: Patriot Battery 0
unit_count:
- 1
unit_types:
- Patriot str
- name: Patriot Battery 1
unit_count:
- 1
unit_types:
- Patriot AMG
- name: Patriot Battery 2
unit_count:
- 1
unit_types:
- Patriot ECS
- name: Patriot Battery 3
unit_count:
- 1
unit_types:
- Patriot cp
- name: Patriot Battery 4
unit_count:
- 1
unit_types:
- Patriot EPP
- name: Patriot Battery 5
unit_count:
- 8
unit_types:
- Patriot ln
- AAA:
- name: Patriot Battery 6
optional: true
unit_count:
- 2
unit_classes:
- AAA
- PD:
- name: Patriot Battery 7
optional: true
unit_count:
- 2
unit_classes:
- SHORAD
layout_file: resources/layouts/anti_air/legacy_ground_templates.miz

View File

@ -1,21 +1,21 @@
name: Rapier AA Site
role: AntiAir
tasks:
- SHORAD
groups:
- name: Rapier AA Site 0
unit_count:
- 1
unit_types:
- rapier_fsa_blindfire_radar
- name: Rapier AA Site 1
unit_count:
- 1
unit_types:
- rapier_fsa_optical_tracker_unit
- name: Rapier AA Site 2
unit_count:
- 2
unit_types:
- rapier_fsa_launcher
- Rapier:
- name: Rapier AA Site 0
unit_count:
- 1
unit_types:
- rapier_fsa_blindfire_radar
- name: Rapier AA Site 1
unit_count:
- 1
unit_types:
- rapier_fsa_optical_tracker_unit
- name: Rapier AA Site 2
unit_count:
- 2
unit_types:
- rapier_fsa_launcher
layout_file: resources/layouts/anti_air/legacy_ground_templates.miz

View File

@ -1,21 +1,21 @@
name: Roland Site
role: AntiAir
tasks:
- SHORAD
groups:
- name: Roland Site 0
unit_count:
- 1
unit_types:
- Roland Radar
- name: Roland Site 1
unit_count:
- 2
unit_types:
- Roland ADS
- name: Roland Site 2
unit_count:
- 1
unit_types:
- M 818
- Roland:
- name: Roland Site 0
unit_count:
- 1
unit_types:
- Roland Radar
- name: Roland Site 1
unit_count:
- 2
unit_types:
- Roland ADS
- name: Roland Site 2
unit_count:
- 1
unit_types:
- M 818
layout_file: resources/layouts/anti_air/legacy_ground_templates.miz

View File

@ -1,89 +1,85 @@
name: S-300 Site
role: AntiAir
tasks:
- LORAD
groups:
- name: S-300 Site SR1
group: 1
unit_count:
- 1
unit_types:
- S-300PS 40B6MD sr # SA-10
- S-300PS SA-10B 40B6MD MAST sr # SA-10B
- S-300V 9S15 sr # SA-12
- S-300PMU1 40B6MD sr # SA-20 + B, is the 5N66E
- S-300VM 9S15M2 sr # SA-23
- name: S-300 Site SR2
group: 1
unit_count:
- 1
unit_types:
- S-300PS 64H6E sr # SA-10
- S-300PS 64H6E TRAILER sr # SA-10B
- S-300V 9S19 sr # SA-12
- S-300PMU1 64N6E sr # SA-20 + B
- S-300VM 9S19M2 sr # SA-23
- name: S-300 Site CP
group: 1
unit_count:
- 1
unit_types:
- S-300PS 54K6 cp # SA-10
- S-300PS SA-10B 54K6 cp # SA-10B
- S-300V 9S457 cp # SA-12
- S-300PMU1 54K6 cp # SA-20
- S-300PMU2 54K6E2 cp # SA-20B
- S-300VM 9S457ME cp # SA-23
- name: S-300 Site TR
group: 1
unit_count:
- 1
unit_types:
- S-300PS 40B6M tr # SA-10
- S-300PS 30N6 TRAILER tr # SA-10B
- S-300V 9S32 tr # SA-12
- S-300PMU1 40B6M tr # SA-20, is the 30N6E!
- S-300PMU2 92H6E tr # SA-20B
- S-300VM 9S32ME tr # SA-23
- name: S-300 Site LN1
group: 1
unit_count:
- 3
unit_types:
- S-300PS 5P85C ln # SA-10
- S-300PS 5P85SE_mod ln # SA-10B
- S-300V 9A82 ln # SA-12
- S-300PMU1 5P85CE ln # SA-20
- S-300PMU2 5P85SE2 ln # SA-20B
- S-300VM 9A82ME ln # SA-23
- name: S-300 Site LN2
group: 1
unit_count:
- 3
unit_types:
- S-300PS 5P85D ln # SA-10
- S-300PS 5P85SU_mod ln # SA-10B
- S-300V 9A83 ln # SA-12
- S-300PMU1 5P85DE ln # SA-20
- S-300PMU2 5P85SE2 ln # SA-20B
- S-300VM 9A83ME ln # SA-23
- name: S-300 Site AAA
group: 2
unit_count:
- 2
unit_classes:
- AAA
- name: S-300 Site SHORAD1
group: 3
unit_count:
- 0
- 2
unit_classes:
- SHORAD
- name: S-300 Site SHORAD2
group: 3
unit_count:
- 0
- 2
unit_classes:
- SHORAD
- S-300:
- name: S-300 Site SR1
unit_count:
- 1
unit_types:
- S-300PS 40B6MD sr # SA-10
- S-300PS SA-10B 40B6MD MAST sr # SA-10B
- S-300V 9S15 sr # SA-12
- S-300PMU1 40B6MD sr # SA-20 + B, is the 5N66E
- S-300VM 9S15M2 sr # SA-23
- name: S-300 Site SR2
unit_count:
- 1
unit_types:
- S-300PS 64H6E sr # SA-10
- S-300PS 64H6E TRAILER sr # SA-10B
- S-300V 9S19 sr # SA-12
- S-300PMU1 64N6E sr # SA-20 + B
- S-300VM 9S19M2 sr # SA-23
- name: S-300 Site CP
unit_count:
- 1
unit_types:
- S-300PS 54K6 cp # SA-10
- S-300PS SA-10B 54K6 cp # SA-10B
- S-300V 9S457 cp # SA-12
- S-300PMU1 54K6 cp # SA-20
- S-300PMU2 54K6E2 cp # SA-20B
- S-300VM 9S457ME cp # SA-23
- name: S-300 Site TR
unit_count:
- 1
unit_types:
- S-300PS 40B6M tr # SA-10
- S-300PS 30N6 TRAILER tr # SA-10B
- S-300V 9S32 tr # SA-12
- S-300PMU1 40B6M tr # SA-20, is the 30N6E!
- S-300PMU2 92H6E tr # SA-20B
- S-300VM 9S32ME tr # SA-23
- name: S-300 Site LN1
unit_count:
- 3
unit_types:
- S-300PS 5P85C ln # SA-10
- S-300PS 5P85SE_mod ln # SA-10B
- S-300V 9A82 ln # SA-12
- S-300PMU1 5P85CE ln # SA-20
- S-300PMU2 5P85SE2 ln # SA-20B
- S-300VM 9A82ME ln # SA-23
- name: S-300 Site LN2
unit_count:
- 3
unit_types:
- S-300PS 5P85D ln # SA-10
- S-300PS 5P85SU_mod ln # SA-10B
- S-300V 9A83 ln # SA-12
- S-300PMU1 5P85DE ln # SA-20
- S-300PMU2 5P85SE2 ln # SA-20B
- S-300VM 9A83ME ln # SA-23
- AAA:
- name: S-300 Site AAA
optional: true
unit_count:
- 2
unit_classes:
- AAA
- PD:
- name: S-300 Site SHORAD1
optional: true
unit_count:
- 0
- 2
unit_classes:
- SHORAD
- name: S-300 Site SHORAD2
optional: true
unit_count:
- 0
- 2
unit_classes:
- SHORAD

View File

@ -1,21 +1,21 @@
name: SA-11 Buk Battery
role: AntiAir
tasks:
- MERAD
groups:
- name: SA-11 Buk Battery 0
unit_count:
- 1
unit_types:
- SA-11 Buk SR 9S18M1
- name: SA-11 Buk Battery 1
unit_count:
- 1
unit_types:
- SA-11 Buk CC 9S470M1
- name: SA-11 Buk Battery 2
unit_count:
- 4
unit_types:
- SA-11 Buk LN 9A310M1
- SA-11:
- name: SA-11 Buk Battery 0
unit_count:
- 1
unit_types:
- SA-11 Buk SR 9S18M1
- name: SA-11 Buk Battery 1
unit_count:
- 1
unit_types:
- SA-11 Buk CC 9S470M1
- name: SA-11 Buk Battery 2
unit_count:
- 4
unit_types:
- SA-11 Buk LN 9A310M1
layout_file: resources/layouts/anti_air/legacy_ground_templates.miz

View File

@ -1,21 +1,21 @@
name: SA-17 Grizzly Battery
role: AntiAir
tasks:
- MERAD
groups:
- name: SA-17 Grizzly Battery 0
unit_count:
- 1
unit_types:
- SA-11 Buk SR 9S18M1
- name: SA-17 Grizzly Battery 1
unit_count:
- 1
unit_types:
- SA-11 Buk CC 9S470M1
- name: SA-17 Grizzly Battery 2
unit_count:
- 3
unit_types:
- SA-17 Buk M1-2 LN 9A310M1-2
- SA-17:
- name: SA-17 Grizzly Battery 0
unit_count:
- 1
unit_types:
- SA-11 Buk SR 9S18M1
- name: SA-17 Grizzly Battery 1
unit_count:
- 1
unit_types:
- SA-11 Buk CC 9S470M1
- name: SA-17 Grizzly Battery 2
unit_count:
- 3
unit_types:
- SA-17 Buk M1-2 LN 9A310M1-2
layout_file: resources/layouts/anti_air/legacy_ground_templates.miz

View File

@ -1,21 +1,21 @@
name: SA-2/S-75 Site
role: AntiAir
tasks:
- MERAD
groups:
- name: SA-2/S-75 Site 0
unit_count:
- 1
unit_types:
- p-19 s-125 sr
- name: SA-2/S-75 Site 1
unit_count:
- 1
unit_types:
- SNR_75V
- name: SA-2/S-75 Site 2
unit_count:
- 6
unit_types:
- S_75M_Volhov
- SA-2:
- name: SA-2/S-75 Site 0
unit_count:
- 1
unit_types:
- p-19 s-125 sr
- name: SA-2/S-75 Site 1
unit_count:
- 1
unit_types:
- SNR_75V
- name: SA-2/S-75 Site 2
unit_count:
- 6
unit_types:
- S_75M_Volhov
layout_file: resources/layouts/anti_air/legacy_ground_templates.miz

View File

@ -1,21 +1,21 @@
name: SA-3/S-125 Site
role: AntiAir
tasks:
- MERAD
groups:
- name: SA-3/S-125 Site 0
unit_count:
- 1
unit_types:
- p-19 s-125 sr
- name: SA-3/S-125 Site 1
unit_count:
- 1
unit_types:
- snr s-125 tr
- name: SA-3/S-125 Site 2
unit_count:
- 4
unit_types:
- 5p73 s-125 ln
- SA-3:
- name: SA-3/S-125 Site 0
unit_count:
- 1
unit_types:
- p-19 s-125 sr
- name: SA-3/S-125 Site 1
unit_count:
- 1
unit_types:
- snr s-125 tr
- name: SA-3/S-125 Site 2
unit_count:
- 4
unit_types:
- 5p73 s-125 ln
layout_file: resources/layouts/anti_air/legacy_ground_templates.miz

View File

@ -1,26 +1,26 @@
name: SA-5/S-200 Site
role: AntiAir
tasks:
- LORAD
groups:
- name: SA-5/S-200 Site 0
unit_count:
- 1
unit_types:
- RLS_19J6
- name: SA-5/S-200 Site 1
unit_count:
- 1
unit_types:
- RPC_5N62V
- name: SA-5/S-200 Site 2
unit_count:
- 1
unit_types:
- Ural-375
- name: SA-5/S-200 Site 3
unit_count:
- 6
unit_types:
- S-200_Launcher
- SA-5:
- name: SA-5/S-200 Site 0
unit_count:
- 1
unit_types:
- RLS_19J6
- name: SA-5/S-200 Site 1
unit_count:
- 1
unit_types:
- RPC_5N62V
- name: SA-5/S-200 Site 2
unit_count:
- 1
unit_types:
- Ural-375
- name: SA-5/S-200 Site 3
unit_count:
- 6
unit_types:
- S-200_Launcher
layout_file: resources/layouts/anti_air/legacy_ground_templates.miz

View File

@ -1,16 +1,16 @@
name: SA-6 Kub Site
role: AntiAir
tasks:
- MERAD
groups:
- name: SA-6 Kub Site 0
unit_count:
- 1
unit_types:
- Kub 1S91 str
- name: SA-6 Kub Site 1
unit_count:
- 4
unit_types:
- Kub 2P25 ln
- SA-6:
- name: SA-6 Kub Site 0
unit_count:
- 1
unit_types:
- Kub 1S91 str
- name: SA-6 Kub Site 1
unit_count:
- 4
unit_types:
- Kub 2P25 ln
layout_file: resources/layouts/anti_air/legacy_ground_templates.miz

View File

@ -1,19 +1,19 @@
name: Short Range Anti Air Group
role: AntiAir
generic: true
tasks:
- SHORAD
groups:
- name: SHORAD Group 0
unit_count:
- 2
unit_classes:
- SHORAD
- name: SHORAD Group 1
optional: true
unit_count:
- 1
- 2
unit_classes:
- Logistics
- SHORAD:
- name: SHORAD Group 0
unit_count:
- 2
unit_classes:
- SHORAD
- name: SHORAD Group 1
optional: true
unit_count:
- 1
- 2
unit_classes:
- Logistics
layout_file: resources/layouts/anti_air/shorad.miz

View File

@ -1,41 +1,41 @@
name: WW2 Ally Flak Site
role: AntiAir
tasks:
- AAA
groups:
- name: WW2 Ally Flak Site 0
unit_count:
- 4
unit_types:
- QF_37_AA
- name: WW2 Ally Flak Site 1
unit_count:
- 8
unit_types:
- M1_37mm
- name: WW2 Ally Flak Site 2
unit_count:
- 8
unit_types:
- M45_Quadmount
- name: WW2 Ally Flak Site 3
unit_count:
- 1
unit_types:
- Willys_MB
- name: WW2 Ally Flak Site 4
unit_count:
- 1
unit_types:
- M30_CC
- name: WW2 Ally Flak Site 5
unit_count:
- 1
unit_types:
- M4_Tractor
- name: WW2 Ally Flak Site 6
unit_count:
- 1
unit_types:
- Bedford_MWD
- Flak:
- name: WW2 Ally Flak Site 0
unit_count:
- 4
unit_types:
- QF_37_AA
- name: WW2 Ally Flak Site 1
unit_count:
- 8
unit_types:
- M1_37mm
- name: WW2 Ally Flak Site 2
unit_count:
- 8
unit_types:
- M45_Quadmount
- name: WW2 Ally Flak Site 3
unit_count:
- 1
unit_types:
- Willys_MB
- name: WW2 Ally Flak Site 4
unit_count:
- 1
unit_types:
- M30_CC
- name: WW2 Ally Flak Site 5
unit_count:
- 1
unit_types:
- M4_Tractor
- name: WW2 Ally Flak Site 6
unit_count:
- 1
unit_types:
- Bedford_MWD
layout_file: resources/layouts/anti_air/legacy_ground_templates.miz

View File

@ -1,16 +1,16 @@
name: WW2 Flak Site
role: AntiAir
tasks:
- AAA
groups:
- name: WW2 Flak Site 0
unit_count:
- 6
unit_types:
- flak18
- name: WW2 Flak Site 1
unit_count:
- 1
unit_types:
- Blitz_36-6700A
- Flak:
- name: WW2 Flak Site 0
unit_count:
- 6
unit_types:
- flak18
- name: WW2 Flak Site 1
unit_count:
- 1
unit_types:
- Blitz_36-6700A
layout_file: resources/layouts/anti_air/legacy_ground_templates.miz

View File

@ -1,84 +1,84 @@
name: allycamp1
generic: true
role: Building
tasks:
- StrikeTarget
- AllyCamp
groups:
- name: allycamp1 0
statics:
- allycamp1 0-0
- allycamp1 0-1
- allycamp1 0-2
- allycamp1 0-3
unit_count:
- 4
unit_types:
- FARP Tent
- name: allycamp1 1
statics:
- allycamp1 1-0
- allycamp1 1-1
- allycamp1 1-2
- allycamp1 1-3
- allycamp1 1-4
- allycamp1 1-5
- allycamp1 1-6
- allycamp1 1-7
- allycamp1 1-8
- allycamp1 1-9
- allycamp1 1-10
- allycamp1 1-11
- allycamp1 1-12
- allycamp1 1-13
- allycamp1 1-14
- allycamp1 1-15
- allycamp1 1-16
- allycamp1 1-17
- allycamp1 1-18
- allycamp1 1-19
- allycamp1 1-20
- allycamp1 1-21
unit_count:
- 22
unit_types:
- Haystack 4
- name: allycamp1 2
statics:
- allycamp1 2-0
- allycamp1 2-1
- allycamp1 2-2
- allycamp1 2-3
- allycamp1 2-4
- allycamp1 2-5
- allycamp1 2-6
- allycamp1 2-7
- allycamp1 2-8
- allycamp1 2-9
- allycamp1 2-10
- allycamp1 2-11
unit_count:
- 12
unit_types:
- Haystack 3
- name: allycamp1 3
statics:
- allycamp1 3-0
- allycamp1 3-1
- allycamp1 3-2
- allycamp1 3-3
- allycamp1 3-4
- allycamp1 3-5
- allycamp1 3-6
- allycamp1 3-7
unit_count:
- 8
unit_types:
- Concertina wire
- name: allycamp1 4
group: 2 # Vehicle and static can not be mixed
unit_count:
- 4
unit_types:
- house2arm
- AllyCamp:
- name: allycamp1 0
statics:
- allycamp1 0-0
- allycamp1 0-1
- allycamp1 0-2
- allycamp1 0-3
unit_count:
- 4
unit_types:
- FARP Tent
- name: allycamp1 1
statics:
- allycamp1 1-0
- allycamp1 1-1
- allycamp1 1-2
- allycamp1 1-3
- allycamp1 1-4
- allycamp1 1-5
- allycamp1 1-6
- allycamp1 1-7
- allycamp1 1-8
- allycamp1 1-9
- allycamp1 1-10
- allycamp1 1-11
- allycamp1 1-12
- allycamp1 1-13
- allycamp1 1-14
- allycamp1 1-15
- allycamp1 1-16
- allycamp1 1-17
- allycamp1 1-18
- allycamp1 1-19
- allycamp1 1-20
- allycamp1 1-21
unit_count:
- 22
unit_types:
- Haystack 4
- name: allycamp1 2
statics:
- allycamp1 2-0
- allycamp1 2-1
- allycamp1 2-2
- allycamp1 2-3
- allycamp1 2-4
- allycamp1 2-5
- allycamp1 2-6
- allycamp1 2-7
- allycamp1 2-8
- allycamp1 2-9
- allycamp1 2-10
- allycamp1 2-11
unit_count:
- 12
unit_types:
- Haystack 3
- name: allycamp1 3
statics:
- allycamp1 3-0
- allycamp1 3-1
- allycamp1 3-2
- allycamp1 3-3
- allycamp1 3-4
- allycamp1 3-5
- allycamp1 3-6
- allycamp1 3-7
unit_count:
- 8
unit_types:
- Concertina wire
- AllyCamp2: # Vehicle and static can not be mixed
- name: allycamp1 4
unit_count:
- 4
unit_types:
- house2arm
layout_file: resources/layouts/buildings/buildings.miz

View File

@ -1,22 +1,22 @@
name: ammo1
generic: true
role: Building
tasks:
- Ammo
groups:
- name: ammo1 0
statics:
- ammo1 0-0
unit_count:
- 1
unit_types:
- .Ammunition depot
- name: ammo1 1
statics:
- ammo1 1-0
- ammo1 1-1
unit_count:
- 2
unit_types:
- Hangar B
- Ammo:
- name: ammo1 0
statics:
- ammo1 0-0
unit_count:
- 1
unit_types:
- .Ammunition depot
- name: ammo1 1
statics:
- ammo1 1-0
- ammo1 1-1
unit_count:
- 2
unit_types:
- Hangar B
layout_file: resources/layouts/buildings/buildings.miz

View File

@ -1,16 +1,16 @@
name: comms
generic: true
role: Building
tasks:
- StrikeTarget
- Comms
groups:
- name: comms1 0
statics:
- comms1 0-0
unit_count:
- 1
unit_types:
- TV tower
- Comms tower M
- Comms:
- name: comms1 0
statics:
- comms1 0-0
unit_count:
- 1
unit_types:
- TV tower
- Comms tower M
layout_file: resources/layouts/buildings/buildings.miz

View File

@ -1,31 +1,31 @@
name: derrick1
generic: true
role: Building
tasks:
- StrikeTarget
- Derrick
groups:
- name: derrick1 0
statics:
- derrick1 0-0
- derrick1 0-1
unit_count:
- 2
unit_types:
- Oil derrick
- name: derrick1 1
statics:
- derrick1 1-0
- derrick1 1-1
unit_count:
- 2
unit_types:
- Pump station
- name: derrick1 2
statics:
- derrick1 2-0
unit_count:
- 1
unit_types:
- Subsidiary structure 2
- Derrick:
- name: derrick1 0
statics:
- derrick1 0-0
- derrick1 0-1
unit_count:
- 2
unit_types:
- Oil derrick
- name: derrick1 1
statics:
- derrick1 1-0
- derrick1 1-1
unit_count:
- 2
unit_types:
- Pump station
- name: derrick1 2
statics:
- derrick1 2-0
unit_count:
- 1
unit_types:
- Subsidiary structure 2
layout_file: resources/layouts/buildings/buildings.miz

View File

@ -1,23 +1,23 @@
name: factory1
generic: true
role: Building
tasks:
- Factory
groups:
- name: factory1 0
statics:
- factory1 0-0
unit_count:
- 1
unit_types:
- Tech combine
- name: factory1 1
statics:
- factory1 1-0
- factory1 1-1
- factory1 1-2
unit_count:
- 3
unit_types:
- Tech hangar A
- Factory:
- name: factory1 0
statics:
- factory1 0-0
unit_count:
- 1
unit_types:
- Tech combine
- name: factory1 1
statics:
- factory1 1-0
- factory1 1-1
- factory1 1-2
unit_count:
- 3
unit_types:
- Tech hangar A
layout_file: resources/layouts/buildings/buildings.miz

View File

@ -1,41 +1,41 @@
name: farp1
generic: true
role: Building
tasks:
- StrikeTarget
- Farp
groups:
- name: farp1 0
statics:
- farp1 0-0
- farp1 0-1
- farp1 0-2
- farp1 0-3
- farp1 0-4
unit_count:
- 5
unit_types:
- FARP Tent
- name: farp1 1
statics:
- farp1 1-0
unit_count:
- 1
unit_types:
- FARP Ammo Dump Coating
- name: farp1 2
statics:
- farp1 2-0
unit_count:
- 1
unit_types:
- FARP CP Blindage
- name: farp1 3
statics:
- farp1 3-0
- farp1 3-1
unit_count:
- 2
unit_types:
- FARP Fuel Depot
- Farp:
- name: farp1 0
statics:
- farp1 0-0
- farp1 0-1
- farp1 0-2
- farp1 0-3
- farp1 0-4
unit_count:
- 5
unit_types:
- FARP Tent
- name: farp1 1
statics:
- farp1 1-0
unit_count:
- 1
unit_types:
- FARP Ammo Dump Coating
- name: farp1 2
statics:
- farp1 2-0
unit_count:
- 1
unit_types:
- FARP CP Blindage
- name: farp1 3
statics:
- farp1 3-0
- farp1 3-1
unit_count:
- 2
unit_types:
- FARP Fuel Depot
layout_file: resources/layouts/buildings/buildings.miz

View File

@ -1,31 +1,31 @@
name: fob1
generic: true
role: Building
tasks:
- FOB
groups:
- name: fob1 0
statics:
- fob1 0-0
unit_count:
- 1
unit_types:
- .Command Center
- name: fob1 1
statics:
- fob1 1-0
- fob1 1-1
- fob1 1-2
unit_count:
- 3
unit_types:
- Barracks 2
- name: fob1 2
statics:
- fob1 2-0
- fob1 2-1
unit_count:
- 2
unit_types:
- Garage small B
- FOB:
- name: fob1 0
statics:
- fob1 0-0
unit_count:
- 1
unit_types:
- .Command Center
- name: fob1 1
statics:
- fob1 1-0
- fob1 1-1
- fob1 1-2
unit_count:
- 3
unit_types:
- Barracks 2
- name: fob1 2
statics:
- fob1 2-0
- fob1 2-1
unit_count:
- 2
unit_types:
- Garage small B
layout_file: resources/layouts/buildings/buildings.miz

View File

@ -1,28 +1,28 @@
name: fuel1
generic: true
role: Building
tasks:
- StrikeTarget
- Fuel
groups:
- name: fuel1 0
statics:
- fuel1 0-0
- fuel1 0-1
- fuel1 0-2
- fuel1 0-3
- fuel1 0-4
- fuel1 0-5
unit_count:
- 6
unit_types:
- Tank
- name: fuel1 1
statics:
- fuel1 1-0
- fuel1 1-1
unit_count:
- 2
unit_types:
- Tank 3
- Fuel:
- name: fuel1 0
statics:
- fuel1 0-0
- fuel1 0-1
- fuel1 0-2
- fuel1 0-3
- fuel1 0-4
- fuel1 0-5
unit_count:
- 6
unit_types:
- Tank
- name: fuel1 1
statics:
- fuel1 1-0
- fuel1 1-1
unit_count:
- 2
unit_types:
- Tank 3
layout_file: resources/layouts/buildings/buildings.miz

View File

@ -1,18 +1,18 @@
name: oil1
generic: true
role: Building
tasks:
- OffShoreStrikeTarget
- Oil
groups:
- name: oil1 0
statics:
- oil1 0-0
- oil1 0-1
- oil1 0-2
- oil1 0-3
unit_count:
- 4
unit_types:
- Oil platform
- Oil:
- name: oil1 0
statics:
- oil1 0-0
- oil1 0-1
- oil1 0-2
- oil1 0-3
unit_count:
- 4
unit_types:
- Oil platform
layout_file: resources/layouts/buildings/buildings.miz

View File

@ -1,37 +1,37 @@
name: power1
generic: true
role: Building
tasks:
- StrikeTarget
- Power
groups:
- name: power1 0
statics:
- power1 0-0
unit_count:
- 1
unit_types:
- Repair workshop
- name: power1 1
statics:
- power1 1-0
unit_count:
- 1
unit_types:
- Workshop A
- name: power1 2
statics:
- power1 2-0
- power1 2-1
unit_count:
- 2
unit_types:
- Garage B
- name: power1 3
statics:
- power1 3-0
unit_count:
- 1
unit_types:
- Farm B
- Power:
- name: power1 0
statics:
- power1 0-0
unit_count:
- 1
unit_types:
- Repair workshop
- name: power1 1
statics:
- power1 1-0
unit_count:
- 1
unit_types:
- Workshop A
- name: power1 2
statics:
- power1 2-0
- power1 2-1
unit_count:
- 2
unit_types:
- Garage B
- name: power1 3
statics:
- power1 3-0
unit_count:
- 1
unit_types:
- Farm B
layout_file: resources/layouts/buildings/buildings.miz

View File

@ -1,39 +1,39 @@
name: village1
generic: true
role: Building
tasks:
- StrikeTarget
- Village
groups:
- name: village1 0
statics:
- village1 0-0
- village1 0-1
unit_count:
- 2
unit_types:
- Small house 1A
- name: village1 1
statics:
- village1 1-0
- village1 1-1
- village1 1-2
- village1 1-3
- village1 1-4
- village1 1-5
- village1 1-6
- village1 1-7
unit_count:
- 8
unit_types:
- Small werehouse 1
- name: village1 2
statics:
- village1 2-0
- village1 2-1
- village1 2-2
unit_count:
- 3
unit_types:
- Small house 1B
- Village:
- name: village1 0
statics:
- village1 0-0
- village1 0-1
unit_count:
- 2
unit_types:
- Small house 1A
- name: village1 1
statics:
- village1 1-0
- village1 1-1
- village1 1-2
- village1 1-3
- village1 1-4
- village1 1-5
- village1 1-6
- village1 1-7
unit_count:
- 8
unit_types:
- Small werehouse 1
- name: village1 2
statics:
- village1 2-0
- village1 2-1
- village1 2-2
unit_count:
- 3
unit_types:
- Small house 1B
layout_file: resources/layouts/buildings/buildings.miz

View File

@ -1,24 +1,24 @@
name: ware1
generic: true
role: Building
tasks:
- StrikeTarget
- Ware
groups:
- name: ware1 0
statics:
- ware1 0-0
unit_count:
- 1
unit_types:
- Warehouse
- name: ware1 1
statics:
- ware1 1-0
- ware1 1-1
- ware1 1-2
unit_count:
- 3
unit_types:
- Hangar A
- Ware:
- name: ware1 0
statics:
- ware1 0-0
unit_count:
- 1
unit_types:
- Warehouse
- name: ware1 1
statics:
- ware1 1-0
- ware1 1-1
- ware1 1-2
unit_count:
- 3
unit_types:
- Hangar A
layout_file: resources/layouts/buildings/buildings.miz

View File

@ -1,34 +1,34 @@
name: ww2bunker1
generic: true
role: Building
tasks:
- StrikeTarget
- WW2Bunker
groups:
- name: ww2bunker1 0
statics:
- ww2bunker1 0-0
- ww2bunker1 0-1
- ww2bunker1 0-2
- ww2bunker1 0-3
unit_count:
- 4
unit_types:
- Siegfried Line
- name: ww2bunker1 1
statics:
- ww2bunker1 1-0
- ww2bunker1 1-1
- ww2bunker1 1-2
- ww2bunker1 1-3
unit_count:
- 4
unit_types:
- Fire Control Bunker
- name: ww2bunker1 2
group: 2 # Vehicle and static can not be mixed
unit_count:
- 4
unit_types:
- SK_C_28_naval_gun
- Bunker:
- name: ww2bunker1 0
statics:
- ww2bunker1 0-0
- ww2bunker1 0-1
- ww2bunker1 0-2
- ww2bunker1 0-3
unit_count:
- 4
unit_types:
- Siegfried Line
- name: ww2bunker1 1
statics:
- ww2bunker1 1-0
- ww2bunker1 1-1
- ww2bunker1 1-2
- ww2bunker1 1-3
unit_count:
- 4
unit_types:
- Fire Control Bunker
- Bunker2: # Vehicle and static can not be mixed
- name: ww2bunker1 2
unit_count:
- 4
unit_types:
- SK_C_28_naval_gun
layout_file: resources/layouts/buildings/buildings.miz

View File

@ -1,57 +1,57 @@
name: ww2bunker2
generic: true
role: Building
tasks:
- StrikeTarget
- WW2Bunker
groups:
- name: ww2bunker2 0
statics:
- ww2bunker2 0-0
unit_count:
- 1
unit_types:
- Fire Control Bunker
- name: ww2bunker2 1
statics:
- ww2bunker2 1-0
- ww2bunker2 1-1
unit_count:
- 2
unit_types:
- Siegfried Line
- name: ww2bunker2 2
statics:
- ww2bunker2 2-0
- ww2bunker2 2-1
- ww2bunker2 2-2
- ww2bunker2 2-3
- ww2bunker2 2-4
- ww2bunker2 2-5
- ww2bunker2 2-6
- ww2bunker2 2-7
unit_count:
- 8
unit_types:
- Concertina wire
- name: ww2bunker2 3
statics:
- ww2bunker2 3-0
unit_count:
- 1
unit_types:
- Belgian gate
- name: ww2bunker2 4
statics:
- ww2bunker2 4-0
- ww2bunker2 4-1
- ww2bunker2 4-2
- ww2bunker2 4-3
- ww2bunker2 4-4
- ww2bunker2 4-5
- ww2bunker2 4-6
unit_count:
- 7
unit_types:
- Czech hedgehogs 1
- Bunker:
- name: ww2bunker2 0
statics:
- ww2bunker2 0-0
unit_count:
- 1
unit_types:
- Fire Control Bunker
- name: ww2bunker2 1
statics:
- ww2bunker2 1-0
- ww2bunker2 1-1
unit_count:
- 2
unit_types:
- Siegfried Line
- name: ww2bunker2 2
statics:
- ww2bunker2 2-0
- ww2bunker2 2-1
- ww2bunker2 2-2
- ww2bunker2 2-3
- ww2bunker2 2-4
- ww2bunker2 2-5
- ww2bunker2 2-6
- ww2bunker2 2-7
unit_count:
- 8
unit_types:
- Concertina wire
- name: ww2bunker2 3
statics:
- ww2bunker2 3-0
unit_count:
- 1
unit_types:
- Belgian gate
- name: ww2bunker2 4
statics:
- ww2bunker2 4-0
- ww2bunker2 4-1
- ww2bunker2 4-2
- ww2bunker2 4-3
- ww2bunker2 4-4
- ww2bunker2 4-5
- ww2bunker2 4-6
unit_count:
- 7
unit_types:
- Czech hedgehogs 1
layout_file: resources/layouts/buildings/buildings.miz

View File

@ -1,34 +1,34 @@
name: Silkworm
role: Defenses
tasks:
- Coastal
groups:
- name: SilkwormGenerator 0
unit_count:
- 1
unit_classes:
- SearchRadar
- name: SilkwormGenerator 1
unit_count:
- 5
unit_classes:
- Missile
- name: SilkwormGenerator 2
optional: true
unit_count:
- 1
unit_classes:
- Logistics
- name: SilkwormGenerator 3
optional: true
unit_count:
- 1
unit_classes:
- AAA
- name: SilkwormGenerator 4
optional: true
unit_count:
- 1
unit_classes:
- SHORAD
- Silkworm:
- name: SilkwormGenerator 0
unit_count:
- 1
unit_classes:
- SearchRadar
- name: SilkwormGenerator 1
unit_count:
- 5
unit_classes:
- Missile
- name: SilkwormGenerator 2
optional: true
unit_count:
- 1
unit_classes:
- Logistics
- name: SilkwormGenerator 3
optional: true
unit_count:
- 1
unit_classes:
- AAA
- name: SilkwormGenerator 4
optional: true
unit_count:
- 1
unit_classes:
- SHORAD
layout_file: resources/layouts/defenses/defenses.miz

View File

@ -1,29 +1,29 @@
name: Missile
role: Defenses
generic: true
tasks:
- Missile
groups:
- name: ScudGenerator 0
unit_count:
- 3
unit_classes:
- Missile
- name: ScudGenerator 1
unit_count:
- 1
unit_classes:
- Logistics
- name: ScudGenerator 2
optional: true
unit_count:
- 1
unit_classes:
- AAA
- name: ScudGenerator 3
optional: true
unit_count:
- 1
unit_classes:
- SHORAD
- Missile:
- name: ScudGenerator 0
unit_count:
- 3
unit_classes:
- Missile
- name: ScudGenerator 1
unit_count:
- 1
unit_classes:
- Logistics
- name: ScudGenerator 2
optional: true
unit_count:
- 1
unit_classes:
- AAA
- name: ScudGenerator 3
optional: true
unit_count:
- 1
unit_classes:
- SHORAD
layout_file: resources/layouts/defenses/defenses.miz

View File

@ -1,17 +1,17 @@
name: Armor Group
role: GroundForce
generic: true
tasks:
- BaseDefense
- FrontLine
groups:
- name: Armor Group 0
unit_count:
- 2
- 6
unit_classes:
- APC
- ATGM
- IFV
- Tank
- Armor Group:
- name: Armor Group 0
unit_count:
- 2
- 6
unit_classes:
- APC
- ATGM
- IFV
- Tank
layout_file: resources/layouts/ground_forces/ground_forces.miz

View File

@ -1,26 +1,26 @@
name: Armor Group with Anti-Air
role: GroundForce
generic: true
tasks:
- BaseDefense
- FrontLine
groups:
- name: Armor Group with Anti-Air 0
unit_count:
- 2
- 6
unit_classes:
- APC
- ATGM
- IFV
- Tank
- name: Armor Group with Anti-Air 1
optional: true
unit_count:
- 1
- 2
unit_classes:
- AAA
- SHORAD
- Manpad
- Armor Group:
- name: Armor Group with Anti-Air 0
unit_count:
- 2
- 6
unit_classes:
- APC
- ATGM
- IFV
- Tank
- name: Armor Group with Anti-Air 1
optional: true
unit_count:
- 1
- 2
unit_classes:
- AAA
- SHORAD
- Manpad
layout_file: resources/layouts/ground_forces/ground_forces.miz

View File

@ -1,19 +1,18 @@
name: Carrier Group
role: Naval
generic: true
tasks:
- AircraftCarrier
groups:
- name: Carrier Group 0
group: 1
unit_count:
- 1
unit_classes:
- AircraftCarrier
- name: Carrier Group 1
group: 2
unit_count:
- 4
unit_classes:
- Destroyer
- Carrier:
- name: Carrier Group 0
unit_count:
- 1
unit_classes:
- AircraftCarrier
- Escort:
- name: Carrier Group 1
unit_count:
- 4
unit_classes:
- Destroyer
layout_file: resources/layouts/naval/legacy_naval_templates.miz

View File

@ -1,25 +1,23 @@
name: Carrier Strike Group 8
role: Naval
generic: true
tasks:
- AircraftCarrier
groups:
- name: Carrier Strike Group 8 0
group: 1
unit_count:
- 1
unit_types:
- Stennis
- name: Carrier Strike Group 8 1
group: 2
unit_count:
- 5
unit_types:
- USS_Arleigh_Burke_IIa
- name: Carrier Strike Group 8 2
group: 2
unit_count:
- 2
unit_types:
- TICONDEROG
- Carrier:
- name: Carrier Strike Group 8 0
unit_count:
- 1
unit_types:
- Stennis
- Escort:
- name: Carrier Strike Group 8 1
unit_count:
- 4
unit_types:
- USS_Arleigh_Burke_IIa
- name: Carrier Strike Group 8 2
unit_count:
- 1
unit_types:
- TICONDEROG
layout_file: resources/layouts/naval/legacy_naval_templates.miz

View File

@ -1,19 +1,18 @@
name: LHA Group
generic: true
role: Naval
tasks:
- HelicopterCarrier
groups:
- name: LHA Group 0
group: 1
unit_count:
- 1
unit_classes:
- HelicopterCarrier
- name: LHA Group 1
group: 2
unit_count:
- 2
unit_classes:
- Destroyer
- LHA:
- name: LHA Group 0
unit_count:
- 1
unit_classes:
- HelicopterCarrier
- Escort:
- name: LHA Group 1
unit_count:
- 2
unit_classes:
- Destroyer
layout_file: resources/layouts/naval/legacy_naval_templates.miz

View File

@ -1,23 +1,23 @@
name: Naval Group
role: Naval
tasks:
- Navy
groups:
- name: Naval Group 0
unit_count:
- 2
unit_classes:
- Frigate
- name: Naval Group 1
unit_count:
- 2
unit_classes:
- Destroyer
- name: Naval Group 2
optional: true
unit_count:
- 0
- 1
unit_classes:
- Cruiser
- Naval Group:
- name: Naval Group 0
unit_count:
- 2
unit_classes:
- Frigate
- name: Naval Group 1
unit_count:
- 2
unit_classes:
- Destroyer
- name: Naval Group 2
optional: true
unit_count:
- 0
- 1
unit_classes:
- Cruiser
layout_file: resources/layouts/naval/naval.miz

View File

@ -1,17 +1,17 @@
name: Naval Two Ship
role: Naval
generic: true
tasks:
- Navy
groups:
- name: Naval Two Ship
unit_count:
- 2
unit_classes:
- Frigate
- Destroyer
- Cruiser
- Boat
- Submarine
- LandingShip
- Naval Two Ship:
- name: Naval Two Ship
unit_count:
- 2
unit_classes:
- Frigate
- Destroyer
- Cruiser
- Boat
- Submarine
- LandingShip
layout_file: resources/layouts/naval/naval.miz

View File

@ -3,14 +3,15 @@ role: Naval
tasks:
- Navy
groups:
- name: WW2 LST Group 0
unit_count:
- 1
unit_types:
- USS_Samuel_Chase
- name: WW2 LST Group 1
unit_count:
- 3
unit_types:
- LST_Mk2
- LST:
- name: WW2 LST Group 0
unit_count:
- 1
unit_types:
- USS_Samuel_Chase
- name: WW2 LST Group 1
unit_count:
- 3
unit_types:
- LST_Mk2
layout_file: resources/layouts/naval/legacy_naval_templates.miz