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). 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.
This will change underlying parts of the code base which will allow major improvements to the Ground Warfare in upcoming features.
**Armed Forces**\ **Armed Forces**\
TODO Describe the introduction of ArmedForces which are similar to the AirWing and Squadrons. 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 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 Layout System**\ **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. 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.
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. 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.
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.
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. 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) ![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: 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.
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.
- GroundWar (Frontline) currently does **not** use the template system TODO Describe the optional flag.
- 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 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: 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) ![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. A layout consists of two special files:
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)
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) ![template_miz_example.png](layout_miz_example.png)
### The template yaml ### The Layout configuration file
TODO Description about the layout yaml file.\
Possible Information: Possible Information:
| Property | Type | Required | Description | Example | | Property | Type | Required | Description | Example |
|---------------|-----------------------|----------|----------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------| |---------------|-----------------------|----------|----------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------|
| Name | (str) | Yes | A name to identify the template | `name: Armor Group` | | 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` | | 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 | | | 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 | | | 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 | | | 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 | | Property | Type | Required | Description | Example |
|--------------|------------------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------| |--------------|------------------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
| name | (str) | Yes | The group name used in the .miz. Must match exactly! | | | 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 | | | 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_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_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) | | | 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) | | | 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 name: Carrier Group
description: A standard AAA template
generic: true generic: true
role: AntiAir
tasks: tasks:
- AAA - AircraftCarrier
groups: groups:
- name: AAA Site 0 - Carrier: # Group Name of the DCS Group
group: 1 - name: Carrier Group 0 # Sub Group used in the layout.miz
unit_count: unit_count:
- 2 - 1
- 6 unit_classes:
unit_classes: - AircraftCarrier
- AAA - Escort: # Group name of the 2nd Group
- name: AAA Site 1 - name: Carrier Group 1
optional: true unit_count:
group: 1 - 4
unit_count: unit_classes:
- 1 - Destroyer
- 2 layout_file: resources/layouts/naval/legacy_naval_templates.miz
unit_classes:
- Logistics
template_file: resources/templates/anti_air/AAA.miz
``` ```
### Roles, Tasks and Classes ### 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.
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
## Migration from Generators ## 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). 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. 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. 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: What was changed:
- Removed the `ewrs` list. All EWRs are now defined in the list "air_defense_units". - 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 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. - 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) - `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 - 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) - 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. - `aircraft_carrier` and `helicopter_carrier` were moved to `naval_units` as well.
## Unit Groups ## Preset Groups
TODO Explain more 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.
- 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
Example: Example:
``` ```
name: SA-10/S-300PS # The name which will be used in the faction file name: SA-10/S-300PS # The name of the group
role: AntiAir # The role of the Group tasks: # Define at least 1 task
tasks: - LORAD # The task(s) the Group can fulfill
- LORAD # The task the Group can fulfill units: # Define at least 1 unit
ground_units:
- SAM SA-10 S-300 "Grumble" Clam Shell SR - SAM SA-10 S-300 "Grumble" Clam Shell SR
- SAM SA-10 S-300 "Grumble" Big Bird SR - SAM SA-10 S-300 "Grumble" Big Bird SR
- SAM SA-10 S-300 "Grumble" C2 - SAM SA-10 S-300 "Grumble" C2
- SAM SA-10 S-300 "Grumble" Flap Lid TR - SAM SA-10 S-300 "Grumble" Flap Lid TR
- SAM SA-10 S-300 "Grumble" TEL D - SAM SA-10 S-300 "Grumble" TEL D
- SAM SA-10 S-300 "Grumble" TEL C - SAM SA-10 S-300 "Grumble" TEL C
ship_units: statics: # Optional
- # Add some naval units here
statics:
- # Add some statics here - # Add some statics here
templates: layouts: # Define at least one layout
- S-300 Site # The template names which should be used by this group - S-300 Site # prefered layouts for these groups
``` ```
A list of all available units is accessible here: [/resources/units/unit_groups](/resources/units/groups) 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)
### Optional Tasks which can be done later - Units are defined with the variant name found in [/resources/units](/resources/units)
- [ ] 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

View File

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

View File

@ -9,18 +9,20 @@ from typing import ClassVar, TYPE_CHECKING, Type, Any, Iterator, Optional
import yaml import yaml
from dcs import Point from dcs import Point
from game import db from game.data.groups import GroupTask
from game.data.groups import GroupRole, GroupTask
from game.data.radar_db import UNITS_WITH_RADAR from game.data.radar_db import UNITS_WITH_RADAR
from game.dcs.groundunittype import GroundUnitType from game.dcs.groundunittype import GroundUnitType
from game.dcs.helpers import static_type_from_name
from game.dcs.shipunittype import ShipUnitType from game.dcs.shipunittype import ShipUnitType
from game.dcs.unittype import UnitType from game.dcs.unittype import UnitType
from game.point_with_heading import PointWithHeading 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 dcs.unittype import UnitType as DcsUnitType, VehicleType, ShipType, StaticType
from game.theater.theatergroup import TheaterGroup from game.theater.theatergroup import TheaterGroup
from game.layout import LAYOUTS
if TYPE_CHECKING: if TYPE_CHECKING:
from game import Game from game import Game
from game.factions.faction import Faction from game.factions.faction import Faction
@ -29,25 +31,40 @@ if TYPE_CHECKING:
@dataclass @dataclass
class ForceGroup: 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 name: str
units: list[UnitType[Any]] units: list[UnitType[Any]]
statics: list[Type[DcsUnitType]] statics: list[Type[DcsUnitType]]
role: GroupRole
tasks: list[GroupTask] = field(default_factory=list) 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_name: ClassVar[dict[str, ForceGroup]] = {}
_by_role: ClassVar[dict[GroupRole, list[ForceGroup]]] = {}
_loaded: bool = False _loaded: bool = False
@staticmethod @staticmethod
def for_layout(layout: TheaterLayout, faction: Faction) -> ForceGroup: def for_layout(layout: TgoLayout, faction: Faction) -> ForceGroup:
"""TODO Documentation""" """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() units: set[UnitType[Any]] = set()
statics: set[Type[DcsUnitType]] = 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): for unit_type in group.possible_types_for_faction(faction):
if issubclass(unit_type, VehicleType): if issubclass(unit_type, VehicleType):
units.add(next(GroundUnitType.for_dcs_type(unit_type))) units.add(next(GroundUnitType.for_dcs_type(unit_type)))
@ -57,10 +74,9 @@ class ForceGroup:
statics.add(unit_type) statics.add(unit_type)
return ForceGroup( 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(units),
list(statics), list(statics),
layout.role,
layout.tasks, layout.tasks,
[layout], [layout],
) )
@ -80,33 +96,38 @@ class ForceGroup:
or type in self.statics or type in self.statics
) )
def dcs_unit_types_for_group(self, group: GroupLayout) -> list[Type[DcsUnitType]]: def dcs_unit_types_for_group(
"""TODO Description""" 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)] unit_types = [t for t in group.unit_types if self.has_access_to_dcs_type(t)]
alternative_types = [] alternative_types = []
for accessible_unit in self.units: for accessible_unit in self.units:
if accessible_unit.unit_class in group.unit_classes: if accessible_unit.unit_class in group.unit_classes:
unit_types.append(accessible_unit.dcs_unit_type) 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) alternative_types.append(accessible_unit.dcs_unit_type)
return unit_types or alternative_types 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): for dcs_type in self.dcs_unit_types_for_group(group):
if issubclass(dcs_type, VehicleType): if issubclass(dcs_type, VehicleType):
yield next(GroundUnitType.for_dcs_type(dcs_type)) yield next(GroundUnitType.for_dcs_type(dcs_type))
elif issubclass(dcs_type, ShipType): elif issubclass(dcs_type, ShipType):
yield next(ShipUnitType.for_dcs_type(dcs_type)) 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): for dcs_type in self.dcs_unit_types_for_group(group):
if issubclass(dcs_type, StaticType): if issubclass(dcs_type, StaticType):
yield dcs_type yield dcs_type
def random_dcs_unit_type_for_group(self, group: GroupLayout) -> Type[DcsUnitType]: def random_dcs_unit_type_for_group(
"""TODO Description""" 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)) return random.choice(self.dcs_unit_types_for_group(group))
def update_group(self, new_group: ForceGroup) -> None: def update_group(self, new_group: ForceGroup) -> None:
@ -130,7 +151,7 @@ class ForceGroup:
def create_ground_object_for_layout( def create_ground_object_for_layout(
self, self,
layout: TheaterLayout, layout: TgoLayout,
name: str, name: str,
position: PointWithHeading, position: PointWithHeading,
control_point: ControlPoint, control_point: ControlPoint,
@ -139,25 +160,31 @@ class ForceGroup:
"""Create a TheaterGroundObject for the given template""" """Create a TheaterGroundObject for the given template"""
go = layout.create_ground_object(name, position, control_point) go = layout.create_ground_object(name, position, control_point)
# Generate all groups using the randomization if it defined # Generate all groups using the randomization if it defined
for group in layout.groups: for group_name, groups in layout.groups.items():
# Choose a random unit_type for the group for group in groups:
try: # Choose a random unit_type for the group
unit_type = self.random_dcs_unit_type_for_group(group) try:
except IndexError: unit_type = self.random_dcs_unit_type_for_group(group)
if group.optional: except IndexError:
# If group is optional it is ok when no unit_type is available if group.optional:
continue # If group is optional it is ok when no unit_type is available
# if non-optional this is a error continue
raise RuntimeError(f"No accessible unit for {self.name} - {group.name}") # if non-optional this is a error
self.create_theater_group_for_tgo(go, group, name, game, unit_type) 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 return go
def create_theater_group_for_tgo( def create_theater_group_for_tgo(
self, self,
ground_object: TheaterGroundObject, ground_object: TheaterGroundObject,
group: GroupLayout, group: TgoLayoutGroup,
name: str, group_name: str,
game: Game, game: Game,
unit_type: Type[DcsUnitType], unit_type: Type[DcsUnitType],
unit_count: Optional[int] = None, unit_count: Optional[int] = None,
@ -165,25 +192,29 @@ class ForceGroup:
"""Create a TheaterGroup and add it to the given TGO""" """Create a TheaterGroup and add it to the given TGO"""
# Random UnitCounter if not forced # Random UnitCounter if not forced
if unit_count is None: if unit_count is None:
unit_count = group.unit_counter unit_count = group.group_size
# Static and non Static groups have to be separated # Generate Units
group_id = group.group - 1 units = group.generate_units(ground_object, unit_type, unit_count)
if len(ground_object.groups) <= group_id: # Get or create the TheaterGroup
# Requested group was not yet created ground_group = ground_object.group_by_name(group_name)
ground_group = TheaterGroup.from_template( if ground_group is not None:
game.next_group_id(), group, ground_object, unit_type, unit_count # TheaterGroup with this name exists already. Extend it
)
# 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)
ground_group.units.extend(units) 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 # 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.id = game.next_unit_id()
unit.name = unit.unit_type.name if unit.unit_type else unit.type.name unit.name = unit.unit_type.name if unit.unit_type else unit.type.name
unit.position = PointWithHeading.from_point( unit.position = PointWithHeading.from_point(
@ -209,42 +240,46 @@ class ForceGroup:
@classmethod @classmethod
def _load_all(cls) -> None: 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(): if not file.is_file():
raise RuntimeError(f"{file.name} is not a valid ForceGroup") raise RuntimeError(f"{file.name} is not a valid ForceGroup")
with file.open(encoding="utf-8") as data_file: with file.open(encoding="utf-8") as data_file:
data = yaml.safe_load(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 = [] statics = []
for static in data.get("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: if static_type is None:
logging.error(f"Static {static} for {file} is not valid") logging.error(f"Static {static} for {file} is not valid")
else: else:
statics.append(static_type) 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( force_group = ForceGroup(
name=data.get("name"), name=name,
units=units, units=units,
statics=statics, statics=statics,
role=group_role,
tasks=group_tasks, tasks=group_tasks,
layouts=layouts, layouts=layouts,
) )
cls._by_name[force_group.name] = force_group 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 cls._loaded = True

View File

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

View File

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

View File

@ -150,7 +150,7 @@ class Faction:
for unit in preset_group.units for unit in preset_group.units
), ),
) )
return list(all_units) return list(set(all_units))
@property @property
def air_defenses(self) -> list[str]: def air_defenses(self) -> list[str]:
@ -158,7 +158,11 @@ class Faction:
# This is used for the faction overview in NewGameWizard # This is used for the faction overview in NewGameWizard
air_defenses = [a.name for a in self.air_defense_units] air_defenses = [a.name for a in self.air_defense_units]
air_defenses.extend( 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) return sorted(air_defenses)

View File

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

View File

@ -1,9 +1,10 @@
from __future__ import annotations from __future__ import annotations
from collections import defaultdict
import logging import logging
import random import random
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Type from typing import TYPE_CHECKING, Iterator, Type
from dcs import Point from dcs import Point
from dcs.unit import Unit from dcs.unit import Unit
@ -50,98 +51,121 @@ class LayoutUnit:
"""Creates a LayoutUnit from a DCS Unit""" """Creates a LayoutUnit from a DCS Unit"""
return LayoutUnit( return LayoutUnit(
unit.name, unit.name,
Point(int(unit.position.x), int(unit.position.y)), unit.position,
int(unit.heading), int(unit.heading),
) )
@dataclass @dataclass
class GroupLayout: class TgoLayoutGroup:
"""The Layout of a TheaterGroup""" """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 name: str
units: list[LayoutUnit] layout_units: list[LayoutUnit]
# The group this template will be merged into # Define the amount of units to be created. This can be a fixed int or a random
group: int = 1 # choice from a range of two ints. If the list is empty it will use the whole group
# size / all available LayoutUnits
# 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
unit_count: list[int] = field(default_factory=list) unit_count: list[int] = field(default_factory=list)
# defintion which unit types are supported # defintion which unit types are supported
unit_types: list[Type[DcsUnitType]] = field(default_factory=list) unit_types: list[Type[DcsUnitType]] = field(default_factory=list)
unit_classes: list[UnitClass] = 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 # Defines if this groupTemplate is required or not
optional: bool = False 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]]: 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)] unit_types = [t for t in self.unit_types if faction.has_access_to_dcs_type(t)]
alternative_types = [] alternative_types = []
for accessible_unit in faction.accessible_units: for accessible_unit in faction.accessible_units:
if accessible_unit.unit_class in self.unit_classes: if accessible_unit.unit_class in self.unit_classes:
unit_types.append(accessible_unit.dcs_unit_type) 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) alternative_types.append(accessible_unit.dcs_unit_type)
if not unit_types and not alternative_types and not self.optional: 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 return unit_types or alternative_types
@property @property
def unit_counter(self) -> int: def group_size(self) -> int:
"""TODO Documentation""" """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."""
default = len(self.units)
if self.unit_count: if self.unit_count:
if len(self.unit_count) == 1: if len(self.unit_count) == 1:
count = self.unit_count[0] return self.unit_count[0]
else: return random.choice(range(min(self.unit_count), max(self.unit_count)))
count = random.choice(range(min(self.unit_count), max(self.unit_count))) return self.max_size
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
@property @property
def max_size(self) -> int: def max_size(self) -> int:
return len(self.units) return len(self.layout_units)
def generate_units( def generate_units(
self, go: TheaterGroundObject, unit_type: Type[DcsUnitType], amount: int self, go: TheaterGroundObject, unit_type: Type[DcsUnitType], amount: int
) -> list[TheaterUnit]: ) -> list[TheaterUnit]:
"""TODO Documentation""" """Generate units of the given unit type and amount for the TgoLayoutGroup"""
return [ 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) for i in range(amount)
] ]
class TheaterLayout: class TgoLayout:
"""TODO Documentation""" """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.name = name
self.role = role
self.description = description self.description = description
self.tasks: list[GroupTask] = [] # The supported tasks self.tasks: list[GroupTask] = [] # The supported
self.groups: list[GroupLayout] = []
# If the template is generic it will be used the generate the general # Mapping of group name and LayoutGroups for a specific TgoGroup
# UnitGroups during faction initialization. Generic Groups allow to be mixed # 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 self.generic: bool = False
def usable_by_faction(self, faction: Faction) -> bool: def usable_by_faction(self, faction: Faction) -> bool:
@ -156,7 +180,7 @@ class TheaterLayout:
try: try:
return all( return all(
len(group.possible_types_for_faction(faction)) > 0 len(group.possible_types_for_faction(faction)) > 0
for group in self.groups for group in self.all_groups
if not group.optional if not group.optional
) )
except LayoutException: except LayoutException:
@ -168,22 +192,20 @@ class TheaterLayout:
position: PointWithHeading, position: PointWithHeading,
control_point: ControlPoint, control_point: ControlPoint,
) -> TheaterGroundObject: ) -> 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 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 @property
def size(self) -> int: def all_groups(self) -> Iterator[TgoLayoutGroup]:
return sum([len(group.units) for group in self.groups]) for groups in self.groups.values():
yield from groups
class AntiAirLayout(TheaterLayout): class AntiAirLayout(TgoLayout):
def create_ground_object( def create_ground_object(
self, self,
name: str, name: str,
@ -200,7 +222,7 @@ class AntiAirLayout(TheaterLayout):
) )
class BuildingLayout(TheaterLayout): class BuildingLayout(TgoLayout):
def create_ground_object( def create_ground_object(
self, self,
name: str, name: str,
@ -224,7 +246,7 @@ class BuildingLayout(TheaterLayout):
raise RuntimeError(f"Building Template {self.name} has no building category") raise RuntimeError(f"Building Template {self.name} has no building category")
class NavalLayout(TheaterLayout): class NavalLayout(TgoLayout):
def create_ground_object( def create_ground_object(
self, self,
name: str, name: str,
@ -240,7 +262,7 @@ class NavalLayout(TheaterLayout):
raise NotImplementedError raise NotImplementedError
class DefensesLayout(TheaterLayout): class DefensesLayout(TgoLayout):
def create_ground_object( def create_ground_object(
self, self,
name: str, name: str,
@ -258,7 +280,7 @@ class DefensesLayout(TheaterLayout):
raise NotImplementedError raise NotImplementedError
class GroundForceLayout(TheaterLayout): class GroundForceLayout(TgoLayout):
def create_ground_object( def create_ground_object(
self, self,
name: str, name: str,

View File

@ -1,4 +1,5 @@
from __future__ import annotations from __future__ import annotations
from collections import defaultdict
import itertools import itertools
import logging import logging
@ -13,10 +14,10 @@ from dcs import Point
from dcs.unitgroup import StaticGroup from dcs.unitgroup import StaticGroup
from game import persistency from game import persistency
from game.data.groups import GroupRole, GroupTask from game.data.groups import GroupRole
from game.layout.layout import ( from game.layout.layout import (
TheaterLayout, TgoLayout,
GroupLayout, TgoLayoutGroup,
LayoutUnit, LayoutUnit,
AntiAirLayout, AntiAirLayout,
BuildingLayout, BuildingLayout,
@ -24,7 +25,7 @@ from game.layout.layout import (
GroundForceLayout, GroundForceLayout,
DefensesLayout, DefensesLayout,
) )
from game.layout.layoutmapping import GroupLayoutMapping, LayoutMapping from game.layout.layoutmapping import LayoutMapping
from game.profiling import logged_duration from game.profiling import logged_duration
from game.version import VERSION from game.version import VERSION
@ -41,21 +42,21 @@ TEMPLATE_TYPES = {
class LayoutLoader: class LayoutLoader:
# list of layouts per category. e.g. AA or similar # Map of all available layouts indexed by name
_templates: dict[str, TheaterLayout] = {} _layouts: dict[str, TgoLayout] = {}
def __init__(self) -> None: def __init__(self) -> None:
self._templates = {} self._layouts = {}
def initialize(self) -> None: def initialize(self) -> None:
if not self._templates: if not self._layouts:
with logged_duration("Loading layouts"): with logged_duration("Loading layouts"):
self.load_templates() self.load_templates()
@property @property
def layouts(self) -> Iterator[TheaterLayout]: def layouts(self) -> Iterator[TgoLayout]:
self.initialize() self.initialize()
yield from self._templates.values() yield from self._layouts.values()
def load_templates(self) -> None: def load_templates(self) -> None:
"""This will load all pre-loaded layouts from a pickle file. """This will load all pre-loaded layouts from a pickle file.
@ -66,60 +67,43 @@ class LayoutLoader:
# Load from pickle if existing # Load from pickle if existing
with file.open("rb") as f: with file.open("rb") as f:
try: 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 # Check if the game version of the dump is identical to the current
if version == VERSION: if version == VERSION:
return return
except Exception as e: 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 # If no dump is available or game version is different create a new dump
self.import_templates() self.import_templates()
def import_templates(self) -> None: def import_templates(self) -> None:
"""This will import all layouts from the template folder """This will import all layouts from the template folder
and dumps them to a pickle""" 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"): with logged_duration("Parsing mapping yamls"):
for file in Path(TEMPLATE_DIR).rglob("*.yaml"): for file in Path(TEMPLATE_DIR).rglob("*.yaml"):
if not file.is_file(): if not file.is_file():
continue raise RuntimeError(f"{file.name} is not a file")
with file.open("r", encoding="utf-8") as f: with file.open("r", encoding="utf-8") as f:
mapping_dict = yaml.safe_load(f) mapping_dict = yaml.safe_load(f)
template_map = LayoutMapping.from_dict(mapping_dict, f.name) template_map = LayoutMapping.from_dict(mapping_dict, f.name)
mappings[template_map.layout_file].append(template_map)
if template_map.layout_file in mappings:
mappings[template_map.layout_file].append(template_map)
else:
mappings[template_map.layout_file] = [template_map]
with logged_duration(f"Parsing all layout miz multithreaded"): with logged_duration(f"Parsing all layout miz multithreaded"):
with ThreadPoolExecutor() as exe: with ThreadPoolExecutor() as exe:
for miz, maps in mappings.items(): exe.map(self._load_from_miz, mappings.keys(), mappings.values())
exe.submit(self._load_from_miz, miz, maps)
logging.info(f"Imported {len(self._templates)} layouts") logging.info(f"Imported {len(self._layouts)} layouts")
self._dump_templates() self._dump_templates()
def _dump_templates(self) -> None: def _dump_templates(self) -> None:
file = Path(persistency.base_path()) / TEMPLATE_DUMP file = Path(persistency.base_path()) / TEMPLATE_DUMP
dump = (VERSION, self._templates) dump = (VERSION, self._layouts)
with file.open("wb") as fdata: with file.open("wb") as fdata:
pickle.dump(dump, 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: def _load_from_miz(self, miz: str, mappings: list[LayoutMapping]) -> None:
template_position: dict[str, Point] = {} template_position: dict[str, Point] = {}
temp_mis = dcs.Mission() temp_mis = dcs.Mission()
@ -130,74 +114,68 @@ class LayoutLoader:
# the .load_file() method: 0:00:00.920409 # the .load_file() method: 0:00:00.920409
temp_mis.load_file(miz) temp_mis.load_file(miz)
for country in itertools.chain( for mapping in mappings:
temp_mis.coalition["red"].countries.values(), # Find the group from the mapping in any coalition
temp_mis.coalition["blue"].countries.values(), for country in itertools.chain(
): temp_mis.coalition["red"].countries.values(),
for dcs_group in itertools.chain( temp_mis.coalition["blue"].countries.values(),
temp_mis.country(country.name).vehicle_group,
temp_mis.country(country.name).ship_group,
temp_mis.country(country.name).static_group,
): ):
try: for dcs_group in itertools.chain(
mapping, group_id, group_mapping = self.mapping_for_group( temp_mis.country(country.name).vehicle_group,
mappings, dcs_group.name temp_mis.country(country.name).ship_group,
) temp_mis.country(country.name).static_group,
except KeyError: ):
logging.warning(f"No mapping for dcs group {dcs_group.name}")
continue
template = self._templates.get(mapping.name, None) try:
if template is None: group_name, group_mapping = mapping.group_for_name(
# Create a new template dcs_group.name
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,
) )
group_template.optional = group_mapping.optional except KeyError:
# Add the group at the correct position continue
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)
def by_name(self, template_name: str) -> Iterator[TheaterLayout]: if not isinstance(dcs_group, StaticGroup) and max(
for template in self.layouts: group_mapping.unit_count
if template.name == template_name: ) > len(dcs_group.units):
yield template logging.error(
f"Incorrect unit_count found in Layout {mapping.name}-{group_mapping.name}"
)
def by_task(self, group_task: GroupTask) -> Iterator[TheaterLayout]: layout = self._layouts.get(mapping.name, None)
for template in self.layouts: if layout is None:
if not group_task or group_task in template.tasks: # Create a new template
yield 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]: for i, unit in enumerate(dcs_group.units):
unique_templates = [] group_layout = None
for group_task in group_tasks: for group in layout.all_groups:
for template in self.by_task(group_task): if group.name == group_mapping.name:
if template not in unique_templates: # We already have a layoutgroup for this dcs_group
unique_templates.append(template) group_layout = group
yield from unique_templates 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 __future__ import annotations
from collections import defaultdict
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Any, Type from typing import Any, Type
from dcs.unittype import UnitType as DcsUnitType from dcs.unittype import UnitType as DcsUnitType
from game import db
from game.data.groups import GroupRole, GroupTask from game.data.groups import GroupRole, GroupTask
from game.data.units import UnitClass from game.data.units import UnitClass
from game.dcs.helpers import unit_type_from_name
@dataclass @dataclass
@ -21,10 +22,6 @@ class GroupLayoutMapping:
# All static units for the group # All static units for the group
statics: list[str] = field(default_factory=list) 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 # 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 # added this will be an exact amount. If 2 values are used it will be a random
# amount between these values. # amount between these values.
@ -36,31 +33,9 @@ class GroupLayoutMapping:
# All unit classes the template supports. # All unit classes the template supports.
unit_classes: list[UnitClass] = field(default_factory=list) 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 # dedicated EWRs are available to the faction
alternative_classes: list[UnitClass] = field(default_factory=list) fallback_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
@staticmethod @staticmethod
def from_dict(d: dict[str, Any]) -> GroupLayoutMapping: def from_dict(d: dict[str, Any]) -> GroupLayoutMapping:
@ -70,27 +45,25 @@ class GroupLayoutMapping:
unit_types = [] unit_types = []
if "unit_types" in d: if "unit_types" in d:
for u in d["unit_types"]: for u in d["unit_types"]:
unit_type = db.unit_type_from_name(u) unit_type = unit_type_from_name(u)
if unit_type: if unit_type:
unit_types.append(unit_type) unit_types.append(unit_type)
group = d["group"] if "group" in d else 1
unit_classes = ( unit_classes = (
[UnitClass(u) for u in d["unit_classes"]] if "unit_classes" in d else [] [UnitClass(u) for u in d["unit_classes"]] if "unit_classes" in d else []
) )
alternative_classes = ( fallback_classes = (
[UnitClass(u) for u in d["alternative_classes"]] [UnitClass(u) for u in d["fallback_classes"]]
if "alternative_classes" in d if "fallback_classes" in d
else [] else []
) )
return GroupLayoutMapping( return GroupLayoutMapping(
d["name"], d["name"],
optional, optional,
statics, statics,
group,
unit_count, unit_count,
unit_types, unit_types,
unit_classes, 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 # Optional field to define if the template can be used to create generic groups
generic: bool generic: bool
# The role the template can be used for
role: GroupRole
# All taskings the template can be used for # All taskings the template can be used for
tasks: list[GroupTask] tasks: list[GroupTask]
# All Groups the template has # 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 # Define the miz file for the template. Optional. If empty use the mapping name
layout_file: str layout_file: str
def to_dict(self) -> dict[str, Any]: @property
d = { def primary_role(self) -> GroupRole:
"name": self.name, return self.tasks[0].role
"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
@staticmethod @staticmethod
def from_dict(d: dict[str, Any], file_name: str) -> LayoutMapping: 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 "" description = d["description"] if "description" in d else ""
generic = d["generic"] if "generic" in d else False generic = d["generic"] if "generic" in d else False
layout_file = ( layout_file = (
@ -149,8 +113,14 @@ class LayoutMapping:
d["name"], d["name"],
description, description,
generic, generic,
GroupRole(d["role"]),
tasks, tasks,
groups, groups,
layout_file, 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() self.generate_navy()
return True 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( def generate_ground_object_from_group(
self, unit_group: ForceGroup, position: PointWithHeading self, unit_group: ForceGroup, position: PointWithHeading
) -> None: ) -> None:

View File

@ -3,7 +3,7 @@ from __future__ import annotations
import itertools import itertools
import logging import logging
from abc import ABC 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.unittype import VehicleType
from dcs.vehicles import vehicle_map from dcs.vehicles import vehicle_map
@ -205,6 +205,12 @@ class TheaterGroundObject(MissionTarget):
def purchasable(self) -> bool: def purchasable(self) -> bool:
raise NotImplementedError 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): class BuildingGroundObject(TheaterGroundObject):
def __init__( def __init__(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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