mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Make units selectable in faction overview during campaign gen
Resolves #35 Resolves #40
This commit is contained in:
parent
cd4ace4ad5
commit
192741af36
@ -4,6 +4,7 @@
|
|||||||
* **[Mission Generation]** Given a CAS flight was planned, delay ground force attack until first CAS flight is on station
|
* **[Mission Generation]** Given a CAS flight was planned, delay ground force attack until first CAS flight is on station
|
||||||
* **[Mission Generation]** Add option to switch ATFLIR to LITENING automatically for ground based F-18C flights
|
* **[Mission Generation]** Add option to switch ATFLIR to LITENING automatically for ground based F-18C flights
|
||||||
* **[Cheat Menu]** Option to instantly transfer squadrons across bases.
|
* **[Cheat Menu]** Option to instantly transfer squadrons across bases.
|
||||||
|
* **[UI]** Add selectable units in faction overview during campaign generation.
|
||||||
|
|
||||||
## Fixes
|
## Fixes
|
||||||
* **[UI]** Removed deprecated options
|
* **[UI]** Removed deprecated options
|
||||||
|
|||||||
@ -60,7 +60,7 @@ class Faction:
|
|||||||
description: str = field(default="")
|
description: str = field(default="")
|
||||||
|
|
||||||
# Available aircraft
|
# Available aircraft
|
||||||
aircrafts: List[AircraftType] = field(default_factory=list)
|
aircraft: List[AircraftType] = field(default_factory=list)
|
||||||
|
|
||||||
# Available awacs aircraft
|
# Available awacs aircraft
|
||||||
awacs: List[AircraftType] = field(default_factory=list)
|
awacs: List[AircraftType] = field(default_factory=list)
|
||||||
@ -178,6 +178,10 @@ class Faction:
|
|||||||
)
|
)
|
||||||
return sorted(air_defenses)
|
return sorted(air_defenses)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def aircrafts(self) -> list[UnitType[Any]]:
|
||||||
|
return list(self.aircraft + self.awacs + self.tankers)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_json(cls: Type[Faction], json: Dict[str, Any]) -> Faction:
|
def from_json(cls: Type[Faction], json: Dict[str, Any]) -> Faction:
|
||||||
faction = Faction(locales=json.get("locales"))
|
faction = Faction(locales=json.get("locales"))
|
||||||
@ -206,14 +210,10 @@ class Faction:
|
|||||||
faction.authors = json.get("authors", "")
|
faction.authors = json.get("authors", "")
|
||||||
faction.description = json.get("description", "")
|
faction.description = json.get("description", "")
|
||||||
|
|
||||||
faction.aircrafts = [AircraftType.named(n) for n in json.get("aircrafts", [])]
|
faction.aircraft = [AircraftType.named(n) for n in json.get("aircrafts", [])]
|
||||||
faction.awacs = [AircraftType.named(n) for n in json.get("awacs", [])]
|
faction.awacs = [AircraftType.named(n) for n in json.get("awacs", [])]
|
||||||
faction.tankers = [AircraftType.named(n) for n in json.get("tankers", [])]
|
faction.tankers = [AircraftType.named(n) for n in json.get("tankers", [])]
|
||||||
|
|
||||||
faction.aircrafts = list(
|
|
||||||
set(faction.aircrafts + faction.awacs + faction.tankers)
|
|
||||||
)
|
|
||||||
|
|
||||||
faction.frontline_units = [
|
faction.frontline_units = [
|
||||||
GroundUnitType.named(n) for n in json.get("frontline_units", [])
|
GroundUnitType.named(n) for n in json.get("frontline_units", [])
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,16 +1,28 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from PySide2 import QtGui, QtWidgets
|
from PySide2 import QtGui, QtWidgets
|
||||||
from PySide2.QtCore import QDate, QItemSelectionModel, QPoint, Qt, Signal
|
from PySide2.QtCore import QDate, QItemSelectionModel, QPoint, Qt, Signal
|
||||||
from PySide2.QtWidgets import QCheckBox, QLabel, QTextEdit, QVBoxLayout, QTextBrowser
|
from PySide2.QtWidgets import (
|
||||||
|
QCheckBox,
|
||||||
|
QLabel,
|
||||||
|
QTextEdit,
|
||||||
|
QVBoxLayout,
|
||||||
|
QTextBrowser,
|
||||||
|
QWidget,
|
||||||
|
QGridLayout,
|
||||||
|
QScrollArea,
|
||||||
|
QSizePolicy,
|
||||||
|
)
|
||||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||||
|
|
||||||
from game.campaignloader.campaign import Campaign, DEFAULT_BUDGET
|
from game.campaignloader.campaign import Campaign, DEFAULT_BUDGET
|
||||||
from game.dcs.aircrafttype import AircraftType
|
from game.dcs.aircrafttype import AircraftType
|
||||||
|
from game.dcs.unittype import UnitType
|
||||||
from game.factions import FACTIONS, Faction
|
from game.factions import FACTIONS, Faction
|
||||||
from game.settings import Settings
|
from game.settings import Settings
|
||||||
from game.theater.start_generator import GameGenerator, GeneratorSettings, ModSettings
|
from game.theater.start_generator import GameGenerator, GeneratorSettings, ModSettings
|
||||||
@ -236,6 +248,87 @@ class IntroPage(QtWidgets.QWizardPage):
|
|||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
|
||||||
|
class QFactionUnits(QScrollArea):
|
||||||
|
def __init__(self, faction: Faction, parent=None):
|
||||||
|
super().__init__()
|
||||||
|
self.setWidgetResizable(True)
|
||||||
|
self.content = QWidget()
|
||||||
|
self.setWidget(self.content)
|
||||||
|
self.parent = parent
|
||||||
|
self.faction = faction
|
||||||
|
self._create_checkboxes()
|
||||||
|
|
||||||
|
def _add_checkboxes(self, units: list, counter: int, grid: QGridLayout) -> int:
|
||||||
|
counter += 1
|
||||||
|
for i, v in enumerate(sorted(units, key=lambda x: x.name), counter):
|
||||||
|
cb = QCheckBox(v.name)
|
||||||
|
cb.setCheckState(Qt.CheckState.Checked)
|
||||||
|
self.checkboxes[v.name] = cb
|
||||||
|
grid.addWidget(cb, i, 1)
|
||||||
|
counter += 1
|
||||||
|
counter += 1
|
||||||
|
return counter
|
||||||
|
|
||||||
|
def _create_checkboxes(self):
|
||||||
|
counter = 0
|
||||||
|
self.checkboxes: dict[str, QCheckBox] = {}
|
||||||
|
grid = QGridLayout()
|
||||||
|
if len(self.faction.aircraft) > 0:
|
||||||
|
grid.addWidget(QLabel("<strong>Aircraft:</strong>"), counter, 0)
|
||||||
|
counter = self._add_checkboxes(self.faction.aircraft, counter, grid)
|
||||||
|
if len(self.faction.awacs) > 0:
|
||||||
|
grid.addWidget(QLabel("<strong>AWACS:</strong>"), counter, 0)
|
||||||
|
counter = self._add_checkboxes(self.faction.awacs, counter, grid)
|
||||||
|
if len(self.faction.tankers) > 0:
|
||||||
|
grid.addWidget(QLabel("<strong>Tankers:</strong>"), counter, 0)
|
||||||
|
counter = self._add_checkboxes(self.faction.tankers, counter, grid)
|
||||||
|
if len(self.faction.frontline_units) > 0:
|
||||||
|
grid.addWidget(QLabel("<strong>Frontlines vehicles:</strong>"), counter, 0)
|
||||||
|
counter = self._add_checkboxes(self.faction.frontline_units, counter, grid)
|
||||||
|
if len(self.faction.artillery_units) > 0:
|
||||||
|
grid.addWidget(QLabel("<strong>Artillery units:</strong>"), counter, 0)
|
||||||
|
counter = self._add_checkboxes(self.faction.artillery_units, counter, grid)
|
||||||
|
if len(self.faction.logistics_units) > 0:
|
||||||
|
grid.addWidget(QLabel("<strong>Logistics units:</strong>"), counter, 0)
|
||||||
|
counter = self._add_checkboxes(self.faction.logistics_units, counter, grid)
|
||||||
|
if len(self.faction.infantry_units) > 0:
|
||||||
|
grid.addWidget(QLabel("<strong>Infantry units:</strong>"), counter, 0)
|
||||||
|
counter = self._add_checkboxes(self.faction.infantry_units, counter, grid)
|
||||||
|
if len(self.faction.preset_groups) > 0:
|
||||||
|
grid.addWidget(QLabel("<strong>Preset groups:</strong>"), counter, 0)
|
||||||
|
counter = self._add_checkboxes(self.faction.preset_groups, counter, grid)
|
||||||
|
if len(self.faction.air_defense_units) > 0:
|
||||||
|
grid.addWidget(QLabel("<strong>Air defenses:</strong>"), counter, 0)
|
||||||
|
counter = self._add_checkboxes(
|
||||||
|
self.faction.air_defense_units, counter, grid
|
||||||
|
)
|
||||||
|
if len(self.faction.naval_units) > 0:
|
||||||
|
grid.addWidget(QLabel("<strong>Naval units:</strong>"), counter, 0)
|
||||||
|
counter = self._add_checkboxes(self.faction.naval_units, counter, grid)
|
||||||
|
if len(self.faction.missiles) > 0:
|
||||||
|
grid.addWidget(QLabel("<strong>Missile units:</strong>"), counter, 0)
|
||||||
|
self._add_checkboxes(self.faction.missiles, counter, grid)
|
||||||
|
|
||||||
|
self.content.setLayout(grid)
|
||||||
|
|
||||||
|
def updateFaction(self, faction: Faction):
|
||||||
|
self.faction = faction
|
||||||
|
self.content = QWidget()
|
||||||
|
self.setWidget(self.content)
|
||||||
|
self._create_checkboxes()
|
||||||
|
self.update()
|
||||||
|
if self.parent:
|
||||||
|
self.parent.update()
|
||||||
|
|
||||||
|
def updateFactionUnits(self, units: list):
|
||||||
|
deletes = []
|
||||||
|
for a in units:
|
||||||
|
if not self.checkboxes[a.name].isChecked():
|
||||||
|
deletes.append(a)
|
||||||
|
for d in deletes:
|
||||||
|
units.remove(d)
|
||||||
|
|
||||||
|
|
||||||
class FactionSelection(QtWidgets.QWizardPage):
|
class FactionSelection(QtWidgets.QWizardPage):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super(FactionSelection, self).__init__(parent)
|
super(FactionSelection, self).__init__(parent)
|
||||||
@ -259,8 +352,6 @@ class FactionSelection(QtWidgets.QWizardPage):
|
|||||||
|
|
||||||
blueFaction = QtWidgets.QLabel("<b>Player Faction :</b>")
|
blueFaction = QtWidgets.QLabel("<b>Player Faction :</b>")
|
||||||
self.blueFactionSelect = QtWidgets.QComboBox()
|
self.blueFactionSelect = QtWidgets.QComboBox()
|
||||||
for f in FACTIONS:
|
|
||||||
self.blueFactionSelect.addItem(f)
|
|
||||||
blueFaction.setBuddy(self.blueFactionSelect)
|
blueFaction.setBuddy(self.blueFactionSelect)
|
||||||
|
|
||||||
redFaction = QtWidgets.QLabel("<b>Enemy Faction :</b>")
|
redFaction = QtWidgets.QLabel("<b>Enemy Faction :</b>")
|
||||||
@ -271,26 +362,41 @@ class FactionSelection(QtWidgets.QWizardPage):
|
|||||||
self.blueFactionDescription = QTextBrowser()
|
self.blueFactionDescription = QTextBrowser()
|
||||||
self.blueFactionDescription.setReadOnly(True)
|
self.blueFactionDescription.setReadOnly(True)
|
||||||
self.blueFactionDescription.setOpenExternalLinks(True)
|
self.blueFactionDescription.setOpenExternalLinks(True)
|
||||||
|
self.blueFactionDescription.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
|
||||||
|
self.blueFactionDescription.setMaximumHeight(120)
|
||||||
|
|
||||||
self.redFactionDescription = QTextBrowser()
|
self.redFactionDescription = QTextBrowser()
|
||||||
self.redFactionDescription.setReadOnly(True)
|
self.redFactionDescription.setReadOnly(True)
|
||||||
self.redFactionDescription.setOpenExternalLinks(True)
|
self.redFactionDescription.setOpenExternalLinks(True)
|
||||||
|
self.redFactionDescription.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
|
||||||
|
self.redFactionDescription.setMaximumHeight(120)
|
||||||
|
|
||||||
# Setup default selected factions
|
# Setup default selected factions
|
||||||
for i, r in enumerate(FACTIONS):
|
for i, r in enumerate(FACTIONS):
|
||||||
self.redFactionSelect.addItem(r)
|
self.blueFactionSelect.addItem(r, FACTIONS[r])
|
||||||
|
self.redFactionSelect.addItem(r, FACTIONS[r])
|
||||||
if r == "Russia 1990":
|
if r == "Russia 1990":
|
||||||
self.redFactionSelect.setCurrentIndex(i)
|
self.redFactionSelect.setCurrentIndex(i)
|
||||||
if r == "USA 2005":
|
if r == "USA 2005":
|
||||||
self.blueFactionSelect.setCurrentIndex(i)
|
self.blueFactionSelect.setCurrentIndex(i)
|
||||||
|
|
||||||
|
# Faction units
|
||||||
|
self.blueFactionUnits = QFactionUnits(
|
||||||
|
self.blueFactionSelect.currentData(), self.blueGroupLayout
|
||||||
|
)
|
||||||
|
self.redFactionUnits = QFactionUnits(
|
||||||
|
self.redFactionSelect.currentData(), self.redGroupLayout
|
||||||
|
)
|
||||||
|
|
||||||
self.blueGroupLayout.addWidget(blueFaction, 0, 0)
|
self.blueGroupLayout.addWidget(blueFaction, 0, 0)
|
||||||
self.blueGroupLayout.addWidget(self.blueFactionSelect, 0, 1)
|
self.blueGroupLayout.addWidget(self.blueFactionSelect, 0, 1)
|
||||||
self.blueGroupLayout.addWidget(self.blueFactionDescription, 1, 0, 1, 2)
|
self.blueGroupLayout.addWidget(self.blueFactionDescription, 1, 0, 1, 2)
|
||||||
|
self.blueGroupLayout.addWidget(self.blueFactionUnits, 2, 0, 1, 2)
|
||||||
|
|
||||||
self.redGroupLayout.addWidget(redFaction, 0, 0)
|
self.redGroupLayout.addWidget(redFaction, 0, 0)
|
||||||
self.redGroupLayout.addWidget(self.redFactionSelect, 0, 1)
|
self.redGroupLayout.addWidget(self.redFactionSelect, 0, 1)
|
||||||
self.redGroupLayout.addWidget(self.redFactionDescription, 1, 0, 1, 2)
|
self.redGroupLayout.addWidget(self.redFactionDescription, 1, 0, 1, 2)
|
||||||
|
self.redGroupLayout.addWidget(self.redFactionUnits, 2, 0, 1, 2)
|
||||||
|
|
||||||
self.factionsGroupLayout.addLayout(self.blueGroupLayout)
|
self.factionsGroupLayout.addLayout(self.blueGroupLayout)
|
||||||
self.factionsGroupLayout.addLayout(self.redGroupLayout)
|
self.factionsGroupLayout.addLayout(self.redGroupLayout)
|
||||||
@ -348,13 +454,35 @@ class FactionSelection(QtWidgets.QWizardPage):
|
|||||||
self.blueFactionDescription.setText(blue_faction_txt)
|
self.blueFactionDescription.setText(blue_faction_txt)
|
||||||
self.redFactionDescription.setText(red_faction_txt)
|
self.redFactionDescription.setText(red_faction_txt)
|
||||||
|
|
||||||
|
self.blueGroupLayout.removeWidget(self.blueFactionUnits)
|
||||||
|
self.blueFactionUnits.updateFaction(blue_faction)
|
||||||
|
self.blueGroupLayout.addWidget(self.blueFactionUnits, 2, 0, 1, 2)
|
||||||
|
self.redGroupLayout.removeWidget(self.redFactionUnits)
|
||||||
|
self.redFactionUnits.updateFaction(red_faction)
|
||||||
|
self.redGroupLayout.addWidget(self.redFactionUnits, 2, 0, 1, 2)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _filter_selected_units(qfu: QFactionUnits) -> Faction:
|
||||||
|
qfu.updateFactionUnits(qfu.faction.aircrafts)
|
||||||
|
qfu.updateFactionUnits(qfu.faction.awacs)
|
||||||
|
qfu.updateFactionUnits(qfu.faction.tankers)
|
||||||
|
qfu.updateFactionUnits(qfu.faction.frontline_units)
|
||||||
|
qfu.updateFactionUnits(qfu.faction.artillery_units)
|
||||||
|
qfu.updateFactionUnits(qfu.faction.logistics_units)
|
||||||
|
qfu.updateFactionUnits(qfu.faction.infantry_units)
|
||||||
|
qfu.updateFactionUnits(qfu.faction.preset_groups)
|
||||||
|
qfu.updateFactionUnits(qfu.faction.air_defense_units)
|
||||||
|
qfu.updateFactionUnits(qfu.faction.naval_units)
|
||||||
|
qfu.updateFactionUnits(qfu.faction.missiles)
|
||||||
|
return qfu.faction
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def selected_blue_faction(self) -> Faction:
|
def selected_blue_faction(self) -> Faction:
|
||||||
return FACTIONS[self.blueFactionSelect.currentText()]
|
return self._filter_selected_units(self.blueFactionUnits)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def selected_red_faction(self) -> Faction:
|
def selected_red_faction(self) -> Faction:
|
||||||
return FACTIONS[self.redFactionSelect.currentText()]
|
return self._filter_selected_units(self.redFactionUnits)
|
||||||
|
|
||||||
|
|
||||||
class TheaterConfiguration(QtWidgets.QWizardPage):
|
class TheaterConfiguration(QtWidgets.QWizardPage):
|
||||||
|
|||||||
@ -76,7 +76,7 @@
|
|||||||
"Paratrooper AKS",
|
"Paratrooper AKS",
|
||||||
"Paratrooper RPG-16"
|
"Paratrooper RPG-16"
|
||||||
],
|
],
|
||||||
"missiles": [
|
"missiles": [
|
||||||
"SSM SS-1C Scud-B"
|
"SSM SS-1C Scud-B"
|
||||||
],
|
],
|
||||||
"preset_groups": [
|
"preset_groups": [
|
||||||
|
|||||||
@ -1,62 +1,4 @@
|
|||||||
{{ faction.description|safe }}
|
<strong>Description:</strong> {{ faction.description|safe }}
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<strong>Author(s):</strong> {{ faction.authors }}
|
<strong>Author(s):</strong> {{ faction.authors }}
|
||||||
<br/><br/>
|
|
||||||
|
|
||||||
|
|
||||||
<strong>Potential aircraft:</strong>
|
|
||||||
<p>
|
|
||||||
The aircraft that will be present in the game are specified by the campaign.
|
|
||||||
Only aircraft in this list will be allowed, but not all aircraft in this
|
|
||||||
list will necessarily be available.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
If the campaign you chose doesn't include the aircraft you want to fly, you
|
|
||||||
can mod the campaign by following the squadron section of the
|
|
||||||
<a style="color: #ffffff"
|
|
||||||
href="https://github.com/dcs-liberation/dcs_liberation/wiki/Custom-Campaigns#squadron-configuration">
|
|
||||||
custom campaigns guide</a>.
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
{% for aircraft in faction.aircrafts | sort(attribute="name") %}
|
|
||||||
<li>{{aircraft.name}}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<strong>Frontlines vehicles:</strong>
|
|
||||||
<ul>
|
|
||||||
{% for vehicle in faction.frontline_units | sort(attribute="name") %}
|
|
||||||
<li>
|
|
||||||
{% if vehicle.name is not none %}
|
|
||||||
{{ vehicle.name }}
|
|
||||||
{% else %}
|
|
||||||
{{ vehicle.id }}
|
|
||||||
{%endif %}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<strong>Artillery units:</strong>
|
|
||||||
<ul>
|
|
||||||
{% for arty in faction.artillery_units | sort(attribute="name") %}
|
|
||||||
<li>
|
|
||||||
{% if arty.name is not none %}
|
|
||||||
{{ arty.name }}
|
|
||||||
{% else %}
|
|
||||||
{{ arty.id }}
|
|
||||||
{%endif %}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<strong>Air defenses:</strong>
|
|
||||||
<ul>
|
|
||||||
{% for air_defense in faction.air_defenses | sort() %}
|
|
||||||
<li>{{air_defense}}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
<br/>
|
|
||||||
|
|||||||
@ -1,50 +1,4 @@
|
|||||||
{{ faction.description|safe }}
|
<strong>Description:</strong> {{ faction.description|safe }}
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<strong>Auteur(s):</strong> {{ faction.authors }}
|
<strong>Auteur(s):</strong> {{ faction.authors }}
|
||||||
<br/><br/>
|
|
||||||
|
|
||||||
|
|
||||||
<strong>Aéronefs disponibles :</strong>
|
|
||||||
<ul>
|
|
||||||
{% for aircraft in faction.aircrafts | sort(attribute="name") %}
|
|
||||||
<li>{{aircraft.name}}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<strong>Véhicules disponibles :</strong>
|
|
||||||
<ul>
|
|
||||||
{% for vehicle in faction.frontline_units | sort(attribute="name") %}
|
|
||||||
<li>
|
|
||||||
{% if vehicle.name is not none %}
|
|
||||||
{{ vehicle.name }}
|
|
||||||
{% else %}
|
|
||||||
{{ vehicle.id }}
|
|
||||||
{%endif %}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<strong>Pièces d'artillerie :</strong>
|
|
||||||
<ul>
|
|
||||||
{% for arty in faction.artillery_units | sort(attribute="name") %}
|
|
||||||
<li>
|
|
||||||
{% if arty.name is not none %}
|
|
||||||
{{ arty.name }}
|
|
||||||
{% else %}
|
|
||||||
{{ arty.id }}
|
|
||||||
{%endif %}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<strong>Défense Sol-Air:</strong>
|
|
||||||
<ul>
|
|
||||||
{% for air_defense in faction.air_defenses | sort() %}
|
|
||||||
<li>{{air_defense}}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
<br/>
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user