From 126a8e8efbf5f4cc26c2f95fc912ffd2efb669d8 Mon Sep 17 00:00:00 2001 From: Khopa Date: Sun, 6 Jun 2021 18:44:29 +0200 Subject: [PATCH 01/15] Added a small WW2 campaign on Normandy map (Replacing the former Normandy Small campaign). --- resources/campaigns/caen_to_evreux.json | 11 +++++++++++ resources/campaigns/caen_to_evreux.miz | Bin 0 -> 20133 bytes resources/campaigns/normandy_small.json | 10 ---------- 3 files changed, 11 insertions(+), 10 deletions(-) create mode 100644 resources/campaigns/caen_to_evreux.json create mode 100644 resources/campaigns/caen_to_evreux.miz delete mode 100644 resources/campaigns/normandy_small.json diff --git a/resources/campaigns/caen_to_evreux.json b/resources/campaigns/caen_to_evreux.json new file mode 100644 index 00000000..5776979e --- /dev/null +++ b/resources/campaigns/caen_to_evreux.json @@ -0,0 +1,11 @@ +{ + "name": "Normandy - From Caen to Evreux", + "theater": "Normandy", + "authors": "Khopa", + "recommended_player_faction": "Allies 1944", + "recommended_enemy_faction": "Germany 1944", + "description": "

This is a light scenario on the Normandy map.

August 1944, allied forces are pushing from Caen/Carpiquet to the cities of Lisieux and Evreux.

Lisieux is an important logistic hub for the Werhmacht, and Evreux airbase is hosting most of the Luftwaffe forces in the region.

", + "miz": "caen_to_evreux.miz", + "performance": 1, + "version": "6.0" +} diff --git a/resources/campaigns/caen_to_evreux.miz b/resources/campaigns/caen_to_evreux.miz new file mode 100644 index 0000000000000000000000000000000000000000..a0f321abd8ea72c22b4ff78018a490e35a5308d3 GIT binary patch literal 20133 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 literal 0 HcmV?d00001 diff --git a/resources/campaigns/normandy_small.json b/resources/campaigns/normandy_small.json deleted file mode 100644 index d09e9a77..00000000 --- a/resources/campaigns/normandy_small.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "Normandy - Normandy Small", - "theater": "Normandy", - "authors": "Khopa", - "recommended_player_faction": "Allies 1944", - "recommended_enemy_faction": "Germany 1944", - "description": "

A lighter version of the Normandy 1944 D-Day scenario.

", - "miz": "normandy_small.miz", - "performance": 2 -} \ No newline at end of file From 40832bd3a14139a19aa36a34229fc83a7bfc90d4 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 6 Jun 2021 13:37:01 -0700 Subject: [PATCH 02/15] Update screenshot on the front page. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7c487395..52433bab 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ DCS Liberation is a [DCS World](https://www.digitalcombatsimulator.com/en/products/world/) turn based single-player or co-op dynamic campaign. It is an external program that generates full and complex DCS missions and manage a persistent combat environment. -![Logo](https://i.imgur.com/4hq0rLq.png) +![Screenshot](https://user-images.githubusercontent.com/315852/120939254-0b4a9f80-c6cc-11eb-82f5-ce3f8d714bfe.png) ## Downloads From 45869c428e3b8200dd8b5967b552aa224c4840ce Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 6 Jun 2021 13:43:34 -0700 Subject: [PATCH 03/15] Label the player checkbox in the roster editor. --- qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py b/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py index ec4530c2..ab4899d2 100644 --- a/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py +++ b/qt_ui/windows/mission/flight/settings/QFlightSlotEditor.py @@ -90,7 +90,7 @@ class PilotControls(QHBoxLayout): self.selector.currentIndexChanged.connect(self.on_pilot_changed) self.addWidget(self.selector) - self.player_checkbox = QCheckBox() + self.player_checkbox = QCheckBox(text="Player") self.player_checkbox.setToolTip("Checked if this pilot is a player.") self.on_pilot_changed(self.selector.currentIndex()) self.addWidget(self.player_checkbox) From d8881e27344a67d1766f0fb9aeaabf6f8c7c7763 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 6 Jun 2021 17:12:44 -0700 Subject: [PATCH 04/15] Fix hangar status display. --- qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py index ce0b9e2a..74afe47e 100644 --- a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py @@ -167,7 +167,7 @@ class QHangarStatus(QHBoxLayout): next_turn = self.control_point.allocated_aircraft(self.game_model.game) max_amount = self.control_point.total_aircraft_parking - components = [f"{next_turn.present} present"] + components = [f"{next_turn.total_present} present"] if next_turn.total_ordered > 0: components.append(f"{next_turn.total_ordered} purchased") elif next_turn.total_ordered < 0: From 37bb83dfa61cc9949795140a9a7a17bbd861a7aa Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 7 Jun 2021 17:48:03 -0700 Subject: [PATCH 05/15] Delete a bunch of unused code from `Base`. --- game/theater/base.py | 108 ------------------------------------------- 1 file changed, 108 deletions(-) diff --git a/game/theater/base.py b/game/theater/base.py index 27390b2d..c7b154bb 100644 --- a/game/theater/base.py +++ b/game/theater/base.py @@ -26,7 +26,6 @@ class Base: self.armor: Dict[Type[VehicleType], int] = {} # TODO: Appears unused. self.aa: Dict[AirDefence, int] = {} - self.commision_points: Dict[Type, float] = {} self.strength = 1 @property @@ -79,61 +78,6 @@ class Base: self.aircraft.items(), self.armor.items(), self.aa.items() ) - def _find_best_unit( - self, available_units: Dict[UnitType, int], for_type: Task, count: int - ) -> Dict[UnitType, int]: - if count <= 0: - logging.warning("{}: no units for {}".format(self, for_type)) - return {} - - sorted_units = [ - key for key in available_units if key in db.UNIT_BY_TASK[for_type] - ] - sorted_units.sort(key=lambda x: db.PRICES[x], reverse=True) - - result: Dict[UnitType, int] = {} - for unit_type in sorted_units: - existing_count = available_units[unit_type] # type: int - if not existing_count: - continue - - if count <= 0: - break - - result_unit_count = min(count, existing_count) - count -= result_unit_count - - assert result_unit_count > 0 - result[unit_type] = result.get(unit_type, 0) + result_unit_count - - logging.info("{} for {} ({}): {}".format(self, for_type, count, result)) - return result - - def _find_best_planes( - self, for_type: Task, count: int - ) -> typing.Dict[FlyingType, int]: - return self._find_best_unit(self.aircraft, for_type, count) - - def _find_best_armor(self, for_type: Task, count: int) -> typing.Dict[Armor, int]: - return self._find_best_unit(self.armor, for_type, count) - - def append_commision_points(self, for_type, points: float) -> int: - self.commision_points[for_type] = ( - self.commision_points.get(for_type, 0) + points - ) - points = self.commision_points[for_type] - if points >= 1: - self.commision_points[for_type] = points - math.floor(points) - return int(math.floor(points)) - - return 0 - - def filter_units(self, applicable_units: typing.Collection): - self.aircraft = { - k: v for k, v in self.aircraft.items() if k in applicable_units - } - self.armor = {k: v for k, v in self.armor.items() if k in applicable_units} - def commision_units(self, units: typing.Dict[typing.Any, int]): for unit_type, unit_count in units.items(): @@ -190,55 +134,3 @@ class Base: def set_strength_to_minimum(self) -> None: self.strength = BASE_MIN_STRENGTH - - def scramble_count(self, multiplier: float, task: Task = None) -> int: - if task: - count = sum( - [v for k, v in self.aircraft.items() if db.unit_task(k) == task] - ) - else: - count = self.total_aircraft - - count = int(math.ceil(count * PLANES_SCRAMBLE_FACTOR * self.strength)) - return min( - min( - max(count, PLANES_SCRAMBLE_MIN_BASE), - int(PLANES_SCRAMBLE_MAX_BASE * multiplier), - ), - count, - ) - - def assemble_count(self): - return int(self.total_armor * 0.5) - - def assemble_aa_count(self) -> int: - # previous logic removed because we always want the full air defense capabilities. - return self.total_aa - - def scramble_sweep(self, multiplier: float) -> typing.Dict[FlyingType, int]: - return self._find_best_planes(CAP, self.scramble_count(multiplier, CAP)) - - def scramble_last_defense(self): - # return as many CAP-capable aircraft as we can since this is the last defense of the base - # (but not more than 20 - that's just nuts) - return self._find_best_planes(CAP, min(self.total_aircraft, 20)) - - def scramble_cas(self, multiplier: float) -> typing.Dict[FlyingType, int]: - return self._find_best_planes(CAS, self.scramble_count(multiplier, CAS)) - - def scramble_interceptors(self, multiplier: float) -> typing.Dict[FlyingType, int]: - return self._find_best_planes(CAP, self.scramble_count(multiplier, CAP)) - - def assemble_attack(self) -> typing.Dict[Armor, int]: - return self._find_best_armor(PinpointStrike, self.assemble_count()) - - def assemble_defense(self) -> typing.Dict[Armor, int]: - count = int(self.total_armor * min(self.strength + 0.5, 1)) - return self._find_best_armor(PinpointStrike, count) - - def assemble_aa(self, count=None) -> typing.Dict[AirDefence, int]: - return self._find_best_unit( - self.aa, - AirDefence, - count and min(count, self.total_aa) or self.assemble_aa_count(), - ) From 914691eaa7e2fe0e99f50097abeef04b6b21bc68 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 7 Jun 2021 17:49:55 -0700 Subject: [PATCH 06/15] Remove more unused code from Base. --- game/db.py | 1 - game/theater/base.py | 41 ++++------------------------------------- 2 files changed, 4 insertions(+), 38 deletions(-) diff --git a/game/db.py b/game/db.py index d4142bcc..1eb48d06 100644 --- a/game/db.py +++ b/game/db.py @@ -1060,7 +1060,6 @@ UNIT_BY_TASK = { frenchpack.DIM__TOYOTA_DESERT, frenchpack.DIM__KAMIKAZE, ], - AirDefence: [], Reconnaissance: [ Unarmed.Truck_M818_6x6, Unarmed.Truck_Ural_375, diff --git a/game/theater/base.py b/game/theater/base.py index c7b154bb..946940a8 100644 --- a/game/theater/base.py +++ b/game/theater/base.py @@ -1,21 +1,15 @@ import itertools import logging -import math import typing from typing import Dict, Type -from dcs.task import AWACS, CAP, CAS, Embarking, PinpointStrike, Task, Transport -from dcs.unittype import FlyingType, UnitType, VehicleType -from dcs.vehicles import AirDefence, Armor +from dcs.task import AWACS, CAP, CAS, Embarking, PinpointStrike, Transport +from dcs.unittype import FlyingType, VehicleType +from dcs.vehicles import AirDefence from game import db from game.db import PRICES -STRENGTH_AA_ASSEMBLE_MIN = 0.2 -PLANES_SCRAMBLE_MIN_BASE = 2 -PLANES_SCRAMBLE_MAX_BASE = 8 -PLANES_SCRAMBLE_FACTOR = 0.3 - BASE_MAX_STRENGTH = 1 BASE_MIN_STRENGTH = 0 @@ -24,8 +18,6 @@ class Base: def __init__(self): self.aircraft: Dict[Type[FlyingType], int] = {} self.armor: Dict[Type[VehicleType], int] = {} - # TODO: Appears unused. - self.aa: Dict[AirDefence, int] = {} self.strength = 1 @property @@ -46,38 +38,15 @@ class Base: logging.exception(f"No price found for {unit_type.id}") return total - @property - def total_aa(self) -> int: - return sum(self.aa.values()) - - def total_units(self, task: Task) -> int: - return sum( - [ - c - for t, c in itertools.chain( - self.aircraft.items(), self.armor.items(), self.aa.items() - ) - if t in db.UNIT_BY_TASK[task] - ] - ) - def total_units_of_type(self, unit_type) -> int: return sum( [ c - for t, c in itertools.chain( - self.aircraft.items(), self.armor.items(), self.aa.items() - ) + for t, c in itertools.chain(self.aircraft.items(), self.armor.items()) if t == unit_type ] ) - @property - def all_units(self): - return itertools.chain( - self.aircraft.items(), self.armor.items(), self.aa.items() - ) - def commision_units(self, units: typing.Dict[typing.Any, int]): for unit_type, unit_count in units.items(): @@ -97,8 +66,6 @@ class Base: target_dict = self.aircraft elif for_task == PinpointStrike: target_dict = self.armor - elif for_task == AirDefence: - target_dict = self.aa if target_dict is not None: target_dict[unit_type] = target_dict.get(unit_type, 0) + unit_count From ef35ad90b8833d743282df5590e6130d89248eb5 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 7 Jun 2021 18:00:57 -0700 Subject: [PATCH 07/15] Remove one user of UNIT_BY_TASK. --- game/db.py | 12 ------------ game/event/event.py | 2 +- game/theater/base.py | 34 ++++++++++++---------------------- game/theater/controlpoint.py | 4 ++-- game/transfers.py | 4 ++-- game/unitdelivery.py | 4 ++-- 6 files changed, 19 insertions(+), 41 deletions(-) diff --git a/game/db.py b/game/db.py index 1eb48d06..22ab6079 100644 --- a/game/db.py +++ b/game/db.py @@ -1331,18 +1331,6 @@ def upgrade_to_supercarrier(unit, name: str): return unit -def unit_task(unit: UnitType) -> Optional[Task]: - for task, units in UNIT_BY_TASK.items(): - if unit in units: - return task - - if unit in SAM_CONVERT: - return unit_task(SAM_CONVERT[unit]) - - print(unit.name + " cause issue") - return None - - def find_unittype(for_task: Type[MainTask], country_name: str) -> List[Type[UnitType]]: return [x for x in UNIT_BY_TASK[for_task] if x in FACTIONS[country_name].units] diff --git a/game/event/event.py b/game/event/event.py index ae1c3a4a..df63d34b 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -434,7 +434,7 @@ class Event: moved_units[frontline_unit] = int(count * move_factor) total_units_redeployed = total_units_redeployed + int(count * move_factor) - destination.base.commision_units(moved_units) + destination.base.commission_units(moved_units) source.base.commit_losses(moved_units) # Also transfer pending deliveries. diff --git a/game/theater/base.py b/game/theater/base.py index 946940a8..15d14f85 100644 --- a/game/theater/base.py +++ b/game/theater/base.py @@ -3,11 +3,8 @@ import logging import typing from typing import Dict, Type -from dcs.task import AWACS, CAP, CAS, Embarking, PinpointStrike, Transport -from dcs.unittype import FlyingType, VehicleType -from dcs.vehicles import AirDefence +from dcs.unittype import FlyingType, VehicleType, UnitType -from game import db from game.db import PRICES BASE_MAX_STRENGTH = 1 @@ -47,30 +44,23 @@ class Base: ] ) - def commision_units(self, units: typing.Dict[typing.Any, int]): - + def commission_units(self, units: typing.Dict[typing.Type[UnitType], int]): for unit_type, unit_count in units.items(): if unit_count <= 0: continue - for_task = db.unit_task(unit_type) - - target_dict = None - if ( - for_task == AWACS - or for_task == CAS - or for_task == CAP - or for_task == Embarking - or for_task == Transport - ): - target_dict = self.aircraft - elif for_task == PinpointStrike: + if issubclass(unit_type, VehicleType): target_dict = self.armor - - if target_dict is not None: - target_dict[unit_type] = target_dict.get(unit_type, 0) + unit_count + elif issubclass(unit_type, FlyingType): + target_dict = self.aircraft else: - logging.error("Unable to determine target dict for " + str(unit_type)) + logging.error( + f"Unexpected unit type of {unit_type}: " + f"{unit_type.__module__}.{unit_type.__name__}" + ) + return + + target_dict[unit_type] = target_dict.get(unit_type, 0) + unit_count def commit_losses(self, units_lost: typing.Dict[typing.Any, int]): diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 85afd44f..8cc6d56c 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -541,7 +541,7 @@ class ControlPoint(MissionTarget, ABC): while self.base.armor: unit_type, count = self.base.armor.popitem() for _ in range(count): - destination.control_point.base.commision_units({unit_type: 1}) + destination.control_point.base.commission_units({unit_type: 1}) destination = heapq.heappushpop(destinations, destination) def capture_aircraft( @@ -589,7 +589,7 @@ class ControlPoint(MissionTarget, ABC): return parking = destination.unclaimed_parking(game) transfer_amount = min([parking, count]) - destination.base.commision_units({airframe: transfer_amount}) + destination.base.commission_units({airframe: transfer_amount}) count -= transfer_amount def retreat_air_units(self, game: Game) -> None: diff --git a/game/transfers.py b/game/transfers.py index 988c1e84..8d26113a 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -109,7 +109,7 @@ class TransferOrder: def disband_at(self, location: ControlPoint) -> None: logging.info(f"Units halting at {location}.") - location.base.commision_units(self.units) + location.base.commission_units(self.units) self.units.clear() @property @@ -562,7 +562,7 @@ class PendingTransfers: if transfer.transport is not None: self.cancel_transport(transfer.transport, transfer) self.pending_transfers.remove(transfer) - transfer.origin.base.commision_units(transfer.units) + transfer.origin.base.commission_units(transfer.units) def perform_transfers(self) -> None: incomplete = [] diff --git a/game/unitdelivery.py b/game/unitdelivery.py index 752d4719..fd250825 100644 --- a/game/unitdelivery.py +++ b/game/unitdelivery.py @@ -104,11 +104,11 @@ class PendingUnitDeliveries: game.message(f"{coalition} sold: {name} x {-count} at {source}") self.units = defaultdict(int) - self.destination.base.commision_units(bought_units) + self.destination.base.commission_units(bought_units) self.destination.base.commit_losses(sold_units) if units_needing_transfer: - ground_unit_source.base.commision_units(units_needing_transfer) + ground_unit_source.base.commission_units(units_needing_transfer) self.create_transfer(game, ground_unit_source, units_needing_transfer) def create_transfer( From ea15421308142e7956a0e29c6c023f7c48aa1fe2 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 7 Jun 2021 18:52:54 -0700 Subject: [PATCH 08/15] Migrate support AEW&C away from find_unittype. --- gen/airsupportgen.py | 78 ++++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/gen/airsupportgen.py b/gen/airsupportgen.py index 3b39a37b..68bf6fe3 100644 --- a/gen/airsupportgen.py +++ b/gen/airsupportgen.py @@ -16,6 +16,7 @@ from dcs.task import ( ) from game import db +from .flights.ai_flight_planner_db import AEWC_CAPABLE from .naming import namegen from .callsigns import callsign_for_support_unit from .conflictgen import Conflict @@ -178,41 +179,46 @@ class AirSupportConflictGenerator: ) if not self.game.settings.disable_legacy_aewc: - possible_awacs = db.find_unittype(AWACS, self.conflict.attackers_side) + possible_awacs = [ + a + for a in self.game.faction_for(player=True).aircrafts + if a in AEWC_CAPABLE + ] - if len(possible_awacs) > 0: - awacs_unit = possible_awacs[0] - freq = self.radio_registry.alloc_uhf() - - awacs_flight = self.mission.awacs_flight( - country=self.mission.country(self.game.player_country), - name=namegen.next_awacs_name( - self.mission.country(self.game.player_country) - ), - plane_type=awacs_unit, - altitude=AWACS_ALT, - airport=None, - position=self.conflict.position.random_point_within( - AWACS_DISTANCE, AWACS_DISTANCE - ), - frequency=freq.mhz, - start_type=StartType.Warm, - ) - awacs_flight.set_frequency(freq.mhz) - - awacs_flight.points[0].tasks.append(SetInvisibleCommand(True)) - awacs_flight.points[0].tasks.append(SetImmortalCommand(True)) - - self.air_support.awacs.append( - AwacsInfo( - group_name=str(awacs_flight.name), - callsign=callsign_for_support_unit(awacs_flight), - freq=freq, - depature_location=None, - start_time=None, - end_time=None, - blue=True, - ) - ) - else: + if not possible_awacs: logging.warning("No AWACS for faction") + return + + awacs_unit = possible_awacs[0] + freq = self.radio_registry.alloc_uhf() + + awacs_flight = self.mission.awacs_flight( + country=self.mission.country(self.game.player_country), + name=namegen.next_awacs_name( + self.mission.country(self.game.player_country) + ), + plane_type=awacs_unit, + altitude=AWACS_ALT, + airport=None, + position=self.conflict.position.random_point_within( + AWACS_DISTANCE, AWACS_DISTANCE + ), + frequency=freq.mhz, + start_type=StartType.Warm, + ) + awacs_flight.set_frequency(freq.mhz) + + awacs_flight.points[0].tasks.append(SetInvisibleCommand(True)) + awacs_flight.points[0].tasks.append(SetImmortalCommand(True)) + + self.air_support.awacs.append( + AwacsInfo( + group_name=str(awacs_flight.name), + callsign=callsign_for_support_unit(awacs_flight), + freq=freq, + depature_location=None, + start_time=None, + end_time=None, + blue=True, + ) + ) From 27b5f24a0f5f9598d94c826a3a0c44a7489c512f Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 7 Jun 2021 18:54:21 -0700 Subject: [PATCH 09/15] Move unit purchase off find_unittype. --- game/db.py | 2 +- game/factions/faction.py | 10 ++++-- .../windows/basemenu/NewUnitTransferDialog.py | 4 +-- .../ground_forces/QArmorRecruitmentMenu.py | 35 +++++++------------ 4 files changed, 23 insertions(+), 28 deletions(-) diff --git a/game/db.py b/game/db.py index 22ab6079..fdc5dc50 100644 --- a/game/db.py +++ b/game/db.py @@ -1332,7 +1332,7 @@ def upgrade_to_supercarrier(unit, name: str): def find_unittype(for_task: Type[MainTask], country_name: str) -> List[Type[UnitType]]: - return [x for x in UNIT_BY_TASK[for_task] if x in FACTIONS[country_name].units] + return [x for x in UNIT_BY_TASK[for_task] if x in FACTIONS[country_name].all_units] MANPADS: List[Type[VehicleType]] = [ diff --git a/game/factions/faction.py b/game/factions/faction.py index ef06ef0d..d17da280 100644 --- a/game/factions/faction.py +++ b/game/factions/faction.py @@ -3,7 +3,7 @@ from game.data.groundunitclass import GroundUnitClass import logging from dataclasses import dataclass, field -from typing import Optional, Dict, Type, List, Any, cast +from typing import Optional, Dict, Type, List, Any, cast, Iterator import dcs from dcs.countries import country_dict @@ -240,7 +240,7 @@ class Faction: return faction @property - def units(self) -> List[Type[UnitType]]: + def all_units(self) -> List[Type[UnitType]]: return ( self.infantry_units + self.aircrafts @@ -251,6 +251,12 @@ class Faction: + self.logistics_units ) + @property + def ground_units(self) -> Iterator[Type[VehicleType]]: + yield from self.artillery_units + yield from self.frontline_units + yield from self.logistics_units + def unit_loader(unit: str, class_repository: List[Any]) -> Optional[Type[UnitType]]: """ diff --git a/qt_ui/windows/basemenu/NewUnitTransferDialog.py b/qt_ui/windows/basemenu/NewUnitTransferDialog.py index 5b58cc4f..57288d5f 100644 --- a/qt_ui/windows/basemenu/NewUnitTransferDialog.py +++ b/qt_ui/windows/basemenu/NewUnitTransferDialog.py @@ -166,9 +166,7 @@ class ScrollingUnitTransferGrid(QFrame): scroll_content = QWidget() task_box_layout = QGridLayout() - unit_types = set( - db.find_unittype(PinpointStrike, self.game_model.game.player_name) - ) + unit_types = set(self.game_model.game.faction_for(player=True).ground_units) sorted_units = sorted( {u for u in unit_types if self.cp.base.total_units_of_type(u)}, key=lambda u: db.unit_get_expanded_info( diff --git a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py index 0a421a6a..3f104e7d 100644 --- a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py @@ -7,10 +7,8 @@ from PySide2.QtWidgets import ( QScrollArea, QVBoxLayout, QWidget, - QMessageBox, ) -from dcs.task import PinpointStrike -from dcs.unittype import FlyingType, UnitType +from dcs.unittype import UnitType from game import db from game.theater import ControlPoint @@ -32,31 +30,24 @@ class QArmorRecruitmentMenu(QFrame, QRecruitBehaviour): def init_ui(self): main_layout = QVBoxLayout() - units = { - PinpointStrike: db.find_unittype( - PinpointStrike, self.game_model.game.player_name - ), - } - scroll_content = QWidget() task_box_layout = QGridLayout() scroll_content.setLayout(task_box_layout) row = 0 - for task_type in units.keys(): - units_column = list(set(units[task_type])) - if len(units_column) == 0: - continue - units_column.sort( - key=lambda u: db.unit_get_expanded_info( - self.game_model.game.player_country, u, "name" - ) + unit_types = list( + set(self.game_model.game.faction_for(player=True).ground_units) + ) + unit_types.sort( + key=lambda u: db.unit_get_expanded_info( + self.game_model.game.player_country, u, "name" ) - for unit_type in units_column: - row = self.add_purchase_row(unit_type, task_box_layout, row) - stretch = QVBoxLayout() - stretch.addStretch() - task_box_layout.addLayout(stretch, row, 0) + ) + for unit_type in unit_types: + row = self.add_purchase_row(unit_type, task_box_layout, row) + stretch = QVBoxLayout() + stretch.addStretch() + task_box_layout.addLayout(stretch, row, 0) scroll_content.setLayout(task_box_layout) scroll = QScrollArea() From 6966c16dd23b4c7333450e0bd0c1dd2413dd5130 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 7 Jun 2021 18:54:39 -0700 Subject: [PATCH 10/15] Remove dead code. --- game/db.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/game/db.py b/game/db.py index fdc5dc50..9d50ff3a 100644 --- a/game/db.py +++ b/game/db.py @@ -1474,23 +1474,6 @@ def task_name(task) -> str: return task.name -def choose_units( - for_task: Task, factor: float, count: int, country: str -) -> List[UnitType]: - suitable_unittypes = find_unittype(for_task, country) - suitable_unittypes = [ - x for x in suitable_unittypes if x not in helicopter_map.values() - ] - suitable_unittypes.sort(key=lambda x: PRICES[x]) - - idx = int(len(suitable_unittypes) * factor) - variety = int(count + count * factor / 2) - - index_start = min(idx, len(suitable_unittypes) - variety) - index_end = min(idx + variety, len(suitable_unittypes)) - return list(set(suitable_unittypes[index_start:index_end])) - - def unitdict_append(unit_dict: UnitsDict, unit_type: UnitType, count: int): unit_dict[unit_type] = unit_dict.get(unit_type, 0) + 1 From ae2a818d8c3cef7cfc7d93866c23a8514e5f8ca6 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 7 Jun 2021 18:54:51 -0700 Subject: [PATCH 11/15] Move the base intel menu off find_unittype. --- qt_ui/windows/basemenu/intel/QIntelInfo.py | 68 ++++++++++------------ 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/qt_ui/windows/basemenu/intel/QIntelInfo.py b/qt_ui/windows/basemenu/intel/QIntelInfo.py index ea5dfa4c..cc9c3ca6 100644 --- a/qt_ui/windows/basemenu/intel/QIntelInfo.py +++ b/qt_ui/windows/basemenu/intel/QIntelInfo.py @@ -1,3 +1,6 @@ +from collections import defaultdict + +from PySide2.QtCore import Qt from PySide2.QtWidgets import ( QFrame, QGridLayout, @@ -7,8 +10,6 @@ from PySide2.QtWidgets import ( QScrollArea, QWidget, ) -from PySide2.QtCore import Qt -from dcs.task import CAP, CAS, Embarking, PinpointStrike from game import Game, db from game.theater import ControlPoint @@ -19,51 +20,44 @@ class QIntelInfo(QFrame): super(QIntelInfo, self).__init__() self.cp = cp self.game = game - self.init_ui() - def init_ui(self): layout = QVBoxLayout() scroll_content = QWidget() - intelLayout = QVBoxLayout() + intel_layout = QVBoxLayout() - units = { - CAP: db.find_unittype(CAP, self.game.enemy_name), - Embarking: db.find_unittype(Embarking, self.game.enemy_name), - CAS: db.find_unittype(CAS, self.game.enemy_name), - PinpointStrike: db.find_unittype(PinpointStrike, self.game.enemy_name), + units_by_task: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int)) + for unit_type, count in self.cp.base.aircraft.items(): + if count: + name = db.unit_get_expanded_info( + self.game.enemy_country, unit_type, "name" + ) + units_by_task[unit_type.task_default.name][name] += count + + units_by_task = { + task: units_by_task[task] for task in sorted(units_by_task.keys()) } - for task_type in units.keys(): - units_column = list(set(units[task_type])) + front_line_units = defaultdict(int) + for unit_type, count in self.cp.base.armor.items(): + if count: + name = db.unit_get_expanded_info( + self.game.enemy_country, unit_type, "name" + ) + front_line_units[name] += count - if sum([self.cp.base.total_units_of_type(u) for u in units_column]) > 0: + units_by_task["Front line units"] = front_line_units + for task, unit_types in units_by_task.items(): + task_group = QGroupBox(task) + task_layout = QGridLayout() + task_group.setLayout(task_layout) - group = QGroupBox(db.task_name(task_type)) - groupLayout = QGridLayout() - group.setLayout(groupLayout) + for row, (name, count) in enumerate(unit_types.items()): + task_layout.addWidget(QLabel(f"{name}"), row, 0) + task_layout.addWidget(QLabel(str(count)), row, 1) - row = 0 - for unit_type in units_column: - existing_units = self.cp.base.total_units_of_type(unit_type) - if existing_units == 0: - continue - groupLayout.addWidget( - QLabel( - "" - + db.unit_get_expanded_info( - self.game.enemy_country, unit_type, "name" - ) - + "" - ), - row, - 0, - ) - groupLayout.addWidget(QLabel(str(existing_units)), row, 1) - row += 1 + intel_layout.addWidget(task_group) - intelLayout.addWidget(group) - - scroll_content.setLayout(intelLayout) + scroll_content.setLayout(intel_layout) scroll = QScrollArea() scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) From fd85efbf55db0528cef3e58bb405ae92b0a8dfe2 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 7 Jun 2021 18:59:02 -0700 Subject: [PATCH 12/15] Remove more dead code in game.db. --- game/db.py | 202 +---------------------------------------------------- 1 file changed, 2 insertions(+), 200 deletions(-) diff --git a/game/db.py b/game/db.py index 9d50ff3a..117cb01a 100644 --- a/game/db.py +++ b/game/db.py @@ -2,7 +2,7 @@ import json from datetime import datetime from enum import Enum from pathlib import Path -from typing import Dict, List, Optional, Tuple, Type, Union +from typing import List, Optional, Type, Union from dcs.countries import country_dict from dcs.helicopters import ( @@ -11,7 +11,6 @@ from dcs.helicopters import ( AH_64D, CH_47D, CH_53E, - HelicopterType, Ka_50, Mi_24V, Mi_26, @@ -132,29 +131,21 @@ from dcs.ships import ( ) from dcs.task import ( AWACS, - AntishipStrike, CAP, CAS, CargoTransportation, Embarking, - Escort, - FighterSweep, - GroundAttack, - Intercept, MainTask, Nothing, PinpointStrike, Reconnaissance, Refueling, - SEAD, - Task, Transport, - RunwayAttack, ) from dcs.terrain.terrain import Airport from dcs.unit import Ship, Unit, Vehicle from dcs.unitgroup import ShipGroup, StaticGroup -from dcs.unittype import FlyingType, ShipType, UnitType, VehicleType +from dcs.unittype import FlyingType, UnitType, VehicleType from dcs.vehicles import ( AirDefence, Armor, @@ -1083,108 +1074,12 @@ UNIT_BY_TASK = { ], } -""" -Units from AirDefense category of UNIT_BY_TASK that will be removed from use if "No SAM" option is checked at the start of the game -""" -SAM_BAN = [ - AirDefence.SAM_Linebacker___Bradley_M6, - AirDefence.SAM_SA_9_Strela_1_Gaskin_TEL, - AirDefence.SAM_SA_8_Osa_Gecko_TEL, - AirDefence.SAM_SA_19_Tunguska_Grison, - AirDefence.SAM_SA_6_Kub_Gainful_TEL, - AirDefence.SAM_SA_8_Osa_Gecko_TEL, - AirDefence.SAM_SA_3_S_125_Goa_LN, - AirDefence.SAM_Hawk_Platoon_Command_Post__PCP, - AirDefence.SAM_SA_2_S_75_Guideline_LN, - AirDefence.SAM_SA_11_Buk_Gadfly_Fire_Dome_TEL, -] - -""" -Used to convert SAM site parts to the corresponding site -""" -SAM_CONVERT = { - AirDefence.SAM_P19_Flat_Face_SR__SA_2_3: AirDefence.SAM_SA_3_S_125_Goa_LN, - AirDefence.SAM_SA_3_S_125_Low_Blow_TR: AirDefence.SAM_SA_3_S_125_Goa_LN, - AirDefence.SAM_SA_3_S_125_Goa_LN: AirDefence.SAM_SA_3_S_125_Goa_LN, - AirDefence.SAM_SA_6_Kub_Gainful_TEL: AirDefence.SAM_SA_6_Kub_Gainful_TEL, - AirDefence.SAM_SA_6_Kub_Straight_Flush_STR: AirDefence.SAM_SA_6_Kub_Gainful_TEL, - AirDefence.SAM_SA_10_S_300_Grumble_TEL_C: AirDefence.SAM_SA_10_S_300_Grumble_TEL_C, - AirDefence.SAM_SA_10_S_300_Grumble_Clam_Shell_SR: AirDefence.SAM_SA_10_S_300_Grumble_TEL_C, - AirDefence.SAM_SA_10_S_300_Grumble_Flap_Lid_TR: AirDefence.SAM_SA_10_S_300_Grumble_TEL_C, - AirDefence.SAM_SA_10_S_300_Grumble_C2: AirDefence.SAM_SA_10_S_300_Grumble_TEL_C, - AirDefence.SAM_SA_10_S_300_Grumble_Big_Bird_SR: AirDefence.SAM_SA_10_S_300_Grumble_C2, - AirDefence.SAM_Hawk_TR__AN_MPQ_46: AirDefence.SAM_Hawk_Platoon_Command_Post__PCP, - AirDefence.SAM_Hawk_SR__AN_MPQ_50: AirDefence.SAM_Hawk_Platoon_Command_Post__PCP, - AirDefence.SAM_Hawk_LN_M192: AirDefence.SAM_Hawk_Platoon_Command_Post__PCP, - "except": { - # this radar is shared between the two S300's. if we attempt to find a SAM site at a base and can't find one - # model, we can safely assume the other was deployed - # well, perhaps not safely, but we'll make the assumption anyway :p - AirDefence.SAM_SA_10_S_300_Grumble_Flap_Lid_TR: AirDefence.SAM_SA_10_S_300_Grumble_C2, - AirDefence.SAM_P19_Flat_Face_SR__SA_2_3: AirDefence.SAM_SA_2_S_75_Guideline_LN, - }, -} - -""" -Units that will always be spawned in the air -""" -TAKEOFF_BAN: List[Type[FlyingType]] = [] - -""" -Units that will be always spawned in the air if launched from the carrier -""" -CARRIER_TAKEOFF_BAN: List[Type[FlyingType]] = [ - Su_33, # Kuznecow is bugged in a way that only 2 aircraft could be spawned -] - """ Units separated by country. country : DCS Country name """ FACTIONS = FactionLoader() -CARRIER_TYPE_BY_PLANE = { - FA_18C_hornet: CVN_74_John_C__Stennis, - F_14A_135_GR: CVN_74_John_C__Stennis, - F_14B: CVN_74_John_C__Stennis, - Ka_50: LHA_1_Tarawa, - SA342M: LHA_1_Tarawa, - UH_1H: LHA_1_Tarawa, - Mi_8MT: LHA_1_Tarawa, - AV8BNA: LHA_1_Tarawa, -} - -""" -Aircraft payload overrides. Usually default loadout for the task is loaded during the mission generation. -Syntax goes as follows: - - `AircraftIdentifier`: { - "Category": "PayloadName", - }, - -where: - * `AircraftIdentifier`: identifier of aircraft (the same that is used troughout the file) - * "Category": (in double quotes) is one of the tasks: CAS, CAP, Intercept, Escort or "*" - * "PayloadName": payload as found in resources/payloads/UNIT_TYPE.lua file. Sometimes this will match payload names - in the mission editor, sometimes it doesn't - -Payload will be used for operation of following type, "*" category will be used always, no matter the operation. -""" - -COMMON_OVERRIDE = { - CAP: "CAP", - Intercept: "CAP", - CAS: "CAS", - PinpointStrike: "STRIKE", - SEAD: "SEAD", - AntishipStrike: "ANTISHIP", - GroundAttack: "STRIKE", - Escort: "CAP", - RunwayAttack: "RUNWAY_ATTACK", - FighterSweep: "CAP", - AWACS: "AEW&C", -} - """ Aircraft livery overrides. Syntax as follows: @@ -1298,16 +1193,6 @@ LHA_CAPABLE = [ ---------- END OF CONFIGURATION SECTION """ -UnitsDict = Dict[UnitType, int] -PlaneDict = Dict[FlyingType, int] -HeliDict = Dict[HelicopterType, int] -ArmorDict = Dict[VehicleType, int] -ShipDict = Dict[ShipType, int] -AirDefenseDict = Dict[AirDefence, int] - -AssignedUnitsDict = Dict[Type[UnitType], Tuple[int, int]] -TaskForceDict = Dict[Type[MainTask], AssignedUnitsDict] - StartingPosition = Union[ShipGroup, StaticGroup, Airport, Point] @@ -1463,89 +1348,6 @@ def unit_type_of(unit: Unit) -> UnitType: return unit.type -def task_name(task) -> str: - if task == AirDefence: - return "AirDefence" - elif task == Embarking: - return "Transportation" - elif task == PinpointStrike: - return "Frontline units" - else: - return task.name - - -def unitdict_append(unit_dict: UnitsDict, unit_type: UnitType, count: int): - unit_dict[unit_type] = unit_dict.get(unit_type, 0) + 1 - - -def unitdict_merge(a: UnitsDict, b: UnitsDict) -> UnitsDict: - b = b.copy() - for k, v in a.items(): - b[k] = b.get(k, 0) + v - - return b - - -def unitdict_split(unit_dict: UnitsDict, count: int): - buffer_dict: Dict[UnitType, int] = {} - for unit_type, unit_count in unit_dict.items(): - for _ in range(unit_count): - unitdict_append(buffer_dict, unit_type, 1) - if sum(buffer_dict.values()) >= count: - yield buffer_dict - buffer_dict = {} - - if len(buffer_dict): - yield buffer_dict - - -def unitdict_restrict_count(unit_dict: UnitsDict, total_count: int) -> UnitsDict: - if total_count == 0: - return {} - - groups = list(unitdict_split(unit_dict, total_count)) - if len(groups) > 0: - return groups[0] - else: - return {} - - -def assigned_units_split(fd: AssignedUnitsDict) -> Tuple[PlaneDict, PlaneDict]: - return ( - {k: v1 for k, (v1, v2) in fd.items()}, - {k: v2 for k, (v1, v2) in fd.items()}, - ) - - -def assigned_units_from(d: PlaneDict) -> AssignedUnitsDict: - return {k: (v, 0) for k, v in d.items()} - - -def assignedunits_split_to_count(dict: AssignedUnitsDict, count: int): - buffer_dict: Dict[Type[UnitType], Tuple[int, int]] = {} - for unit_type, (unit_count, client_count) in dict.items(): - for _ in range(unit_count): - new_count, new_client_count = buffer_dict.get(unit_type, (0, 0)) - - new_count += 1 - - if client_count > 0: - new_client_count += 1 - client_count -= 1 - - buffer_dict[unit_type] = new_count, new_client_count - if new_count >= count: - yield buffer_dict - buffer_dict = {} - - if len(buffer_dict): - yield buffer_dict - - -def unitdict_from(fd: AssignedUnitsDict) -> Dict: - return {k: v1 for k, (v1, v2) in fd.items()} - - def country_id_from_name(name): for k, v in country_dict.items(): if v.name == name: From 622a171ac4ff42de483ae68f0dcc565b7da06298 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 7 Jun 2021 19:01:16 -0700 Subject: [PATCH 13/15] Move armor TGO purchase off find_unittype. --- qt_ui/windows/groundobject/QGroundObjectMenu.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/qt_ui/windows/groundobject/QGroundObjectMenu.py b/qt_ui/windows/groundobject/QGroundObjectMenu.py index 9df4b454..23c6e3ba 100644 --- a/qt_ui/windows/groundobject/QGroundObjectMenu.py +++ b/qt_ui/windows/groundobject/QGroundObjectMenu.py @@ -340,11 +340,7 @@ class QBuyGroupForGroundObjectDialog(QDialog): buy_ewr_layout.addLayout(stretch, 2, 0) # Armored units - - armored_units = db.find_unittype( - PinpointStrike, faction.name - ) # Todo : refactor this legacy nonsense - for unit in set(armored_units): + for unit in set(faction.ground_units): self.buyArmorCombo.addItem( db.unit_type_name_2(unit) + " [$" + str(db.PRICES[unit]) + "M]", userData=unit, From 07cdfc16d06501aa5015b9fc32f031da835eb07a Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 7 Jun 2021 19:12:30 -0700 Subject: [PATCH 14/15] Move support tanker off find_unittype. --- gen/airsupportgen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gen/airsupportgen.py b/gen/airsupportgen.py index 68bf6fe3..4943d0cf 100644 --- a/gen/airsupportgen.py +++ b/gen/airsupportgen.py @@ -103,7 +103,7 @@ class AirSupportConflictGenerator: fallback_tanker_number = 0 for i, tanker_unit_type in enumerate( - db.find_unittype(Refueling, self.conflict.attackers_side) + self.game.faction_for(player=True).tankers ): alt, airspeed = self._get_tanker_params(tanker_unit_type) variant = db.unit_type_name(tanker_unit_type) From 0a9dc49e7f061bd9b64a4748d63dbc096dbbd58b Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 7 Jun 2021 19:13:49 -0700 Subject: [PATCH 15/15] Remove UNIT_BY_TASK. --- game/db.py | 381 ----------------------------------------------------- 1 file changed, 381 deletions(-) diff --git a/game/db.py b/game/db.py index 117cb01a..9d5c4edf 100644 --- a/game/db.py +++ b/game/db.py @@ -715,365 +715,6 @@ PRICES = { highdigitsams.SAM_SA_17_Buk_M1_2_LN_9A310M1_2: 40, } -""" -Units separated by tasks. This will include units for both countries. Be advised that unit could only belong to single task! - -Following tasks are present: -* CAP - figther aircraft for CAP/Escort/Intercept -* CAS - CAS aircraft -* Transport - transport aircraft (used as targets in intercept operations) -* AWACS - awacs -* AntishipStrike - units that will engage shipping -* PinpointStrike - armor that will engage in ground war -* AirDefense - AA units -* Reconnaissance - units that will be used as targets in destroy insurgents operations -* Nothing - troops that will be used for helicopter transport operations -* Embarking - helicopters that will be used for helicopter transport operations -* Carriage - aircraft carriers -* CargoTransportation - ships that will be used as targets for ship intercept operations -""" -UNIT_BY_TASK = { - CAP: [ - A_4E_C, - Bf_109K_4, - C_101CC, - FA_18C_hornet, - FW_190A8, - FW_190D9, - F_14A_135_GR, - F_14B, - F_15C, - F_16A, - F_16C_50, - F_22A, - F_4E, - F_5E_3, - I_16, - JF_17, - J_11A, - M_2000C, - MiG_19P, - MiG_21Bis, - MiG_23MLD, - MiG_25PD, - MiG_29A, - MiG_29G, - MiG_29S, - MiG_31, - Mirage_2000_5, - P_51D, - P_51D_30_NA, - SA342Mistral, - SpitfireLFMkIX, - SpitfireLFMkIXCW, - Su_27, - Su_30, - Su_33, - Su_57, - ], - CAS: [ - AH_1W, - AH_64A, - AH_64D, - AJS37, - AV8BNA, - A_10A, - A_10C, - A_10C_2, - A_20G, - B_17G, - B_1B, - B_52H, - F_117A, - F_15E, - F_86F_Sabre, - Hercules, - Ju_88A4, - Ka_50, - L_39ZA, - MB_339PAN, - MQ_9_Reaper, - MiG_15bis, - MiG_27K, - Mi_24V, - Mi_28N, - Mi_8MT, - OH_58D, - P_47D_30, - P_47D_30bl1, - P_47D_40, - RQ_1A_Predator, - SA342L, - SA342M, - SA342Minigun, - SH_60B, - S_3B, - Su_17M4, - Su_24M, - Su_24MR, - Su_25, - Su_25T, - Su_34, - Tornado_GR4, - Tornado_IDS, - Tu_160, - Tu_22M3, - Tu_95MS, - UH_1H, - WingLoong_I, - ], - Transport: [ - An_26B, - An_30M, - CH_47D, - CH_53E, - C_130, - C_17A, - IL_76MD, - Mi_26, - UH_60A, - Yak_40, - ], - Refueling: [ - IL_78M, - KC130, - KC135MPRS, - KC_135, - S_3B_Tanker, - ], - AWACS: [ - A_50, - E_2C, - E_3A, - KJ_2000, - ], - PinpointStrike: [ - Armor.APC_MTLB, - Armor.APC_MTLB, - Armor.APC_MTLB, - Armor.APC_MTLB, - Armor.APC_MTLB, - Artillery.Grad_MRL_FDDM__FC, - Artillery.Grad_MRL_FDDM__FC, - Artillery.Grad_MRL_FDDM__FC, - Artillery.Grad_MRL_FDDM__FC, - Artillery.Grad_MRL_FDDM__FC, - Armor.Scout_BRDM_2, - Armor.Scout_BRDM_2, - Armor.Scout_BRDM_2, - Armor.APC_BTR_RD, - Armor.APC_BTR_RD, - Armor.APC_BTR_RD, - Armor.APC_BTR_RD, - Armor.APC_BTR_80, - Armor.APC_BTR_80, - Armor.APC_BTR_80, - Armor.APC_BTR_80, - Armor.APC_BTR_80, - Armor.IFV_BTR_82A, - Armor.IFV_BTR_82A, - Armor.IFV_BMP_1, - Armor.IFV_BMP_1, - Armor.IFV_BMP_1, - Armor.IFV_BMP_2, - Armor.IFV_BMP_2, - Armor.IFV_BMP_3, - Armor.IFV_BMP_3, - Armor.IFV_BMD_1, - Armor.LT_PT_76, - Armor.ZBD_04A, - Armor.ZBD_04A, - Armor.ZBD_04A, - Armor.MBT_T_55, - Armor.MBT_T_55, - Armor.MBT_T_55, - Armor.MBT_T_72B, - Armor.MBT_T_72B, - Armor.MBT_T_72B3, - Armor.MBT_T_72B3, - Armor.MBT_T_80U, - Armor.MBT_T_80U, - Armor.MBT_T_90, - Armor.ZTZ_96B, - Armor.Scout_Cobra, - Armor.Scout_Cobra, - Armor.Scout_Cobra, - Armor.Scout_Cobra, - Armor.APC_M113, - Armor.APC_M113, - Armor.APC_M113, - Armor.APC_M113, - Armor.APC_TPz_Fuchs, - Armor.APC_TPz_Fuchs, - Armor.APC_TPz_Fuchs, - Armor.APC_TPz_Fuchs, - Armor.ATGM_HMMWV, - Armor.ATGM_HMMWV, - Armor.ATGM_VAB_Mephisto, - Armor.ATGM_VAB_Mephisto, - Armor.Scout_HMMWV, - Armor.Scout_HMMWV, - Armor.IFV_M2A2_Bradley, - Armor.IFV_M2A2_Bradley, - Armor.ATGM_Stryker, - Armor.ATGM_Stryker, - Armor.IFV_M1126_Stryker_ICV, - Armor.IFV_M1126_Stryker_ICV, - Armor.IFV_M1126_Stryker_ICV, - Armor.SPG_Stryker_MGS, - Armor.IFV_Warrior, - Armor.IFV_Warrior, - Armor.IFV_Warrior, - Armor.IFV_LAV_25, - Armor.IFV_LAV_25, - Armor.IFV_Marder, - Armor.IFV_Marder, - Armor.IFV_Marder, - Armor.IFV_Marder, - Armor.MBT_M60A3_Patton, - Armor.MBT_M60A3_Patton, - Armor.MBT_M60A3_Patton, - Armor.MBT_Leopard_1A3, - Armor.MBT_Leopard_1A3, - Armor.MBT_M1A2_Abrams, - Armor.MBT_Leclerc, - Armor.MBT_Leopard_2A6M, - Armor.MBT_Challenger_II, - Armor.MBT_Chieftain_Mk_3, - Armor.MBT_Merkava_IV, - Armor.MT_Pz_Kpfw_V_Panther_Ausf_G, - Armor.Tk_PzIV_H, - Armor.HT_Pz_Kpfw_VI_Tiger_I, - Armor.HT_Pz_Kpfw_VI_Ausf__B_Tiger_II, - Armor.APC_Sd_Kfz_251_Halftrack, - Armor.APC_Sd_Kfz_251_Halftrack, - Armor.APC_Sd_Kfz_251_Halftrack, - Armor.APC_Sd_Kfz_251_Halftrack, - Armor.IFV_Sd_Kfz_234_2_Puma, - Armor.IFV_Sd_Kfz_234_2_Puma, - Armor.Tk_M4_Sherman, - Armor.MT_M4A4_Sherman_Firefly, - Armor.CT_Cromwell_IV, - Unarmed.Carrier_M30_Cargo, - Unarmed.Carrier_M30_Cargo, - Armor.APC_M2A1_Halftrack, - Armor.APC_M2A1_Halftrack, - Armor.APC_M2A1_Halftrack, - Armor.APC_M2A1_Halftrack, - Armor.MT_Pz_Kpfw_V_Panther_Ausf_G, - Armor.Tk_PzIV_H, - Armor.HT_Pz_Kpfw_VI_Tiger_I, - Armor.HT_Pz_Kpfw_VI_Ausf__B_Tiger_II, - Armor.SPG_Jagdpanther_G1, - Armor.SPG_Jagdpanzer_IV, - Armor.SPG_Sd_Kfz_184_Elefant, - Armor.APC_Sd_Kfz_251_Halftrack, - Armor.IFV_Sd_Kfz_234_2_Puma, - Armor.Tk_M4_Sherman, - Armor.MT_M4A4_Sherman_Firefly, - Armor.CT_Cromwell_IV, - Unarmed.Carrier_M30_Cargo, - Unarmed.Carrier_M30_Cargo, - Unarmed.Carrier_M30_Cargo, - Armor.APC_M2A1_Halftrack, - Armor.APC_M2A1_Halftrack, - Armor.CT_Centaur_IV, - Armor.CT_Centaur_IV, - Armor.HIT_Churchill_VII, - Armor.Car_M8_Greyhound_Armored, - Armor.Car_M8_Greyhound_Armored, - Armor.SPG_M10_GMC, - Armor.SPG_M10_GMC, - Armor.SPG_StuG_III_Ausf__G, - Armor.SPG_StuG_IV, - Artillery.SPG_M12_GMC_155mm, - Armor.SPG_Sturmpanzer_IV_Brummbar, - Armor.Car_Daimler_Armored, - Armor.LT_Mk_VII_Tetrarch, - Artillery.MLRS_M270_227mm, - Artillery.SPH_M109_Paladin_155mm, - Artillery.SPM_2S9_Nona_120mm_M, - Artillery.SPH_2S1_Gvozdika_122mm, - Artillery.SPH_2S3_Akatsia_152mm, - Artillery.SPH_2S19_Msta_152mm, - Artillery.MLRS_BM_21_Grad_122mm, - Artillery.MLRS_BM_21_Grad_122mm, - Artillery.MLRS_9K57_Uragan_BM_27_220mm, - Artillery.MLRS_9A52_Smerch_HE_300mm, - Artillery.SPH_Dana_vz77_152mm, - Artillery.SPH_T155_Firtina_155mm, - Artillery.PLZ_05, - Artillery.SPG_M12_GMC_155mm, - Armor.SPG_Sturmpanzer_IV_Brummbar, - AirDefence.SPAAA_ZU_23_2_Mounted_Ural_375, - AirDefence.SPAAA_ZU_23_2_Insurgent_Mounted_Ural_375, - AirDefence.SPAAA_ZSU_57_2, - AirDefence.SPAAA_ZSU_23_4_Shilka_Gun_Dish, - AirDefence.SAM_SA_8_Osa_Gecko_TEL, - AirDefence.SAM_SA_9_Strela_1_Gaskin_TEL, - AirDefence.SAM_SA_13_Strela_10M3_Gopher_TEL, - AirDefence.SAM_SA_15_Tor_Gauntlet, - AirDefence.SAM_SA_19_Tunguska_Grison, - AirDefence.SPAAA_Gepard, - AirDefence.SPAAA_Vulcan_M163, - AirDefence.SAM_Linebacker___Bradley_M6, - AirDefence.SAM_Chaparral_M48, - AirDefence.SAM_Avenger__Stinger, - AirDefence.SAM_Roland_ADS, - AirDefence.HQ_7_Self_Propelled_LN, - AirDefence.AAA_8_8cm_Flak_18, - AirDefence.AAA_8_8cm_Flak_36, - AirDefence.AAA_8_8cm_Flak_37, - AirDefence.AAA_8_8cm_Flak_41, - AirDefence.AAA_Bofors_40mm, - AirDefence.AAA_S_60_57mm, - AirDefence.AAA_M1_37mm, - AirDefence.AAA_QF_3_7, - frenchpack.DIM__TOYOTA_BLUE, - frenchpack.DIM__TOYOTA_DESERT, - frenchpack.DIM__TOYOTA_GREEN, - frenchpack.DIM__KAMIKAZE, - frenchpack.AMX_10RCR, - frenchpack.AMX_10RCR_SEPAR, - frenchpack.ERC_90, - frenchpack.TRM_2000_PAMELA, - frenchpack.VAB__50, - frenchpack.VAB_MEPHISTO, - frenchpack.VAB_T20_13, - frenchpack.VBL__50, - frenchpack.VBL_AANF1, - frenchpack.VBAE_CRAB, - frenchpack.VBAE_CRAB_MMP, - frenchpack.AMX_30B2, - frenchpack.Leclerc_Serie_XXI, - frenchpack.DIM__TOYOTA_BLUE, - frenchpack.DIM__TOYOTA_GREEN, - frenchpack.DIM__TOYOTA_DESERT, - frenchpack.DIM__KAMIKAZE, - ], - Reconnaissance: [ - Unarmed.Truck_M818_6x6, - Unarmed.Truck_Ural_375, - Unarmed.LUV_UAZ_469_Jeep, - ], - Nothing: [ - Infantry.Infantry_M4, - Infantry.Infantry_AK_74, - ], - Embarking: [], - Carriage: [ - CVN_74_John_C__Stennis, - LHA_1_Tarawa, - CV_1143_5_Admiral_Kuznetsov, - ], - CargoTransportation: [ - Cargo_Ivanov, - Bulker_Yakushev, - Tanker_Elnya_160, - Boat_Armed_Hi_speed, - ], -} - """ Units separated by country. country : DCS Country name @@ -1216,10 +857,6 @@ def upgrade_to_supercarrier(unit, name: str): return unit -def find_unittype(for_task: Type[MainTask], country_name: str) -> List[Type[UnitType]]: - return [x for x in UNIT_BY_TASK[for_task] if x in FACTIONS[country_name].all_units] - - MANPADS: List[Type[VehicleType]] = [ AirDefence.MANPADS_SA_18_Igla_Grouse, AirDefence.MANPADS_SA_18_Igla_S_Grouse, @@ -1355,24 +992,6 @@ def country_id_from_name(name): return -1 -def _validate_db(): - # check unit by task uniquity - total_set = set() - for t, unit_collection in UNIT_BY_TASK.items(): - for unit_type in set(unit_collection): - assert unit_type not in total_set, "{} is duplicate for task {}".format( - unit_type, t - ) - total_set.add(unit_type) - - # check prices - for unit_type in total_set: - assert unit_type in PRICES, "{} not in prices".format(unit_type) - - -_validate_db() - - class DefaultLiveries: class Default(Enum): af_standard = ""