Compare commits

...

45 Commits

Author SHA1 Message Date
Dan Albert
a0e5a707fb Merge pull request #1053 from Khopa/develop_2_5_x
Release 2.5.1.
2021-05-02 13:28:45 -07:00
Dan Albert
4555a4968d Update changelog for 2.5.1. 2021-05-02 13:17:35 -07:00
SnappyComebacks
ae34e4749b Move base EWRs into their own category.
Without this we're sometimes spawning base EWRs at points far outside the base perimeter.
2021-04-28 21:07:22 -07:00
Khopa
635eee9590 Fixed ai_flight_planner for maps lacking frontlines (such as battle of britain on The Channel map) 2021-04-24 00:11:53 +02:00
Khopa
f0558c4c1e Fixed ai_flight_planner for maps lacking frontlines (such as battle of britain on The Channel map) 2021-04-23 23:45:14 +02:00
Dan Albert
637ca8fbca Stop projecting threat zones from front lines.
This is an interim improvement since we should probably be pushing the
BARCAPs into TARCAP roles when the front line is so close. This does
regress flight pathing for anything that should route around the front
(to avoid getting shot at by SHORADS and TARCAPs), but for now it's one
or the other and this is the one everyone's complaining about.

(cherry picked from commit e474748f4d)
2021-04-22 18:23:12 -07:00
Dan Albert
e4e65df976 Generalize commit range display for all patrols.
Fixes https://github.com/Khopa/dcs_liberation/issues/890

(cherry picked from commit 132ba905c7)
2021-04-22 17:55:49 -07:00
Dan Albert
29579a2aec Remove missed merge conflict marker. 2021-04-22 17:49:34 -07:00
Dan Albert
e32b43cffb Show BARCAP commit ranges by default.
BARCAP placement confuses a lot of people but this should make it more
clear.

(cherry picked from commit 208d1b82b5)
2021-04-22 17:46:29 -07:00
C. Perreau
de2e5f861b Merge pull request #1007 from Khopa/develop_2_5_x
Release 2.5.0
2021-04-22 00:08:42 +02:00
Khopa
b27a7fc71b Fixed Lint issue 2021-04-21 22:54:48 +02:00
Khopa
5861ce6146 Fixed error with Ramat David frequency (typo) 2021-04-21 22:38:08 +02:00
Khopa
c732ed556f Fixed airfields frequency on Persian Gulf 2021-04-21 22:30:08 +02:00
Khopa
be1a75e520 Fixed airfields frequency on Syria 2021-04-21 22:14:18 +02:00
Khopa
c41d10c581 Pydcs update to latest version 2021-04-21 12:57:19 +02:00
Dan Albert
157a59e3c4 Fix UI crash when unchecking default loadout.
This was throwing because it was being called with the wrong number of
arguments, preventing the UI from actually updating back to the default.
2021-04-18 13:05:22 -07:00
Khopa
d24c65c3aa Fixed airfield data airport name for Persian Gulf map 2021-04-18 20:10:52 +02:00
Khopa
d4d441ff9b Fixed some factions errors that weren't caught yet. 2021-04-18 18:11:00 +02:00
Khopa
f43fb1223f Fix : Fixed duplicate units on cold war flak site. 2021-04-18 15:16:17 +02:00
Khopa
3db275414d Allow 0 income multiplier in game settings windows (this was already possible in new game wizard) 2021-04-18 01:20:32 +02:00
Khopa
6e0ff6c805 pydcs update 2021-04-18 01:13:50 +02:00
Dan Albert
9c359efbff Note Litening -> ATFLIR change. 2021-04-17 16:03:56 -07:00
Dan Albert
c5cc1ea8e8 Make the F/A-18C strike loadout less silly.
Instead of 4xMk83 and 4xGBU-38, 2 bags and 2 GBU-31. ATFLIR added for
TOO/BDA.
2021-04-17 15:51:46 -07:00
Dan Albert
afb6a33131 Replace Litening II with ATFLIR in Honet loadouts.
https://github.com/pydcs/dcs/pull/120
2021-04-17 15:43:49 -07:00
Khopa
539a11f54d Added icons for new units 2021-04-18 00:15:10 +02:00
Khopa
9324e549e6 Updated changelog 2021-04-18 00:13:43 +02:00
Khopa
c8f6b6df87 Fixed lint issue 2021-04-18 00:11:06 +02:00
Dan Albert
38f632097e Add support for DCS 2.7 weather generation.
https://github.com/Khopa/dcs_liberation/issues/981
2021-04-17 15:06:17 -07:00
Khopa
e63743f537 Improved FOB support : new custom banner for FOB menu and do not display aircrafts menu on first page. 2021-04-17 23:49:49 +02:00
Khopa
ce13295cf0 pydcs repo now pointing on temporary branch 2-7-temp on https://github.com/Khopa/dcs for new weather development 2021-04-17 23:06:48 +02:00
Khopa
23c02a3510 Updated airfields data for the Channel map 2021-04-17 17:50:41 +02:00
Khopa
01ea7b9ee1 Updated airfields metadata for Syria 2021-04-17 17:37:15 +02:00
Khopa
6fed1284a1 Updated airfields metadata for Syria 2021-04-17 17:35:40 +02:00
Khopa
5574d849bd Unit support : S-60 added to Syria faction 2021-04-17 13:11:58 +02:00
Khopa
c2ce3a6992 Fixed Lint issue 2021-04-17 13:11:26 +02:00
Khopa
b61d15fdf4 Unit support : Added support for the PLZ-05, new artillery unit from the Chinese Asset Pack 2021-04-17 11:28:36 +02:00
Khopa
ad5cc83fb3 Unit support : now using the new unit S-60 57mm AA Gun units. 2021-04-17 11:23:00 +02:00
Ronny Röhricht
2f53edd775 Add plugin for exporting RED and BLUE threat circles to LotATC.
Implemented as a plugin because LotATC needs actual lat/lon, and the only APIs for those are in lua.

Fixes https://github.com/Khopa/dcs_liberation/issues/956.
2021-04-17 00:55:06 -07:00
Khopa
923459c88b Pydcs update to the good commit reference 2021-04-17 02:35:34 +02:00
Khopa
1192d26448 Fixed lint issue 2021-04-17 02:27:42 +02:00
Khopa
2d5e827417 Pydcs update to master repo 2021-04-17 02:26:31 +02:00
Khopa
a30d9276b8 Merge remote-tracking branch 'khopa/develop' into develop 2021-04-17 02:22:56 +02:00
Dan Albert
0cd088122e Remove WIP status of AEW&C missions. 2021-04-15 21:35:25 -07:00
Dan Albert
b6f3467a89 Update changelog. 2021-04-15 21:34:31 -07:00
SnappyComebacks
52ce1a5959 Add support for additional EWR sites in campaigns.
* A Bluefor EWR 55GS in the campaign miz defines an optional EWR site. There is no distinction between how close or far it is to a base, so it's possible that there will be many EWRs within an airbase.
* A Redfor EWR 1L13 in the campaign miz defines a required EWR site.

It would be a good future idea to limit the amount of EWRs within a certain distance from an airbase. That way there's no chance of 5 EWRs all at the same airbase. Even better if there were something preventing any two EWRs from being right next to each other.

No campaigns take advantage of this yet.

Fixes https://github.com/Khopa/dcs_liberation/issues/524
2021-04-15 21:23:27 -07:00
56 changed files with 1026 additions and 341 deletions

1
.gitignore vendored
View File

@@ -11,6 +11,7 @@ a.py
resources/tools/a.miz
# User-specific stuff
.idea/
.env
/kneeboards
/liberation_preferences.json

View File

@@ -1,28 +1,51 @@
# 2.5.1
## Features/Improvements
* **[UI]** Engagement ranges are now displayed by default.
* **[UI]** Engagement range display generalized to work for all patrolling flight plans (BARCAP, TARCAP, and CAS).
* **[Flight Planner]** Front lines no longer project threat zones to avoid pushing BARCAPs back so much. TARCAPs will be forcibly planned but strike packages will not route around front lines even if it is reasonable to do so.
## Fixes
* **[Campaigns]** EWRs associated with a base will now only be generated near the base.
* **[Flight Planner]** Fixed error when generating AEW&C flight plans in campaigns with no front lines.
# 2.5.0
Saves from 2.4 are not compatible with 2.5.
## Features/Improvements
* **[Flight Planner]** (WIP) Added AEW&C missions. (by siKruger)
* **[Kneeboard]** Added dark kneeboard option (by GvonH)
* **[Engine]** DCS 2.7 Support
* **[UI]** Improved FOB menu, added a custom banner, and do not display aircraft recruitment menu
* **[Flight Planner]** Added AEW&C missions. (by siKruger)
* **[Kneeboard]** Added dark kneeboard option (by GvonH)
* **[Campaigns]** Multiple EWR sites may now be generated, and EWR sites may be generated outside bases (by SnappyComebacks)
* **[Mission Generation]** Cloudy and rainy (but not thunderstorm) weather will use the cloud presets from DCS 2.7.
* **[Plugins]** Added LotATC export plugin (by drsoran)
* **[Plugins]** Added Splash Damage Plugin (by Wheelijoe)
* **[Loadouts]** Replaced Litening with ATFLIR for all default F/A-18C loadouts.
## Fixes
* **[Flight Planner]** Front lines now project threat zones, so TARCAP/escorts will not be pruned for flights near the front. Packages may also route around the front line when practical.
* **[Flight Planner]** Fixed error when planning BAI at SAMs with dead subgroups.
* **[Flight Planner]** Mig-19 was not allowed for CAS roles fixed
* **[Flight Planner]** Increased size of navigation planning area to avoid plannign failures with distant waypoints.
* **[Flight Planner]** Fixed UI refresh when unchecking the "default loadout" box in the loadout editor.
* **[Objective names]** Fixed typos in objective name : ARMADILLLO -> ARMADILLO (by SnappyComebacks)
* **[Payloads]** F-86 Sabre was missing a custom payload
* **[Payloads]** Added GAR-8 period restrictions (by Mustang-25)
* **[Campaign]** Date now progresses.
# 2.4.4
## Fixes
* **[Campaign]** Added game over message when a coalition runs out of functioning airbases.
* **[Mission Generation]** Fixed "invalid face handle" error in kneeboard generation that occurred on some machines.
## Regressions
* **[Mod Support]** Stopped support for 2.5.5 Rafale Mode, and removed factions that were using it
* **[Mod Support]** Su-57 mod support might be out of date
# 2.4.3
## Features/Improvements

View File

@@ -18,4 +18,5 @@ AAA_UNITS = [
AirDefence.AAA_SP_Kdo_G_40,
AirDefence.AAA_8_8cm_Flak_41,
AirDefence.AAA_40mm_Bofors,
AirDefence.AAA_S_60_57mm,
]

View File

@@ -523,6 +523,7 @@ PRICES = {
Artillery.MLRS_9A52_Smerch_HE_300mm: 40,
Artillery.Mortar_2B11_120mm: 4,
Artillery.SPH_Dana_vz77_152mm: 26,
Artillery.PLZ_05: 25,
Unarmed.LUV_UAZ_469_Jeep: 3,
Unarmed.Truck_Ural_375: 3,
Infantry.Infantry_M4: 1,
@@ -631,6 +632,7 @@ PRICES = {
AirDefence.AAA_8_8cm_Flak_41: 10,
AirDefence.EWR_FuMG_401_Freya_LZ: 25,
AirDefence.AAA_40mm_Bofors: 8,
AirDefence.AAA_S_60_57mm: 8,
AirDefence.AAA_M1_37mm: 7,
AirDefence.AAA_M45_Quadmount_HB_12_7mm: 4,
AirDefence.AAA_QF_3_7: 10,
@@ -974,6 +976,7 @@ UNIT_BY_TASK = {
Artillery.MLRS_BM_27_Uragan_220mm,
Artillery.MLRS_9A52_Smerch_HE_300mm,
Artillery.SPH_Dana_vz77_152mm,
Artillery.PLZ_05,
Artillery.SPG_M12_GMC_155mm,
Artillery.SPG_Sturmpanzer_IV_Brummbar,
AirDefence.SPAAA_ZU_23_2_Mounted_Ural_375,
@@ -997,6 +1000,7 @@ UNIT_BY_TASK = {
AirDefence.AAA_8_8cm_Flak_37,
AirDefence.AAA_8_8cm_Flak_41,
AirDefence.AAA_40mm_Bofors,
AirDefence.AAA_S_60_57mm,
AirDefence.AAA_M1_37mm,
AirDefence.AAA_QF_3_7,
frenchpack.DIM__TOYOTA_BLUE,

View File

@@ -446,6 +446,8 @@ class Operation:
"AWACs": {},
"JTACs": {},
"TargetPoints": {},
"RedAA": {},
"BlueAA": {},
} # type: ignore
for tanker in airsupportgen.air_support.tankers:
@@ -503,6 +505,26 @@ class Operation:
},
}
for cp in cls.game.theater.controlpoints:
for ground_object in cp.ground_objects:
if ground_object.might_have_aa and not ground_object.is_dead:
for g in ground_object.groups:
threat_range = ground_object.threat_range(g)
if not threat_range:
continue
faction = "BlueAA" if cp.captured else "RedAA"
luaData[faction][g.name] = {
"name": ground_object.name,
"range": threat_range.meters,
"position": {
"x": ground_object.position.x,
"y": ground_object.position.y,
},
}
# set a LUA table with data from Liberation that we want to set
# at the moment it contains Liberation's install path, and an overridable definition for the JTACAutoLase function
# later, we'll add data about the units and points having been generated, in order to facilitate the configuration of the plugin lua scripts
@@ -595,7 +617,33 @@ class Operation:
-- list the aircraft carriers generated by Liberation
-- dcsLiberation.Carriers = {}
-- later, we'll add more data to the table
-- list the Red AA generated by Liberation
dcsLiberation.RedAA = {
"""
for key in luaData["RedAA"]:
data = luaData["RedAA"][key]
name = data["name"]
radius = data["range"]
positionX = data["position"]["x"]
positionY = data["position"]["y"]
lua += f" {{dcsGroupName='{key}', name='{name}', range='{radius}', positionX='{positionX}', positionY='{positionY}' }}, \n"
lua += "}"
lua += """
-- list the Blue AA generated by Liberation
dcsLiberation.BlueAA = {
"""
for key in luaData["BlueAA"]:
data = luaData["BlueAA"][key]
name = data["name"]
radius = data["range"]
positionX = data["position"]["x"]
positionY = data["position"]["y"]
lua += f" {{dcsGroupName='{key}', name='{name}', range='{radius}', positionX='{positionX}', positionY='{positionY}' }}, \n"
lua += "}"
lua += """
"""

View File

@@ -118,6 +118,8 @@ class MizCampaignLoader:
AirDefence.SAM_SA_3_S_125_Goa_LN.id,
}
REQUIRED_EWR_UNIT_TYPE = AirDefence.EWR_1L13.id
BASE_DEFENSE_RADIUS = nautical_miles(2)
def __init__(self, miz: Path, theater: ConflictTheater) -> None:
@@ -247,6 +249,12 @@ class MizCampaignLoader:
if group.units[0].type in self.REQUIRED_MEDIUM_RANGE_SAM_UNIT_TYPES:
yield group
@property
def required_ewrs(self) -> Iterator[VehicleGroup]:
for group in self.red.vehicle_group:
if group.units[0].type in self.REQUIRED_EWR_UNIT_TYPE:
yield group
@property
def helipads(self) -> Iterator[StaticGroup]:
for group in self.blue.static_group:
@@ -356,9 +364,14 @@ class MizCampaignLoader:
for group in self.ewrs:
closest, distance = self.objective_info(group)
closest.preset_locations.ewrs.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
if distance < self.BASE_DEFENSE_RADIUS:
closest.preset_locations.base_ewrs.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
else:
closest.preset_locations.ewrs.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
for group in self.offshore_strike_targets:
closest, distance = self.objective_info(group)
@@ -396,6 +409,12 @@ class MizCampaignLoader:
PointWithHeading.from_point(group.position, group.units[0].heading)
)
for group in self.required_ewrs:
closest, distance = self.objective_info(group)
closest.preset_locations.required_ewrs.append(
PointWithHeading.from_point(group.position, group.units[0].heading)
)
for group in self.helipads:
closest, distance = self.objective_info(group)
closest.helipads.append(

View File

@@ -63,6 +63,7 @@ class LocationType(Enum):
BaseAirDefense = "base air defense"
Coastal = "coastal defense"
Ewr = "EWR"
BaseEwr = "Base EWR"
Garrison = "garrison"
MissileSite = "missile site"
OffshoreStrikeTarget = "offshore strike target"
@@ -86,6 +87,9 @@ class PresetLocations:
#: Locations used by EWRs.
ewrs: List[PointWithHeading] = field(default_factory=list)
#: Locations used by Base EWRs.
base_ewrs: List[PointWithHeading] = field(default_factory=list)
#: Locations used by non-carrier ships. Carriers and LHAs are not random.
ships: List[PointWithHeading] = field(default_factory=list)
@@ -107,6 +111,9 @@ class PresetLocations:
#: Locations of medium range SAMs which should always be spawned.
required_medium_range_sams: List[PointWithHeading] = field(default_factory=list)
#: Locations of EWRs which should always be spawned.
required_ewrs: List[PointWithHeading] = field(default_factory=list)
@staticmethod
def _random_from(points: List[PointWithHeading]) -> Optional[PointWithHeading]:
"""Finds, removes, and returns a random position from the given list."""
@@ -128,6 +135,8 @@ class PresetLocations:
return self._random_from(self.coastal_defenses)
if location_type == LocationType.Ewr:
return self._random_from(self.ewrs)
if location_type == LocationType.BaseEwr:
return self._random_from(self.base_ewrs)
if location_type == LocationType.Garrison:
return self._random_from(self.base_garrisons)
if location_type == LocationType.MissileSite:
@@ -389,7 +398,7 @@ class ControlPoint(MissionTarget, ABC):
for base_defense in self.base_defenses:
p = PointWithHeading.from_point(base_defense.position, base_defense.heading)
if isinstance(base_defense, EwrGroundObject):
self.preset_locations.ewrs.append(p)
self.preset_locations.base_ewrs.append(p)
elif isinstance(base_defense, SamGroundObject):
self.preset_locations.base_air_defense.append(p)
elif isinstance(base_defense, VehicleGroupGroundObject):

View File

@@ -37,10 +37,8 @@ from gen.fleet.ship_group_generator import (
from gen.locations.preset_location_finder import MizDataLocationFinder
from gen.missiles.missiles_group_generator import generate_missile_group
from gen.sam.airdefensegroupgenerator import AirDefenseRange
from gen.sam.sam_group_generator import (
generate_anti_air_group,
generate_ewr_group,
)
from gen.sam.sam_group_generator import generate_anti_air_group
from gen.sam.ewr_group_generator import generate_ewr_group
from . import (
ConflictTheater,
ControlPoint,
@@ -457,14 +455,18 @@ class BaseDefenseGenerator:
self.generate_base_defenses()
def generate_ewr(self) -> None:
position = self.location_finder.location_for(LocationType.Ewr)
position = self.location_finder.location_for(LocationType.BaseEwr)
if position is None:
return
group_id = self.game.next_group_id()
g = EwrGroundObject(
namegen.random_objective_name(), group_id, position, self.control_point
namegen.random_objective_name(),
group_id,
position,
self.control_point,
True,
)
group = generate_ewr_group(self.game, g, self.faction)
@@ -609,6 +611,7 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
def generate_ground_points(self) -> None:
"""Generate ground objects and AA sites for the control point."""
skip_sams = self.generate_required_aa()
skip_ewrs = self.generate_required_ewr()
if self.control_point.is_global:
return
@@ -625,6 +628,12 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
skip_sams -= 1
else:
self.generate_aa_site()
# 1 in 4 additional objectives are EWR.
elif random.randint(0, 3) == 0:
if skip_ewrs > 0:
skip_ewrs -= 1
else:
self.generate_ewr_site()
else:
self.generate_ground_point()
@@ -656,6 +665,17 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
presets.required_medium_range_sams
)
def generate_required_ewr(self) -> int:
"""Generates the EWR sites that are required by the campaign.
Returns:
The number of EWR sites that were generated.
"""
presets = self.control_point.preset_locations
for position in presets.required_ewrs:
self.generate_ewr_at(position)
return len(presets.required_ewrs)
def generate_ground_point(self) -> None:
try:
category = random.choice(self.faction.building_set)
@@ -733,6 +753,33 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
g.groups = groups
self.control_point.connected_objectives.append(g)
def generate_ewr_site(self) -> None:
position = self.location_finder.location_for(LocationType.Ewr)
if position is None:
return
self.generate_ewr_at(position)
def generate_ewr_at(self, position: Point) -> None:
group_id = self.game.next_group_id()
g = EwrGroundObject(
namegen.random_objective_name(),
group_id,
position,
self.control_point,
for_airbase=False,
)
group = generate_ewr_group(self.game, g, self.faction)
if group is None:
logging.error(
"Could not generate ewr group for %s at %s",
g.name,
self.control_point,
)
return
g.groups = [group]
self.control_point.connected_objectives.append(g)
def generate_missile_sites(self) -> None:
for i in range(self.faction.missiles_group_count):
self.generate_missile_site()

View File

@@ -442,7 +442,12 @@ class VehicleGroupGroundObject(BaseDefenseGroundObject):
class EwrGroundObject(BaseDefenseGroundObject):
def __init__(
self, name: str, group_id: int, position: Point, control_point: ControlPoint
self,
name: str,
group_id: int,
position: Point,
control_point: ControlPoint,
for_airbase: bool,
) -> None:
super().__init__(
name=name,
@@ -452,7 +457,7 @@ class EwrGroundObject(BaseDefenseGroundObject):
heading=0,
control_point=control_point,
dcs_identifier="EWR",
airbase_group=True,
airbase_group=for_airbase,
sea_object=False,
)

View File

@@ -152,23 +152,6 @@ class ThreatZones:
threat_zone = point.buffer(threat_range.meters)
air_defenses.append(threat_zone)
for front_line in game.theater.conflicts(player):
vector = Conflict.frontline_vector(
front_line.control_point_a, front_line.control_point_b, game.theater
)
start = vector[0]
end = vector[0].point_from_heading(vector[1], vector[2])
line = LineString(
[
ShapelyPoint(start.x, start.y),
ShapelyPoint(end.x, end.y),
]
)
doctrine = game.faction_for(player).doctrine
air_threats.append(line.buffer(doctrine.cap_engagement_range.meters))
return cls(
airbases=unary_union(air_threats), air_defenses=unary_union(air_defenses)
)

View File

@@ -3,11 +3,12 @@ from __future__ import annotations
import datetime
import logging
import random
from dataclasses import dataclass
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional, TYPE_CHECKING
from dcs.weather import Weather as PydcsWeather, Wind
from dcs.cloud_presets import Clouds as PydcsClouds
from dcs.weather import CloudPreset, Weather as PydcsWeather, Wind
from game.settings import Settings
from game.utils import Distance, meters
@@ -36,6 +37,23 @@ class Clouds:
density: int
thickness: int
precipitation: PydcsWeather.Preceptions
preset: Optional[CloudPreset] = field(default=None)
@classmethod
def random_preset(cls, rain: bool) -> Clouds:
clouds = (p.value for p in PydcsClouds)
if rain:
presets = [p for p in clouds if "Rain" in p.name]
else:
presets = [p for p in clouds if "Rain" not in p.name]
preset = random.choice(presets)
return Clouds(
base=random.randint(preset.min_base, preset.max_base),
density=0,
thickness=0,
precipitation=PydcsWeather.Preceptions.None_,
preset=preset,
)
@dataclass(frozen=True)
@@ -101,12 +119,11 @@ class ClearSkies(Weather):
class Cloudy(Weather):
def generate_clouds(self) -> Optional[Clouds]:
return Clouds(
base=self.random_cloud_base(),
density=random.randint(1, 8),
thickness=self.random_cloud_thickness(),
precipitation=PydcsWeather.Preceptions.None_,
)
return Clouds.random_preset(rain=False)
def generate_fog(self) -> Optional[Fog]:
# DCS 2.7 says to not use fog with the cloud presets.
return None
def generate_wind(self) -> WindConditions:
return self.random_wind(0, 4)
@@ -114,12 +131,11 @@ class Cloudy(Weather):
class Raining(Weather):
def generate_clouds(self) -> Optional[Clouds]:
return Clouds(
base=self.random_cloud_base(),
density=random.randint(5, 8),
thickness=self.random_cloud_thickness(),
precipitation=PydcsWeather.Preceptions.Rain,
)
return Clouds.random_preset(rain=True)
def generate_fog(self) -> Optional[Fog]:
# DCS 2.7 says to not use fog with the cloud presets.
return None
def generate_wind(self) -> WindConditions:
return self.random_wind(0, 6)

View File

@@ -383,8 +383,8 @@ AIRFIELD_DATA = {
"31": ("IVZ", MHz(108, 750)),
},
),
# TODO : PERSIAN GULF MAP
"Liwa Airbase": AirfieldData(
# PERSIAN GULF MAP
"Liwa AFB": AirfieldData(
theater="Persian Gulf",
icao="OMLW",
elevation=400,
@@ -394,7 +394,7 @@ AIRFIELD_DATA = {
vor=("OMLW", MHz(117, 400)),
atc=AtcData(MHz(4, 225), MHz(39, 350), MHz(119, 300), MHz(250, 950)),
),
"Al Dhafra AB": AirfieldData(
"Al Dhafra AFB": AirfieldData(
theater="Persian Gulf",
icao="OMAM",
elevation=52,
@@ -402,50 +402,50 @@ AIRFIELD_DATA = {
tacan=TacanChannel(96, TacanBand.X),
tacan_callsign="MA",
vor=("MA", MHz(114, 900)),
atc=AtcData(MHz(4, 250), MHz(39, 400), MHz(126, 500), MHz(251, 000)),
atc=AtcData(MHz(4, 300), MHz(39, 500), MHz(126, 500), MHz(251, 100)),
ils={
"13": ("MMA", MHz(111, 100)),
"31": ("IMA", MHz(109, 100)),
},
),
"Al-Bateen Airport": AirfieldData(
"Al-Bateen": AirfieldData(
theater="Persian Gulf",
icao="OMAD",
elevation=11,
runway_length=6808,
vor=("ALB", MHz(114, 0)),
atc=AtcData(MHz(4, 25), MHz(38, 950), MHz(119, 900), MHz(250, 550)),
atc=AtcData(MHz(4, 75), MHz(39, 50), MHz(119, 900), MHz(250, 600)),
),
"Sas Al Nakheel Airport": AirfieldData(
"Sas Al Nakheel": AirfieldData(
theater="Persian Gulf",
icao="OMNK",
elevation=9,
runway_length=5387,
vor=("SAS", MHz(128, 930)),
atc=AtcData(MHz(3, 975), MHz(38, 850), MHz(128, 900), MHz(250, 450)),
atc=AtcData(MHz(4, 0), MHz(38, 900), MHz(128, 900), MHz(250, 450)),
),
"Abu Dhabi International Airport": AirfieldData(
"Abu Dhabi Intl": AirfieldData(
theater="Persian Gulf",
icao="OMAA",
elevation=91,
runway_length=12817,
vor=("ADV", MHz(114, 250)),
atc=AtcData(MHz(4, 000), MHz(38, 900), MHz(119, 200), MHz(250, 500)),
atc=AtcData(MHz(4, 50), MHz(39, 0), MHz(119, 200), MHz(250, 550)),
),
"Al Ain International Airport": AirfieldData(
"Al Ain Intl": AirfieldData(
theater="Persian Gulf",
icao="OMAL",
elevation=813,
runway_length=11267,
vor=("ALN", MHz(112, 600)),
atc=AtcData(MHz(4, 75), MHz(39, 50), MHz(119, 850), MHz(250, 650)),
atc=AtcData(MHz(4, 125), MHz(39, 150), MHz(119, 850), MHz(250, 700)),
),
"Al Maktoum Intl": AirfieldData(
theater="Persian Gulf",
icao="OMDW",
elevation=123,
runway_length=11500,
atc=AtcData(MHz(4, 300), MHz(39, 500), MHz(118, 650), MHz(251, 100)),
atc=AtcData(MHz(4, 350), MHz(39, 600), MHz(118, 600), MHz(251, 200)),
ils={
"30": ("IJWA", MHz(109, 750)),
"12": ("IMA", MHz(111, 750)),
@@ -458,7 +458,7 @@ AIRFIELD_DATA = {
runway_length=11865,
tacan=TacanChannel(99, TacanBand.X),
tacan_callsign="MIN",
atc=AtcData(MHz(3, 800), MHz(38, 500), MHz(121, 800), MHz(250, 100)),
atc=AtcData(MHz(3, 800), MHz(38, 500), MHz(118, 550), MHz(250, 100)),
ils={
"27": ("IMNR", MHz(110, 750)),
"9": ("IMNW", MHz(110, 700)),
@@ -469,7 +469,7 @@ AIRFIELD_DATA = {
icao="OMDB",
elevation=16,
runway_length=11018,
atc=AtcData(MHz(4, 275), MHz(39, 450), MHz(118, 750), MHz(251, 50)),
atc=AtcData(MHz(4, 325), MHz(39, 550), MHz(118, 750), MHz(251, 150)),
ils={
"30": ("IDBL", MHz(110, 900)),
"12": ("IDBR", MHz(110, 100)),
@@ -480,7 +480,7 @@ AIRFIELD_DATA = {
icao="OMSJ",
elevation=98,
runway_length=10535,
atc=AtcData(MHz(3, 850), MHz(38, 600), MHz(118, 600), MHz(252, 200)),
atc=AtcData(MHz(3, 850), MHz(38, 600), MHz(118, 600), MHz(250, 200)),
ils={
"30": ("ISHW", MHz(111, 950)),
"12": ("ISRE", MHz(108, 550)),
@@ -492,18 +492,18 @@ AIRFIELD_DATA = {
elevation=60,
runway_length=9437,
vor=("FJV", MHz(113, 800)),
atc=AtcData(MHz(4, 325), MHz(39, 550), MHz(124, 600), MHz(251, 150)),
atc=AtcData(MHz(4, 375), MHz(39, 650), MHz(124, 600), MHz(251, 250)),
ils={
"29": ("IFJR", MHz(111, 500)),
},
),
"Ras AL Khaimah": AirfieldData(
"Ras Al Khaimah Intl": AirfieldData(
theater="Persian Gulf",
icao="OMRK",
elevation=70,
runway_length=8406,
vor=("OMRK", MHz(113, 600)),
atc=AtcData(MHz(4, 150), MHz(39, 200), MHz(121, 600), MHz(250, 800)),
atc=AtcData(MHz(4, 200), MHz(39, 300), MHz(121, 600), MHz(250, 900)),
),
"Khasab": AirfieldData(
theater="Persian Gulf",
@@ -516,7 +516,11 @@ AIRFIELD_DATA = {
},
),
"Sir Abu Nuayr": AirfieldData(
theater="Persian Gulf", icao="OMSN", elevation=25, runway_length=2229
theater="Persian Gulf",
icao="OMSN",
elevation=25,
runway_length=2229,
atc=AtcData(MHz(3, 900), MHz(38, 700), MHz(118, 0), MHz(250, 800)),
),
"Sirri Island": AirfieldData(
theater="Persian Gulf",
@@ -526,7 +530,7 @@ AIRFIELD_DATA = {
vor=("SIR", MHz(113, 750)),
atc=AtcData(MHz(3, 875), MHz(38, 650), MHz(135, 50), MHz(250, 250)),
),
"Abu Musa Island Airport": AirfieldData(
"Abu Musa Island": AirfieldData(
theater="Persian Gulf",
icao="OIBA",
elevation=16,
@@ -555,13 +559,13 @@ AIRFIELD_DATA = {
vor=("KHM", MHz(117, 100)),
atc=AtcData(MHz(3, 825), MHz(38, 550), MHz(118, 50), MHz(250, 150)),
),
"Bandar-e-Jask airfield": AirfieldData(
"Bandar-e-Jask": AirfieldData(
theater="Persian Gulf",
icao="OIZJ",
elevation=26,
runway_length=6842,
vor=("KHM", MHz(116, 300)),
atc=AtcData(MHz(3, 825), MHz(38, 550), MHz(118, 50), MHz(250, 150)),
atc=AtcData(MHz(4, 25), MHz(38, 950), MHz(118, 150), MHz(250, 500)),
),
"Bandar Lengeh": AirfieldData(
theater="Persian Gulf",
@@ -569,26 +573,26 @@ AIRFIELD_DATA = {
elevation=80,
runway_length=7625,
vor=("LEN", MHz(114, 800)),
atc=AtcData(MHz(4, 225), MHz(39, 350), MHz(121, 700), MHz(250, 950)),
atc=AtcData(MHz(4, 275), MHz(39, 450), MHz(121, 700), MHz(251, 50)),
),
"Kish International Airport": AirfieldData(
"Kish Intl": AirfieldData(
theater="Persian Gulf",
icao="OIBK",
elevation=114,
runway_length=10617,
tacan=TacanChannel(112, TacanBand.X),
tacan_callsign="KIH",
atc=AtcData(MHz(4, 50), MHz(39, 000), MHz(121, 650), MHz(250, 600)),
atc=AtcData(MHz(4, 100), MHz(39, 100), MHz(121, 650), MHz(250, 650)),
),
"Lavan Island Airport": AirfieldData(
"Lavan Island": AirfieldData(
theater="Persian Gulf",
icao="OIBV",
elevation=75,
runway_length=8234,
vor=("LVA", MHz(116, 850)),
atc=AtcData(MHz(4, 100), MHz(39, 100), MHz(128, 550), MHz(250, 700)),
atc=AtcData(MHz(4, 150), MHz(39, 200), MHz(128, 550), MHz(250, 750)),
),
"Lar Airbase": AirfieldData(
"Lar": AirfieldData(
theater="Persian Gulf",
icao="OISL",
elevation=2635,
@@ -603,7 +607,7 @@ AIRFIELD_DATA = {
runway_length=7300,
tacan=TacanChannel(47, TacanBand.X),
tacan_callsign="HDR",
atc=AtcData(MHz(4, 350), MHz(39, 600), MHz(123, 150), MHz(251, 200)),
atc=AtcData(MHz(4, 400), MHz(39, 700), MHz(123, 150), MHz(251, 300)),
ils={
"8": ("IBHD", MHz(108, 900)),
},
@@ -616,19 +620,19 @@ AIRFIELD_DATA = {
tacan=TacanChannel(78, TacanBand.X),
tacan_callsign="BND",
vor=("BND", MHz(117, 200)),
atc=AtcData(MHz(4, 200), MHz(39, 300), MHz(118, 100), MHz(250, 900)),
atc=AtcData(MHz(4, 250), MHz(39, 401), MHz(118, 100), MHz(251, 0)),
ils={
"21": ("IBND", MHz(333, 800)),
},
),
"Jiroft Airport": AirfieldData(
"Jiroft": AirfieldData(
theater="Persian Gulf",
icao="OIKJ",
elevation=2664,
runway_length=9160,
atc=AtcData(MHz(4, 125), MHz(39, 120), MHz(136, 0), MHz(250, 750)),
),
"Kerman Airport": AirfieldData(
"Kerman": AirfieldData(
theater="Persian Gulf",
icao="OIKK",
elevation=5746,
@@ -636,9 +640,9 @@ AIRFIELD_DATA = {
tacan=TacanChannel(97, TacanBand.X),
tacan_callsign="KER",
vor=("KER", MHz(112, 0)),
atc=AtcData(MHz(3, 900), MHz(38, 700), MHz(118, 250), MHz(250, 300)),
atc=AtcData(MHz(3, 925), MHz(38, 750), MHz(118, 250), MHz(250, 300)),
),
"Shiraz International Airport": AirfieldData(
"Shiraz Intl": AirfieldData(
theater="Persian Gulf",
icao="OISS",
elevation=4878,
@@ -646,7 +650,7 @@ AIRFIELD_DATA = {
tacan=TacanChannel(94, TacanBand.X),
tacan_callsign="SYZ1",
vor=("SYZ", MHz(112, 0)),
atc=AtcData(MHz(3, 925), MHz(38, 750), MHz(121, 900), MHz(250, 350)),
atc=AtcData(MHz(3, 950), MHz(38, 800), MHz(121, 900), MHz(250, 350)),
),
# Syria Map
"Adana Sakirpasa": AirfieldData(
@@ -655,7 +659,7 @@ AIRFIELD_DATA = {
elevation=55,
runway_length=8115,
vor=("ADA", MHz(112, 700)),
atc=AtcData(MHz(4, 225), MHz(39, 350), MHz(121, 100), MHz(250, 900)),
atc=AtcData(MHz(4, 275), MHz(39, 450), MHz(121, 100), MHz(251, 0)),
ils={
"05": ("IADA", MHz(108, 700)),
},
@@ -668,7 +672,7 @@ AIRFIELD_DATA = {
tacan=TacanChannel(21, TacanBand.X),
tacan_callsign="DAN",
vor=("DAN", MHz(108, 400)),
atc=AtcData(MHz(3, 850), MHz(38, 600), MHz(129, 400), MHz(360, 100)),
atc=AtcData(MHz(3, 900), MHz(38, 700), MHz(122, 100), MHz(360, 100)),
ils={
"50": ("IDAN", MHz(109, 300)),
"23": ("DANM", MHz(111, 700)),
@@ -679,7 +683,7 @@ AIRFIELD_DATA = {
icao="OS71",
elevation=1614,
runway_length=4648,
atc=AtcData(MHz(4, 125), MHz(39, 150), MHz(120, 600), MHz(250, 700)),
atc=AtcData(MHz(4, 175), MHz(39, 250), MHz(120, 600), MHz(250, 800)),
),
"Hatay": AirfieldData(
theater="Syria",
@@ -687,7 +691,7 @@ AIRFIELD_DATA = {
elevation=253,
runway_length=9052,
vor=("HTY", MHz(112, 500)),
atc=AtcData(MHz(3, 825), MHz(38, 550), MHz(128, 500), MHz(250, 150)),
atc=AtcData(MHz(3, 875), MHz(38, 650), MHz(128, 500), MHz(250, 250)),
ils={
"22": ("IHTY", MHz(108, 150)),
"04": ("IHAT", MHz(108, 900)),
@@ -698,25 +702,21 @@ AIRFIELD_DATA = {
icao="OS66",
elevation=1200,
runway_length=6662,
atc=AtcData(MHz(4, 275), MHz(39, 450), MHz(120, 500), MHz(251)),
atc=AtcData(MHz(4, 325), MHz(39, 550), MHz(120, 500), MHz(251, 100)),
),
"Aleppo": AirfieldData(
theater="Syria",
icao="OSAP",
elevation=1253,
runway_length=8332,
atc=AtcData(MHz(4, 150), MHz(39, 200), MHz(119, 100), MHz(250, 750)),
ils={
"50": ("IDAN", MHz(109, 300)),
"23": ("DANM", MHz(111, 700)),
},
atc=AtcData(MHz(4, 200), MHz(39, 300), MHz(119, 100), MHz(250, 850)),
),
"Jirah": AirfieldData(
theater="Syria",
icao="OS62",
elevation=1170,
runway_length=9090,
atc=AtcData(MHz(3, 875), MHz(38, 650), MHz(118, 100), MHz(250, 200)),
atc=AtcData(MHz(3, 925), MHz(38, 750), MHz(118, 100), MHz(250, 300)),
),
"Taftanaz": AirfieldData(
theater="Syria",
@@ -729,14 +729,14 @@ AIRFIELD_DATA = {
icao="OS59",
elevation=1083,
runway_length=9036,
atc=AtcData(MHz(4, 350), MHz(39, 600), MHz(118, 500), MHz(251, 150)),
atc=AtcData(MHz(4, 500), MHz(39, 900), MHz(122, 800), MHz(251, 450)),
),
"Abu al-Dahur": AirfieldData(
theater="Syria",
icao="OS57",
elevation=820,
runway_length=8728,
atc=AtcData(MHz(3, 950), MHz(38, 800), MHz(122, 200), MHz(250, 350)),
atc=AtcData(MHz(4, 0), MHz(38, 900), MHz(122, 200), MHz(250, 450)),
),
"Bassel Al-Assad": AirfieldData(
theater="Syria",
@@ -744,7 +744,7 @@ AIRFIELD_DATA = {
elevation=93,
runway_length=7305,
vor=("LTK", MHz(114, 800)),
atc=AtcData(MHz(4), MHz(38, 900), MHz(118, 100), MHz(250, 450)),
atc=AtcData(MHz(4, 50), MHz(39, 0), MHz(118, 100), MHz(250, 550)),
ils={
"17": ("IBA", MHz(109, 100)),
},
@@ -754,28 +754,28 @@ AIRFIELD_DATA = {
icao="OS58",
elevation=983,
runway_length=7957,
atc=AtcData(MHz(3, 800), MHz(38, 500), MHz(118, 50), MHz(250, 100)),
atc=AtcData(MHz(3, 850), MHz(38, 600), MHz(118, 50), MHz(250, 200)),
),
"Rene Mouawad": AirfieldData(
theater="Syria",
icao="OLKA",
elevation=14,
runway_length=8614,
atc=AtcData(MHz(4, 325), MHz(39, 550), MHz(129, 500), MHz(251, 100)),
atc=AtcData(MHz(4, 375), MHz(39, 650), MHz(121, 0), MHz(251, 200)),
),
"Al Quasayr": AirfieldData(
theater="Syria",
icao="OS70",
elevation=1729,
runway_length=8585,
atc=AtcData(MHz(4, 400), MHz(39, 700), MHz(119, 200), MHz(251, 250)),
atc=AtcData(MHz(4, 550), MHz(40, 0), MHz(119, 200), MHz(251, 550)),
),
"Palmyra": AirfieldData(
theater="Syria",
icao="OSPR",
elevation=1267,
runway_length=8704,
atc=AtcData(MHz(4, 175), MHz(39, 250), MHz(121, 900), MHz(250, 800)),
atc=AtcData(MHz(4, 225), MHz(39, 350), MHz(121, 900), MHz(250, 900)),
),
"Wujah Al Hajar": AirfieldData(
theater="Syria",
@@ -783,14 +783,14 @@ AIRFIELD_DATA = {
elevation=619,
runway_length=4717,
vor=("CAK", MHz(116, 200)),
atc=AtcData(MHz(4, 425), MHz(39, 750), MHz(121, 500), MHz(251, 300)),
atc=AtcData(MHz(4, 575), MHz(40, 50), MHz(121, 500), MHz(251, 600)),
),
"An Nasiriyah": AirfieldData(
theater="Syria",
icao="OS64",
elevation=2746,
runway_length=8172,
atc=AtcData(MHz(4, 450), MHz(39, 800), MHz(122, 300), MHz(251, 350)),
atc=AtcData(MHz(4, 600), MHz(40, 100), MHz(122, 300), MHz(251, 650)),
),
"Rayak": AirfieldData(
theater="Syria",
@@ -798,7 +798,7 @@ AIRFIELD_DATA = {
elevation=2934,
runway_length=8699,
vor=("HTY", MHz(124, 400)),
atc=AtcData(MHz(4, 300), MHz(39, 500), MHz(124, 400), MHz(251, 50)),
atc=AtcData(MHz(4, 350), MHz(39, 600), MHz(124, 400), MHz(251, 150)),
),
"Beirut-Rafic Hariri": AirfieldData(
theater="Syria",
@@ -806,7 +806,7 @@ AIRFIELD_DATA = {
elevation=39,
runway_length=9463,
vor=("KAD", MHz(112, 600)),
atc=AtcData(MHz(4, 475), MHz(39, 850), MHz(118, 900), MHz(251, 400)),
atc=AtcData(MHz(4, 675), MHz(40, 250), MHz(118, 900), MHz(251, 800)),
ils={
"17": ("BIL", MHz(109, 500)),
},
@@ -816,32 +816,32 @@ AIRFIELD_DATA = {
icao="OS61",
elevation=2066,
runway_length=8902,
atc=AtcData(MHz(4, 550), MHz(40), MHz(120, 300), MHz(251, 550)),
atc=AtcData(MHz(4, 750), MHz(40, 400), MHz(120, 300), MHz(251, 950)),
),
"Marj as Sultan North": AirfieldData(
theater="Syria",
elevation=2007,
runway_length=268,
atc=AtcData(MHz(4, 25), MHz(38, 950), MHz(122, 700), MHz(250, 500)),
atc=AtcData(MHz(4, 75), MHz(38, 50), MHz(122, 700), MHz(250, 600)),
),
"Marj as Sultan South": AirfieldData(
theater="Syria",
elevation=2007,
runway_length=166,
atc=AtcData(MHz(4, 525), MHz(39, 950), MHz(122, 900), MHz(251, 500)),
atc=AtcData(MHz(4, 725), MHz(40, 350), MHz(122, 900), MHz(251, 900)),
),
"Mezzeh": AirfieldData(
theater="Syria",
icao="OS67",
elevation=2355,
runway_length=7522,
atc=AtcData(MHz(4, 100), MHz(39, 100), MHz(120, 700), MHz(250, 650)),
atc=AtcData(MHz(4, 150), MHz(39, 200), MHz(120, 700), MHz(250, 750)),
),
"Qabr as Sitt": AirfieldData(
theater="Syria",
elevation=2134,
runway_length=489,
atc=AtcData(MHz(4, 200), MHz(39, 300), MHz(122, 600), MHz(250, 850)),
atc=AtcData(MHz(4, 250), MHz(39, 400), MHz(122, 600), MHz(250, 950)),
),
"Damascus": AirfieldData(
theater="Syria",
@@ -849,7 +849,7 @@ AIRFIELD_DATA = {
elevation=2007,
runway_length=11423,
vor=("DAM", MHz(116)),
atc=AtcData(MHz(4, 500), MHz(39, 900), MHz(118, 500), MHz(251, 450)),
atc=AtcData(MHz(4, 700), MHz(40, 300), MHz(118, 500), MHz(251, 850)),
ils={
"24": ("IDA", MHz(109, 900)),
},
@@ -859,42 +859,42 @@ AIRFIELD_DATA = {
icao="OS63",
elevation=2160,
runway_length=7576,
atc=AtcData(MHz(4, 50), MHz(39), MHz(120, 800), MHz(250, 550)),
atc=AtcData(MHz(4, 100), MHz(39, 100), MHz(120, 800), MHz(250, 6550)),
),
"Kiryat Shmona": AirfieldData(
theater="Syria",
icao="LLKS",
elevation=328,
runway_length=3258,
atc=AtcData(MHz(3, 975), MHz(38, 850), MHz(118, 400), MHz(250, 400)),
atc=AtcData(MHz(4, 25), MHz(38, 950), MHz(118, 400), MHz(250, 500)),
),
"Khalkhalah": AirfieldData(
theater="Syria",
icao="OS69",
elevation=2337,
runway_length=8248,
atc=AtcData(MHz(3, 900), MHz(38, 700), MHz(122, 500), MHz(250, 250)),
atc=AtcData(MHz(3, 950), MHz(38, 800), MHz(122, 500), MHz(250, 350)),
),
"Haifa": AirfieldData(
theater="Syria",
icao="LLHA",
elevation=19,
runway_length=3253,
atc=AtcData(MHz(3, 775), MHz(38, 450), MHz(127, 800), MHz(250, 50)),
atc=AtcData(MHz(3, 825), MHz(38, 550), MHz(127, 800), MHz(250, 150)),
),
"Ramat David": AirfieldData(
theater="Syria",
icao="LLRD",
elevation=105,
runway_length=7037,
atc=AtcData(MHz(4, 250), MHz(39, 400), MHz(118, 600), MHz(250, 950)),
atc=AtcData(MHz(4, 300), MHz(39, 500), MHz(118, 600), MHz(251, 50)),
),
"Megiddo": AirfieldData(
theater="Syria",
icao="LLMG",
elevation=180,
runway_length=6098,
atc=AtcData(MHz(4, 75), MHz(39, 50), MHz(119, 900), MHz(250, 600)),
atc=AtcData(MHz(4, 125), MHz(39, 150), MHz(119, 900), MHz(250, 700)),
),
"Eyn Shemer": AirfieldData(
theater="Syria",
@@ -908,7 +908,66 @@ AIRFIELD_DATA = {
icao="OJMF",
elevation=2204,
runway_length=8595,
atc=AtcData(MHz(3, 925), MHz(38, 750), MHz(118, 300), MHz(250, 300)),
atc=AtcData(MHz(3, 975), MHz(38, 850), MHz(118, 300), MHz(250, 400)),
),
"Tha'lah": AirfieldData(
theater="Syria",
icao="OS60",
elevation=2381,
runway_length=8025,
atc=AtcData(MHz(4, 650), MHz(40, 200), MHz(122, 400), MHz(251, 750)),
),
"Shayrat": AirfieldData(
theater="Syria",
icao="OS60",
elevation=2637,
runway_length=8553,
atc=AtcData(MHz(4, 450), MHz(39, 800), MHz(122, 200), MHz(251, 350)),
),
"Tiyas": AirfieldData(
theater="Syria",
icao="OS72",
elevation=1797,
runway_length=9420,
atc=AtcData(MHz(4, 525), MHz(39, 950), MHz(120, 500), MHz(251, 500)),
),
"Rosh Pina": AirfieldData(
theater="Syria",
icao="LLIB",
elevation=865,
runway_length=2711,
atc=AtcData(MHz(4, 400), MHz(39, 700), MHz(118, 450), MHz(251, 250)),
),
"Sayqal": AirfieldData(
theater="Syria",
icao="OS68",
elevation=2273,
runway_length=8536,
atc=AtcData(MHz(4, 425), MHz(39, 750), MHz(120, 400), MHz(251, 300)),
),
"H4": AirfieldData(
theater="Syria",
icao="OJHR",
elevation=2257,
runway_length=7179,
atc=AtcData(MHz(3, 800), MHz(38, 500), MHz(120, 400), MHz(250, 100)),
),
"Naqoura": AirfieldData(
theater="Syria",
icao="",
elevation=378,
runway_length=0,
atc=AtcData(MHz(4, 625), MHz(40, 150), MHz(122, 000), MHz(251, 700)),
),
"Gaziantep": AirfieldData(
theater="Syria",
icao="LTAJ",
elevation=2287,
runway_length=8871,
atc=AtcData(MHz(3, 775), MHz(38, 450), MHz(120, 100), MHz(250, 50)),
ils={
"28": ("IGNP", MHz(109, 10)),
},
),
# NTTR
"Mina Airport 3Q0": AirfieldData(
@@ -1304,55 +1363,73 @@ AIRFIELD_DATA = {
"Detling": AirfieldData(
theater="Channel",
elevation=623,
runway_length=2557,
atc=AtcData(MHz(3, 950), MHz(118, 400), MHz(38, 800), MHz(250, 400)),
runway_length=3482,
atc=AtcData(MHz(4, 50), MHz(118, 600), MHz(39, 0), MHz(250, 600)),
),
"High Halden": AirfieldData(
theater="Channel",
elevation=104,
runway_length=3296,
atc=AtcData(MHz(3, 750), MHz(118, 800), MHz(38, 400), MHz(250, 0)),
atc=AtcData(MHz(3, 800), MHz(118, 100), MHz(38, 500), MHz(250, 100)),
),
"Lympne": AirfieldData(
theater="Channel",
elevation=351,
runway_length=2548,
atc=AtcData(MHz(3, 925), MHz(118, 350), MHz(38, 750), MHz(250, 350)),
runway_length=3054,
atc=AtcData(MHz(4, 25), MHz(118, 550), MHz(38, 950), MHz(250, 550)),
),
"Hawkinge": AirfieldData(
theater="Channel",
elevation=524,
runway_length=3013,
atc=AtcData(MHz(3, 900), MHz(118, 300), MHz(38, 700), MHz(250, 300)),
atc=AtcData(MHz(4, 0), MHz(118, 500), MHz(38, 900), MHz(250, 500)),
),
"Manston": AirfieldData(
theater="Channel",
elevation=160,
runway_length=8626,
atc=AtcData(MHz(3, 875), MHz(118, 250), MHz(38, 650), MHz(250, 250)),
atc=AtcData(MHz(3, 975), MHz(118, 250), MHz(38, 650), MHz(250, 250)),
),
"Dunkirk Mardyck": AirfieldData(
theater="Channel",
elevation=16,
runway_length=1737,
atc=AtcData(MHz(3, 850), MHz(118, 200), MHz(38, 600), MHz(250, 200)),
atc=AtcData(MHz(3, 950), MHz(118, 450), MHz(38, 850), MHz(250, 450)),
),
"Saint Omer Longuenesse": AirfieldData(
theater="Channel",
elevation=219,
runway_length=1929,
atc=AtcData(MHz(3, 825), MHz(118, 150), MHz(38, 550), MHz(250, 150)),
atc=AtcData(MHz(3, 925), MHz(118, 350), MHz(38, 750), MHz(250, 350)),
),
"Merville Calonne": AirfieldData(
theater="Channel",
elevation=52,
runway_length=7580,
atc=AtcData(MHz(3, 800), MHz(118, 100), MHz(38, 500), MHz(250, 100)),
atc=AtcData(MHz(3, 900), MHz(118, 300), MHz(38, 700), MHz(250, 300)),
),
"Abbeville Drucat": AirfieldData(
theater="Channel",
elevation=183,
runway_length=4726,
atc=AtcData(MHz(3, 875), MHz(118, 250), MHz(38, 650), MHz(250, 250)),
),
"Eastchurch": AirfieldData(
theater="Channel",
elevation=30,
runway_length=2983,
atc=AtcData(MHz(3, 775), MHz(118, 50), MHz(38, 450), MHz(250, 50)),
),
"Headcorn": AirfieldData(
theater="Channel",
elevation=114,
runway_length=3680,
atc=AtcData(MHz(3, 825), MHz(118, 150), MHz(38, 550), MHz(250, 150)),
),
"Biggin Hill": AirfieldData(
theater="Channel",
elevation=552,
runway_length=3953,
atc=AtcData(MHz(3, 850), MHz(118, 200), MHz(38, 600), MHz(250, 200)),
),
}

View File

@@ -17,6 +17,7 @@ class EnvironmentGenerator:
self.mission.weather.clouds_thickness = clouds.thickness
self.mission.weather.clouds_density = clouds.density
self.mission.weather.clouds_iprecptns = clouds.precipitation
self.mission.weather.clouds_preset = clouds.preset
def set_fog(self, fog: Optional[Fog]) -> None:
if fog is None:

View File

@@ -450,21 +450,31 @@ class ObjectiveFinder:
c for c in self.game.theater.controlpoints if c.is_friendly(self.is_player)
)
def farthest_friendly_control_point(self) -> ControlPoint:
def farthest_friendly_control_point(self) -> Optional[ControlPoint]:
"""
Iterates over all friendly control points and find the one farthest away from the frontline
BUT! prefer Cvs. Everybody likes CVs!
"""
from_frontline = 0
cp = None
first_friendly_cp = None
for c in self.game.theater.controlpoints:
if c.is_carrier and c.is_friendly(self.is_player):
return c
if c.is_friendly(self.is_player) and c.has_frontline:
if c.distance_to(self.front_lines().__next__()) > from_frontline:
from_frontline = c.distance_to(self.front_lines().__next__())
cp = c
return cp
if c.is_friendly(self.is_player):
if first_friendly_cp is None:
first_friendly_cp = c
if c.is_carrier:
return c
if c.has_active_frontline:
if c.distance_to(self.front_lines().__next__()) > from_frontline:
from_frontline = c.distance_to(self.front_lines().__next__())
cp = c
# If no frontlines on the map, return the first friendly cp
if cp is None:
return first_friendly_cp
else:
return cp
def enemy_control_points(self) -> Iterator[ControlPoint]:
"""Iterates over all enemy control points."""
@@ -546,9 +556,10 @@ class CoalitionMissionPlanner:
# Find farthest, friendly CP for AEWC
cp = self.objective_finder.farthest_friendly_control_point()
yield ProposedMission(
cp, [ProposedFlight(FlightType.AEWC, 1, self.MAX_AWEC_RANGE)]
)
if cp is not None:
yield ProposedMission(
cp, [ProposedFlight(FlightType.AEWC, 1, self.MAX_AWEC_RANGE)]
)
# Find friendly CPs within 100 nmi from an enemy airfield, plan CAP.
for cp in self.objective_finder.vulnerable_control_points():
@@ -579,9 +590,23 @@ class CoalitionMissionPlanner:
front_line,
[
ProposedFlight(FlightType.CAS, 2, self.MAX_CAS_RANGE),
ProposedFlight(
FlightType.TARCAP, 2, self.MAX_CAP_RANGE, EscortType.AirToAir
),
# This is *not* an escort because front lines don't create a threat
# zone. Generating threat zones from front lines causes the front
# line to push back BARCAPs as it gets closer to the base. While
# front lines do have the same problem of potentially pulling
# BARCAPs off bases to engage a front line TARCAP, that's probably
# the one time where we do want that.
#
# TODO: Use intercepts and extra TARCAPs to cover bases near fronts.
# We don't have intercept missions yet so this isn't something we
# can do today, but we should probably return to having the front
# line project a threat zone (so that strike missions will route
# around it) and instead *not plan* a BARCAP at bases near the
# front, since there isn't a place to put a barrier. Instead, the
# aircraft that would have been a BARCAP could be used as additional
# interceptors and TARCAPs which will defend the base but won't be
# trying to avoid front line contacts.
ProposedFlight(FlightType.TARCAP, 2, self.MAX_CAP_RANGE),
],
)

View File

@@ -110,6 +110,7 @@ TYPE_ARTILLERY = [
Artillery.MLRS_M270_227mm,
Artillery.SPH_2S9_Nona_120mm_M,
Artillery.SPH_Dana_vz77_152mm,
Artillery.PLZ_05,
Artillery.SPH_2S19_Msta_152mm,
Artillery.MLRS_9A52_Smerch_CM_300mm,
# WW2
@@ -175,6 +176,7 @@ TYPE_SHORAD = [
AirDefence.AAA_8_8cm_Flak_37,
AirDefence.AAA_8_8cm_Flak_41,
AirDefence.AAA_40mm_Bofors,
AirDefence.AAA_S_60_57mm,
AirDefence.AAA_M1_37mm,
AirDefence.AAA_QF_3_7,
]

View File

@@ -68,7 +68,8 @@ class MizDataLocationFinder:
for vehicle_group in m.country("Iran").vehicle_group:
if (
len(vehicle_group.units) > 0
and vehicle_group.units[0].type == MissilesSS.AShM_SS_N_2_Silkworm.id
and vehicle_group.units[0].type
== MissilesSS.AShM_SS_N_2_Silkworm.id
):
antiship_locations.append(
PresetLocation(

View File

@@ -12,13 +12,13 @@ from gen.sam.group_generator import GroupGenerator
class EarlyColdWarFlakGenerator(AirDefenseGroupGenerator):
"""
This generator attempt to mimic an early cold-war era flak AAA site.
The Flak 18 88mm is used as the main long range gun and 2 Bofors 40mm guns provide short range protection.
The Flak 18 88mm is used as the main long range gun, S-60 is used as a mid range gun and 2 Bofors 40mm guns provide short range protection.
This does not include search lights and telemeter computer (Kdo.G 40) because these are paid units only available in WW2 asset pack
"""
name = "Early Cold War Flak Site"
price = 58
price = 74
def generate(self):
@@ -37,22 +37,38 @@ class EarlyColdWarFlakGenerator(AirDefenseGroupGenerator):
self.heading,
)
# Short range guns
# Medium range guns
self.add_unit(
AirDefence.AAA_40mm_Bofors,
AirDefence.AAA_S_60_57mm,
"SHO#1",
self.position.x - 40,
self.position.y - 40,
self.heading + 180,
),
self.add_unit(
AirDefence.AAA_40mm_Bofors,
AirDefence.AAA_S_60_57mm,
"SHO#2",
self.position.x + spacing * 2 + 40,
self.position.y + spacing + 40,
self.heading,
),
# Short range guns
self.add_unit(
AirDefence.AAA_ZU_23_Closed_Emplacement,
"SHO#3",
self.position.x - 80,
self.position.y - 40,
self.heading + 180,
),
self.add_unit(
AirDefence.AAA_ZU_23_Closed_Emplacement,
"SHO#4",
self.position.x + spacing * 2 + 80,
self.position.y + spacing + 40,
self.heading,
),
# Add a truck
self.add_unit(
Unarmed.Truck_KAMAZ_43101,
@@ -70,7 +86,7 @@ class EarlyColdWarFlakGenerator(AirDefenseGroupGenerator):
class ColdWarFlakGenerator(AirDefenseGroupGenerator):
"""
This generator attempt to mimic a cold-war era flak AAA site.
The Flak 18 88mm is used as the main long range gun while 2 Zu-23 guns provide short range protection.
The Flak 18 88mm is used as the main long range gun, 2 S-60 57mm gun improve mid range firepower, while 2 Zu-23 guns even provide short range protection.
The site is also fitted with a P-19 radar for early detection.
"""
@@ -94,22 +110,38 @@ class ColdWarFlakGenerator(AirDefenseGroupGenerator):
self.heading,
)
# Short range guns
# Medium range guns
self.add_unit(
AirDefence.AAA_ZU_23_Closed_Emplacement,
AirDefence.AAA_S_60_57mm,
"SHO#1",
self.position.x - 40,
self.position.y - 40,
self.heading + 180,
),
self.add_unit(
AirDefence.AAA_ZU_23_Closed_Emplacement,
AirDefence.AAA_S_60_57mm,
"SHO#2",
self.position.x + spacing * 2 + 40,
self.position.y + spacing + 40,
self.heading,
),
# Short range guns
self.add_unit(
AirDefence.AAA_ZU_23_Closed_Emplacement,
"SHO#3",
self.position.x - 80,
self.position.y - 40,
self.heading + 180,
),
self.add_unit(
AirDefence.AAA_ZU_23_Closed_Emplacement,
"SHO#4",
self.position.x + spacing * 2 + 80,
self.position.y + spacing + 40,
self.heading,
),
# Add a P19 Radar for EWR
self.add_unit(
AirDefence.SAM_P19_Flat_Face_SR__SA_2_3,

View File

@@ -0,0 +1,63 @@
import random
from typing import List, Optional, Type
from dcs.unitgroup import VehicleGroup
from game import Game
from game.factions.faction import Faction
from game.theater.theatergroundobject import EwrGroundObject
from gen.sam.ewrs import (
BigBirdGenerator,
BoxSpringGenerator,
DogEarGenerator,
FlatFaceGenerator,
HawkEwrGenerator,
PatriotEwrGenerator,
RolandEwrGenerator,
SnowDriftGenerator,
StraightFlushGenerator,
TallRackGenerator,
)
from gen.sam.group_generator import GroupGenerator
EWR_MAP = {
"BoxSpringGenerator": BoxSpringGenerator,
"TallRackGenerator": TallRackGenerator,
"DogEarGenerator": DogEarGenerator,
"RolandEwrGenerator": RolandEwrGenerator,
"FlatFaceGenerator": FlatFaceGenerator,
"PatriotEwrGenerator": PatriotEwrGenerator,
"BigBirdGenerator": BigBirdGenerator,
"SnowDriftGenerator": SnowDriftGenerator,
"StraightFlushGenerator": StraightFlushGenerator,
"HawkEwrGenerator": HawkEwrGenerator,
}
def get_faction_possible_ewrs_generator(
faction: Faction,
) -> List[Type[GroupGenerator]]:
"""
Return the list of possible EWR generators for the given faction
:param faction: Faction name to search units for
"""
return [EWR_MAP[s] for s in faction.ewrs]
def generate_ewr_group(
game: Game, ground_object: EwrGroundObject, faction: Faction
) -> Optional[VehicleGroup]:
"""Generates an early warning radar group.
:param game: The Game.
:param ground_object: The ground object which will own the EWR group.
:param faction: Owner faction.
:return: The generated group, or None if one could not be generated.
"""
generators = get_faction_possible_ewrs_generator(faction)
if len(generators) > 0:
generator_class = random.choice(generators)
generator = generator_class(game, ground_object)
generator.generate()
return generator.get_generated_group()
return None

View File

@@ -6,7 +6,6 @@ from dcs.vehicles import AirDefence
from game import Game
from game.factions.faction import Faction
from game.theater import TheaterGroundObject
from game.theater.theatergroundobject import SamGroundObject
from gen.sam.aaa_bofors import BoforsGenerator
from gen.sam.aaa_flak import FlakGenerator
@@ -23,20 +22,7 @@ from gen.sam.cold_war_flak import (
ColdWarFlakGenerator,
EarlyColdWarFlakGenerator,
)
from gen.sam.ewrs import (
BigBirdGenerator,
BoxSpringGenerator,
DogEarGenerator,
FlatFaceGenerator,
HawkEwrGenerator,
PatriotEwrGenerator,
RolandEwrGenerator,
SnowDriftGenerator,
StraightFlushGenerator,
TallRackGenerator,
)
from gen.sam.freya_ewr import FreyaGenerator
from gen.sam.group_generator import GroupGenerator
from gen.sam.sam_avenger import AvengerGenerator
from gen.sam.sam_chaparral import ChaparralGenerator
from gen.sam.sam_gepard import GepardGenerator
@@ -152,19 +138,6 @@ SAM_PRICES = {
AirDefence.HQ_7_Self_Propelled_LN: 35,
}
EWR_MAP = {
"BoxSpringGenerator": BoxSpringGenerator,
"TallRackGenerator": TallRackGenerator,
"DogEarGenerator": DogEarGenerator,
"RolandEwrGenerator": RolandEwrGenerator,
"FlatFaceGenerator": FlatFaceGenerator,
"PatriotEwrGenerator": PatriotEwrGenerator,
"BigBirdGenerator": BigBirdGenerator,
"SnowDriftGenerator": SnowDriftGenerator,
"StraightFlushGenerator": StraightFlushGenerator,
"HawkEwrGenerator": HawkEwrGenerator,
}
def get_faction_possible_sams_generator(
faction: Faction,
@@ -176,14 +149,6 @@ def get_faction_possible_sams_generator(
return [SAM_MAP[s] for s in faction.air_defenses]
def get_faction_possible_ewrs_generator(faction: Faction) -> List[Type[GroupGenerator]]:
"""
Return the list of possible SAM generator for the given faction
:param faction: Faction name to search units for
"""
return [EWR_MAP[s] for s in faction.ewrs]
def _generate_anti_air_from(
generators: Sequence[Type[AirDefenseGroupGenerator]],
game: Game,
@@ -236,22 +201,3 @@ def generate_anti_air_group(
if groups:
return groups
return []
def generate_ewr_group(
game: Game, ground_object: TheaterGroundObject, faction: Faction
) -> Optional[VehicleGroup]:
"""Generates an early warning radar group.
:param game: The Game.
:param ground_object: The ground object which will own the EWR group.
:param faction: Owner faction.
:return: The generated group, or None if one could not be generated.
"""
generators = get_faction_possible_ewrs_generator(faction)
if len(generators) > 0:
generator_class = random.choice(generators)
generator = generator_class(game, ground_object)
generator.generate()
return generator.get_generated_group()
return None

2
pydcs

Submodule pydcs updated: d899985e28...cd14f0a049

View File

@@ -103,7 +103,9 @@ class DisplayOptions:
waypoint_info = DisplayRule("Waypoint Information", True)
culling = DisplayRule("Display Culling Zones", False)
actual_frontline_pos = DisplayRule("Display Actual Frontline Location", False)
barcap_commit_range = DisplayRule("Display selected BARCAP commit range", False)
patrol_engagement_range = DisplayRule(
"Display selected patrol engagement range", True
)
flight_paths = FlightPathOptions()
blue_threat_zones = ThreatZoneOptions("Blue")
red_threat_zones = ThreatZoneOptions("Red")

View File

@@ -147,6 +147,8 @@ def load_icons():
"./resources/ui/ground_assets/" + category + "_blue.png"
)
ICONS["destroyed"] = QPixmap("./resources/ui/ground_assets/destroyed.png")
ICONS["EWR"] = QPixmap("./resources/ui/ground_assets/ewr.png")
ICONS["EWR_blue"] = QPixmap("./resources/ui/ground_assets/ewr_blue.png")
ICONS["ship"] = QPixmap("./resources/ui/ground_assets/ship.png")
ICONS["ship_blue"] = QPixmap("./resources/ui/ground_assets/ship_blue.png")
ICONS["missile"] = QPixmap("./resources/ui/ground_assets/missile.png")

View File

@@ -7,7 +7,7 @@ from PySide2.QtWidgets import (
QLabel,
QVBoxLayout,
)
from dcs.weather import Weather as PydcsWeather
from dcs.weather import CloudPreset, Weather as PydcsWeather
import qt_ui.uiconstants as CONST
from game.utils import mps
@@ -162,7 +162,7 @@ class QWeatherWidget(QGroupBox):
self.turn = turn
self.conditions = conditions
self.updateForecast()
self.update_forecast()
self.updateWinds()
def updateWinds(self):
@@ -186,55 +186,76 @@ class QWeatherWidget(QGroupBox):
self.windFL26SpeedLabel.setText(f"{int(windFL26Speed.knots)}kts")
self.windFL26DirLabel.setText(f"{windFL26Dir}º")
def updateForecast(self):
def update_forecast_from_preset(self, preset: CloudPreset) -> None:
self.forecastFog.setText("No fog")
if "Rain" in preset.name:
self.forecastRain.setText("Rain")
self.update_forecast_icons("rain")
else:
self.forecastRain.setText("No rain")
self.update_forecast_icons("partly-cloudy")
# We get a description like the following for the cloud preset.
#
# 09 ##Two Layer Broken/Scattered \nMETAR:BKN 7.5/10 SCT 20/22 FEW41
#
# The second line is probably interesting but doesn't fit into the widget
# currently, so for now just extract the first line.
self.forecastClouds.setText(preset.description.splitlines()[0].split("##")[1])
def update_forecast(self):
"""Updates the Forecast Text and icon with the current conditions wind info."""
icon = []
if (
self.conditions.weather.clouds
and self.conditions.weather.clouds.preset is not None
):
self.update_forecast_from_preset(self.conditions.weather.clouds.preset)
return
if self.conditions.weather.clouds is None:
cloudDensity = 0
cloud_density = 0
precipitation = None
else:
cloudDensity = self.conditions.weather.clouds.density
cloud_density = self.conditions.weather.clouds.density
precipitation = self.conditions.weather.clouds.precipitation
fog = self.conditions.weather.fog or None
is_night = self.conditions.time_of_day == TimeOfDay.Night
time = "night" if is_night else "day"
if cloudDensity <= 0:
if not cloud_density:
self.forecastClouds.setText("Sunny")
icon = [time, "clear"]
if cloudDensity > 0 and cloudDensity < 3:
weather_type = "clear"
elif cloud_density < 3:
self.forecastClouds.setText("Partly Cloudy")
icon = [time, "partly-cloudy"]
if cloudDensity >= 3 and cloudDensity < 5:
weather_type = "partly-cloudy"
elif cloud_density < 5:
self.forecastClouds.setText("Mostly Cloudy")
icon = [time, "partly-cloudy"]
if cloudDensity >= 5:
weather_type = "partly-cloudy"
else:
self.forecastClouds.setText("Totally Cloudy")
icon = [time, "partly-cloudy"]
weather_type = "partly-cloudy"
if precipitation == PydcsWeather.Preceptions.Rain:
self.forecastRain.setText("Rain")
icon = [time, "rain"]
weather_type = "rain"
elif precipitation == PydcsWeather.Preceptions.Thunderstorm:
self.forecastRain.setText("Thunderstorm")
icon = [time, "thunderstorm"]
weather_type = "thunderstorm"
else:
self.forecastRain.setText("No Rain")
self.forecastRain.setText("No rain")
if not fog:
if not self.conditions.weather.fog is not None:
self.forecastFog.setText("No fog")
else:
visibility = round(fog.visibility.nautical_miles, 1)
visibility = round(self.conditions.weather.fog.visibility.nautical_miles, 1)
self.forecastFog.setText(f"Fog vis: {visibility}nm")
icon = [time, ("cloudy" if cloudDensity > 1 else None), "fog"]
if cloud_density > 1:
weather_type = "cloudy-fog"
else:
weather_type = "fog"
icon_key = "Weather_{}".format("-".join(filter(None.__ne__, icon)))
self.update_forecast_icons(weather_type)
def update_forecast_icons(self, weather_type: str) -> None:
time = "night" if self.conditions.time_of_day == TimeOfDay.Night else "day"
icon_key = f"Weather_{time}-{weather_type}"
icon = CONST.ICONS.get(icon_key) or CONST.ICONS["Weather_night-partly-cloudy"]
self.weather_icon.setPixmap(icon)

View File

@@ -58,6 +58,8 @@ from gen.flights.flightplan import (
FlightPlan,
FlightPlanBuilder,
InvalidObjectiveLocation,
PatrollingFlightPlan,
TarCapFlightPlan,
)
from gen.flights.traveltime import TotEstimator
from qt_ui.displayoptions import DisplayOptions, ThreatZoneOptions
@@ -721,13 +723,11 @@ class QLiberationMap(QGraphicsView):
)
prev_pos = tuple(new_pos)
if selected and DisplayOptions.barcap_commit_range:
self.draw_barcap_commit_range(scene, flight)
if selected and DisplayOptions.patrol_engagement_range:
self.draw_patrol_commit_range(scene, flight)
def draw_barcap_commit_range(self, scene: QGraphicsScene, flight: Flight) -> None:
if flight.flight_type is not FlightType.BARCAP:
return
if not isinstance(flight.flight_plan, BarCapFlightPlan):
def draw_patrol_commit_range(self, scene: QGraphicsScene, flight: Flight) -> None:
if not isinstance(flight.flight_plan, PatrollingFlightPlan):
return
start = flight.flight_plan.patrol_start
end = flight.flight_plan.patrol_end

View File

@@ -172,6 +172,8 @@ class QBaseMenu2(QDialog):
return "./resources/ui/carrier.png"
elif self.cp.cptype == ControlPointType.LHA_GROUP:
return "./resources/ui/lha.png"
elif self.cp.cptype == ControlPointType.FOB:
return "./resources/ui/fob.png"
else:
return "./resources/ui/airbase.png"

View File

@@ -1,6 +1,6 @@
from PySide2.QtWidgets import QTabWidget
from game.theater import ControlPoint, OffMapSpawn
from game.theater import ControlPoint, OffMapSpawn, Fob
from qt_ui.models import GameModel
from qt_ui.windows.basemenu.airfield.QAirfieldCommand import QAirfieldCommand
from qt_ui.windows.basemenu.base_defenses.QBaseDefensesHQ import QBaseDefensesHQ
@@ -19,14 +19,26 @@ class QBaseMenuTabs(QTabWidget):
self.intel = QIntelInfo(cp, game_model.game)
self.addTab(self.intel, "Intel")
else:
self.airfield_command = QAirfieldCommand(cp, game_model)
self.addTab(self.airfield_command, "Airfield Command")
if cp.is_carrier:
self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game)
self.addTab(self.base_defenses_hq, "Fleet")
elif not isinstance(cp, OffMapSpawn):
self.ground_forces_hq = QGroundForcesHQ(cp, game_model)
self.addTab(self.ground_forces_hq, "Ground Forces HQ")
self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game)
self.addTab(self.base_defenses_hq, "Base Defenses")
if cp:
if isinstance(cp, Fob):
self.ground_forces_hq = QGroundForcesHQ(cp, game_model)
self.addTab(self.ground_forces_hq, "Ground Forces HQ")
if cp.helipads:
self.airfield_command = QAirfieldCommand(cp, game_model)
self.addTab(self.airfield_command, "Heliport")
self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game)
self.addTab(self.base_defenses_hq, "Base Defenses")
else:
self.airfield_command = QAirfieldCommand(cp, game_model)
self.addTab(self.airfield_command, "Airfield Command")
if cp.is_carrier:
self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game)
self.addTab(self.base_defenses_hq, "Fleet")
elif not isinstance(cp, OffMapSpawn):
self.ground_forces_hq = QGroundForcesHQ(cp, game_model)
self.addTab(self.ground_forces_hq, "Ground Forces HQ")
self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game)
self.addTab(self.base_defenses_hq, "Base Defenses")

View File

@@ -12,11 +12,12 @@ from PySide2.QtWidgets import (
QVBoxLayout,
QWidget,
)
from dcs.helicopters import helicopter_map
from dcs.task import CAP, CAS, AWACS
from dcs.unittype import FlyingType, UnitType
from game import db
from game.theater import ControlPoint
from game.theater import ControlPoint, ControlPointType
from qt_ui.models import GameModel
from qt_ui.uiconstants import ICONS
from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour
@@ -63,6 +64,11 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour):
continue
if self.cp.is_lha and unit not in db.LHA_CAPABLE:
continue
if (
self.cp.cptype in [ControlPointType.FOB, ControlPointType.FARP]
and unit not in helicopter_map.values()
):
continue
unit_types.add(unit)
sorted_units = sorted(

View File

@@ -43,4 +43,4 @@ class QLoadoutEditor(QGroupBox):
self.flight.use_custom_loadout = self.isChecked()
if not self.isChecked():
for i in self.findChildren(QPylonEditor):
i.default_loadout(i.pylon.number)
i.default_loadout()

View File

@@ -223,7 +223,7 @@ class FactionSelection(QtWidgets.QWizardPage):
self.redFactionSelect.activated.connect(self.updateUnitRecap)
def setDefaultFactions(self, campaign: Campaign):
""" Set default faction for selected campaign """
"""Set default faction for selected campaign"""
self.blueFactionSelect.clear()
self.redFactionSelect.clear()

View File

@@ -213,14 +213,14 @@ class QSettingsWindow(QDialog):
self.player_income = TenthsSpinSlider(
"Player income multiplier",
1,
0,
50,
int(self.game.settings.player_income_multiplier * 10),
)
self.player_income.spinner.valueChanged.connect(self.applySettings)
self.enemy_income = TenthsSpinSlider(
"Enemy income multiplier",
1,
0,
50,
int(self.game.settings.enemy_income_multiplier * 10),
)

View File

@@ -17,26 +17,22 @@ local unitPayloads = {
["num"] = 6,
},
[4] = {
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
["CLSID"] = "{AN_ASQ_228}",
["num"] = 4,
},
[5] = {
["CLSID"] = "{A111396E-D3E8-4b9c-8AC9-2432489304D5}",
["num"] = 5,
},
[6] = {
["CLSID"] = "LAU_117_AGM_65F",
["num"] = 3,
},
[7] = {
[6] = {
["CLSID"] = "LAU_117_AGM_65F",
["num"] = 2,
},
[8] = {
[7] = {
["CLSID"] = "{5CE2FF2A-645A-4197-B48D-8720AC69394F}",
["num"] = 1,
},
[9] = {
[8] = {
["CLSID"] = "{5CE2FF2A-645A-4197-B48D-8720AC69394F}",
["num"] = 9,
},
@@ -61,26 +57,22 @@ local unitPayloads = {
["num"] = 6,
},
[4] = {
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
["CLSID"] = "{AN_ASQ_228}",
["num"] = 4,
},
[5] = {
["CLSID"] = "{A111396E-D3E8-4b9c-8AC9-2432489304D5}",
["num"] = 5,
},
[6] = {
["CLSID"] = "{F16A4DE0-116C-4A71-97F0-2CF85B0313EC}",
["num"] = 3,
},
[7] = {
[6] = {
["CLSID"] = "{F16A4DE0-116C-4A71-97F0-2CF85B0313EC}",
["num"] = 2,
},
[8] = {
[7] = {
["CLSID"] = "{5CE2FF2A-645A-4197-B48D-8720AC69394F}",
["num"] = 1,
},
[9] = {
[8] = {
["CLSID"] = "{5CE2FF2A-645A-4197-B48D-8720AC69394F}",
["num"] = 9,
},
@@ -137,38 +129,34 @@ local unitPayloads = {
["name"] = "STRIKE",
["pylons"] = {
[1] = {
["CLSID"] = "{BRU55_2*GBU-38}",
["num"] = 7,
},
[2] = {
["CLSID"] = "{BRU33_2X_MK-83}",
["num"] = 8,
},
[3] = {
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
["num"] = 6,
},
[4] = {
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
["num"] = 4,
},
[5] = {
["CLSID"] = "{FPU_8A_FUEL_TANK}",
["num"] = 5,
},
[6] = {
["CLSID"] = "{BRU55_2*GBU-38}",
["num"] = 3,
},
[7] = {
["CLSID"] = "{BRU33_2X_MK-83}",
["num"] = 2,
},
[8] = {
["CLSID"] = "{5CE2FF2A-645A-4197-B48D-8720AC69394F}",
["num"] = 1,
},
[9] = {
[2] = {
["CLSID"] = "{GBU_31_V_2B}",
["num"] = 2,
},
[3] = {
["CLSID"] = "{FPU_8A_FUEL_TANK}",
["num"] = 3,
},
[4] = {
["CLSID"] = "{AN_ASQ_228}",
["num"] = 4,
},
[5] = {
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
["num"] = 6,
},
[6] = {
["CLSID"] = "{FPU_8A_FUEL_TANK}",
["num"] = 7,
},
[7] = {
["CLSID"] = "{GBU_31_V_2B}",
["num"] = 8,
},
[8] = {
["CLSID"] = "{5CE2FF2A-645A-4197-B48D-8720AC69394F}",
["num"] = 9,
},
@@ -189,30 +177,26 @@ local unitPayloads = {
["num"] = 7,
},
[3] = {
["CLSID"] = "{A111396E-D3E8-4b9c-8AC9-2432489304D5}",
["num"] = 5,
},
[4] = {
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
["num"] = 6,
},
[5] = {
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
[4] = {
["CLSID"] = "{AN_ASQ_228}",
["num"] = 4,
},
[6] = {
[5] = {
["CLSID"] = "{5CE2FF2A-645A-4197-B48D-8720AC69394F}",
["num"] = 1,
},
[7] = {
[6] = {
["CLSID"] = "{5CE2FF2A-645A-4197-B48D-8720AC69394F}",
["num"] = 9,
},
[8] = {
[7] = {
["CLSID"] = "{AGM_84D}",
["num"] = 3,
},
[9] = {
[8] = {
["CLSID"] = "{AGM_84D}",
["num"] = 2,
},

View File

@@ -33,7 +33,7 @@
],
"logistics_units": [
"Truck_Bedford",
"CCKW_353"
"Truck_GMC_Jimmy_6x6_Truck"
],
"infantry_units": [
"Infantry_SMLE_No_4_Mk_1",

View File

@@ -27,6 +27,7 @@
],
"artillery_units": [
"MLRS_9A52_Smerch_HE_300mm",
"PLZ_05",
"SPH_2S9_Nona_120mm_M"
],
"logistics_units": [

View File

@@ -22,7 +22,8 @@
"MBT_M60A3_Patton",
"IFV_BMP_1",
"SPAAA_ZSU_23_4_Shilka_Gun_Dish",
"SPAAA_ZSU_57_2"
"SPAAA_ZSU_57_2",
"SPAAA_ZU_23_2_Mounted_Ural_375"
],
"artillery_units": [
"MLRS_BM_21_Grad_122mm",
@@ -45,7 +46,7 @@
"ZSU57Generator",
"ZSU23Generator",
"ZU23Generator",
"ZU23UralGenerator"
"ColdWarFlakGenerator"
],
"ewrs": [
"TallRackGenerator"

View File

@@ -18,7 +18,8 @@
"APC_MTLB",
"MBT_T_55",
"SPAAA_ZU_23_2_Mounted_Ural_375",
"AAA_8_8cm_Flak_18"
"AAA_8_8cm_Flak_18",
"AAA_S_60_57mm"
],
"artillery_units": [
"MLRS_BM_21_Grad_122mm"

View File

@@ -24,7 +24,8 @@
"IFV_BMP_1",
"MBT_T_55",
"SPAAA_ZU_23_2_Mounted_Ural_375",
"SPAAA_ZSU_57_2"
"SPAAA_ZSU_57_2",
"AAA_S_60_57mm"
],
"artillery_units": [
"MLRS_BM_21_Grad_122mm"

View File

@@ -19,7 +19,7 @@
],
"logistics_units": [
"Truck_Bedford",
"CCKW_353"
"Truck_GMC_Jimmy_6x6_Truck"
],
"infantry_units": [
"Infantry_SMLE_No_4_Mk_1"

View File

@@ -9,7 +9,7 @@
],
"frontline_units": [
"IFV_Sd_Kfz_234_2_Puma",
"APC_Sd_Kfz_251_Halftrack_Halftrack",
"APC_Sd_Kfz_251_Halftrack",
"MT_PzIV_H",
"MT_M4_Sherman",
"AAA_40mm_Bofors"

View File

@@ -21,7 +21,8 @@
"MT_PzIV_H",
"MBT_T_55",
"SPAAA_ZU_23_2_Mounted_Ural_375",
"SPAAA_ZSU_57_2"
"SPAAA_ZSU_57_2",
"AAA_S_60_57mm"
],
"artillery_units": [
"MLRS_BM_21_Grad_122mm"

View File

@@ -23,7 +23,8 @@
"MT_PzIV_H",
"SPG_StuG_III_Ausf__G",
"SPG_Jagdpanzer_IV",
"SPAAA_ZSU_57_2"
"SPAAA_ZSU_57_2",
"AAA_S_60_57mm"
],
"artillery_units": [
"MLRS_BM_21_Grad_122mm"

View File

@@ -21,7 +21,8 @@
"APC_MTLB",
"MBT_T_55",
"SPAAA_ZU_23_2_Mounted_Ural_375",
"SPAAA_ZSU_57_2"
"SPAAA_ZSU_57_2",
"AAA_S_60_57mm"
],
"artillery_units": [
"MLRS_BM_21_Grad_122mm"

View File

@@ -23,7 +23,8 @@
"MBT_T_55",
"MBT_T_72B",
"SPAAA_ZU_23_2_Mounted_Ural_375",
"SPAAA_ZSU_57_2"
"SPAAA_ZSU_57_2",
"AAA_S_60_57mm"
],
"artillery_units": [
"MLRS_BM_21_Grad_122mm"

View File

@@ -29,7 +29,7 @@
],
"logistics_units": [
"Truck_Bedford",
"CCKW_353"
"Truck_GMC_Jimmy_6x6_Truck"
],
"infantry_units": [
"Infantry_SMLE_No_4_Mk_1"

View File

@@ -25,7 +25,7 @@
"SPG_M12_GMC_155mm"
],
"logistics_units": [
"CCKW_353"
"Truck_GMC_Jimmy_6x6_Truck"
],
"infantry_units": [
"Infantry_M1_Garand"

View File

@@ -21,7 +21,7 @@
"frontline_units": [
"MBT_M60A3_Patton",
"APC_M113",
"APC_M1025_HMMWV",
"APC_HMMWV__Scout",
"SPAAA_Vulcan_M163"
],
"artillery_units": [

View File

@@ -0,0 +1,58 @@
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- configuration file for the LotATC Export script
--
-- This configuration is tailored for a mission generated by DCS Liberation
-- see https://github.com/Khopa/dcs_liberation
-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- LotATC Export plugin - configuration
logger:info("DCSLiberation|LotATC Export plugin - configuration")
local function discoverLotAtcDrawingsPath()
-- establish a search pattern into the following modes
-- 1. Environment variable LOTATC_DRAWINGS_DIR, to support server exporting with auto load from LotATC
-- 2. DCS saved games folder as configured in DCS Liberation
local drawingEnvDir = os.getenv("LOTATC_DRAWINGS_DIR")
if drawingEnvDir then
return drawingEnvDir
else
return lfs.writedir()..[[\Mods\services\LotAtc\userdb\drawings\]]
end
end
if dcsLiberation then
logger:info("DCSLiberation|LotATC Export plugin - configuration dcsLiberation")
local exportRedAA = true
local exportBlueAA = false
local exportSymbols = true
-- retrieve specific options values
if dcsLiberation.plugins then
logger:info("DCSLiberation|LotATC Export plugin - configuration dcsLiberation.plugins")
if dcsLiberation.plugins.lotatc then
logger:info("DCSLiberation|LotATC Export plugin - dcsLiberation.plugins.lotatcExport")
exportRedAA = dcsLiberation.plugins.lotatc.exportRedAA
logger:info(string.format("DCSLiberation|LotATC Export plugin - exportRedAA = %s",tostring(exportRedAA)))
exportBlueAA = dcsLiberation.plugins.lotatc.exportBlueAA
logger:info(string.format("DCSLiberation|LotATC Export plugin - exportBlueAA = %s",tostring(exportBlueAA)))
exportBlueAA = dcsLiberation.plugins.lotatc.exportSymbols
logger:info(string.format("DCSLiberation|LotATC Export plugin - exportSymbols = %s",tostring(exportSymbols)))
end
end
-- actual configuration code
if LotAtcExportConfig then
LotAtcExportConfig.exportRedAA = exportRedAA
LotAtcExportConfig.exportBlueAA = exportBlueAA
LotAtcExportConfig.exportSymbols = exportSymbols
LotAtcExportConfig.drawingBasePath = discoverLotAtcDrawingsPath()
LotatcExport()
end
end

View File

@@ -0,0 +1,253 @@
--[[
Export script for LotATC drawings
Allows to export certain DCS Liberation objects as predefined drawing in LotATC.
This script runs at mission startup and generates a drawing JSON file to be imported
in LotATC.
]]
LotAtcExportConfig = {
["exportRedAA"] = false,
["exportBlueAA"] = false,
["exportSymbols"] = false,
["exportVersion"] = "2.2.0",
["drawingBasePath"] = nil,
["redColor"] = "#7FE32000",
["blueColor"] = "#7F0084FF"
}
local function factionName(isFriend)
if isFriend then
return "BLUE"
else
return "RED"
end
end
local function uuid()
local random = math.random
local template ='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
return string.gsub(template, '[xy]', function (c)
local v = (c == 'x') and random(0, 0xf) or random(8, 0xb)
return string.format('%x', v)
end)
end
local function ends_with(str, ending)
return ending == "" or str:sub(-#ending) == ending
end
local function combine(path1, path2)
if not ends_with(path1, "\\") then
path1 = path1 .. "\\"
end
return path1 .. path2
end
local function lotatcExport_get_aa_nato_name(unit, isFriend)
if not redIADS or not blueIADS then
return nil
end
-- logger:info(string.format("DCSLiberation|LotATC Export plugin - try get NATO name for unit %s", unit.dcsGroupName))
local iads = redIADS
if isFriend then
iads = blueIADS
end
local samSite = iads:getSAMSiteByGroupName(unit.dcsGroupName)
if samSite and samSite.natoName then
-- logger:info(string.format("DCSLiberation|LotATC Export plugin - NATO name is %s", samSite.natoName))
return samSite.natoName
else
return nil
end
end
local function lotatcExport_get_name(unit, isFriend)
local classification = "SAM"
if string.find(unit.dcsGroupName, "|EWR|", 1, true) then
classification = "EWR"
elseif string.find(unit.dcsGroupName, "|AA", 1, true) then
classification = "AAA"
end
local natoName = lotatcExport_get_aa_nato_name(unit, isFriend)
local name = nil
if not natoName then
name = string.format("%s|%s", unit.name, classification)
else
name = string.format("%s|%s|%s", unit.name, classification, natoName)
end
return name, classification
end
local function lotatc_write_json(filename, json)
logger:info(string.format("DCSLiberation|LotATC Export plugin - writing %s", filename))
local function Write()
local fp = io.open(filename, 'w')
if fp then
fp:write(json)
fp:close()
end
end
if pcall(Write) then
else
logger:error("Unable to write LotATC export file to %s", filename)
end
end
local function lotatcExport_threat_circles_for_faction(faction, color, isFriend)
local drawings = {}
for _,aa in pairs(faction) do
logger:info(string.format("DCSLiberation|LotATC Export plugin - exporting threat circle for %s", aa.dcsGroupName))
local convLat, convLon = coord.LOtoLL({x = aa.positionX, y = 0, z = aa.positionY})
local name = lotatcExport_get_name(aa, isFriend)
table.insert(drawings,
{
["author"] = "DCSLiberation",
["brushStyle"] = 1,
["color"] = color,
["colorBg"] = "#00000000",
["id"] = string.format("{%s}", uuid()),
["longitude"] = convLon,
["latitude"] = convLat,
["radius"] = tonumber(aa.range),
["lineWidth"] = 2,
["name"] = name,
["shared"] = true,
["timestamp"] = "",
["type"] = "circle",
["text"] = name,
["font"] = {
["color"] = color,
["font"] = "Lato"
}
})
end
local lotatcData = {
["name"] = "Threat Circles " .. factionName(isFriend),
["enable"] = "true",
["version"] = LotAtcExportConfig.exportVersion,
["drawings"] = drawings
}
local drawings_json = json:encode(lotatcData)
return drawings_json
end
local function lotatcExport_symbols_for_faction(faction, color, isFriend)
local drawings = {}
for _,aa in pairs(faction) do
logger:info(string.format("DCSLiberation|LotATC Export plugin - exporting AA symbol for %s", aa.dcsGroupName))
local convLat, convLon = coord.LOtoLL({x = aa.positionX, y = 0, z = aa.positionY})
local name = lotatcExport_get_name(aa, isFriend)
local classification = "hostile"
if isFriend then
classification = "friend"
end
local sub_dimension = "none"
if string.find(aa.dcsGroupName, "|EWR|", 1, true) then
sub_dimension = "ew"
end
table.insert(drawings,
{
["author"] = "DCSLiberation",
["brushStyle"] = 1,
["classification"] = {
["classification"] = classification,
["dimension"] = "land_unit",
["sub_dimension"] = sub_dimension
},
["color"] = color,
["colorBg"] = "#33FF0000",
["font"] = {
["color"] = color,
["font"] = "Lato"
},
["id"] = string.format("{%s}", uuid()),
["longitude"] = convLon,
["latitude"] = convLat,
["lineWidth"] = 2,
["name"] = name,
["shared"] = true,
["timestamp"] = "",
["type"] = "symbol",
["text"] = name
})
end
local lotatcData = {
["name"] = "Threat Symbols " .. factionName(isFriend),
["enable"] = "true",
["version"] = LotAtcExportConfig.exportVersion,
["drawings"] = drawings,
}
local drawings_json = json:encode(lotatcData)
return drawings_json
end
local function lotatc_export_faction(faction, color, factionPath, isFriend)
local exportBasePathFaction = combine(LotAtcExportConfig.drawingBasePath, factionPath)
lfs.mkdir(exportBasePathFaction)
local exportFileName = combine(exportBasePathFaction, "threatZones.json")
local json = lotatcExport_threat_circles_for_faction(faction, color, isFriend)
lotatc_write_json(exportFileName, json)
if LotAtcExportConfig.exportSymbols then
exportFileName = combine(exportBasePathFaction, "threatSymbols.json")
json = lotatcExport_symbols_for_faction(faction, color, isFriend);
lotatc_write_json(exportFileName, json)
end
end
function LotatcExport()
if not json then
local message = "Unable to export LotATC drawings, JSON library is not loaded!"
logger:error(message)
return
end
if not LotAtcExportConfig.drawingBasePath then
local message = "No writable export path for LotATC drawings. Set environment variable LOTATC_DRAWINGS_DIR pointing to your export path."
logger:error(message)
return
end
local message = "Export LotATC drawings to "..LotAtcExportConfig.drawingBasePath
logger:info(message)
-- The RED AA is exported to the blue folder and vice versa. If a BLUE GCI connects he/she
-- wants to see the RED AA.
if LotAtcExportConfig.exportRedAA then
lotatc_export_faction(dcsLiberation.RedAA, LotAtcExportConfig.redColor, [[blue\]], false)
end
if LotAtcExportConfig.exportBlueAA then
lotatc_export_faction(dcsLiberation.BlueAA, LotAtcExportConfig.blueColor, [[red\]], true)
end
end

View File

@@ -0,0 +1,33 @@
{
"nameInUI": "LotATC Export",
"defaultValue": false,
"specificOptions": [
{
"nameInUI": "Export RED AA",
"mnemonic": "exportRedAA",
"defaultValue": true
},
{
"nameInUI": "Export BLUE AA",
"mnemonic": "exportBlueAA",
"defaultValue": false
},
{
"nameInUI": "Export AA Symbols",
"mnemonic": "exportSymbols",
"defaultValue": true
}
],
"scriptsWorkOrders": [
{
"file": "LotAtcExport.lua",
"mnemonic": "LotAtcExport-script"
}
],
"configurationWorkOrders": [
{
"file": "LotAtcExport-config.lua",
"mnemonic": "LotAtcExport-config"
}
]
}

View File

@@ -1,8 +1,9 @@
[
"base",
"jtacautolase",
"skynetiads",
"ewrs",
"herculescargo",
"splashdamage"
]
[
"base",
"jtacautolase",
"skynetiads",
"ewrs",
"herculescargo",
"splashdamage",
"lotatc"
]

BIN
resources/ui/fob.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 881 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1011 B