Merge branch 'develop' into helipads

# Conflicts:
#	game/data/weapons.py
#	game/db.py
#	game/theater/conflicttheater.py
#	resources/factions/france_1995.json
#	resources/factions/insurgents.json
#	resources/factions/iraq_1991.json
#	resources/factions/syria_1967_with_ww2_weapons.json
#	resources/factions/syria_2011.json
This commit is contained in:
Khopa 2021-05-21 13:58:22 +02:00
commit f31861441b
55 changed files with 345 additions and 132 deletions

View File

@ -9,6 +9,8 @@ assignees: ''
Before filing, please search the issue tracker to see if the issue has already been reported. Before filing, please search the issue tracker to see if the issue has already been reported.
If reporting a DCS AI bug, check https://github.com/dcs-liberation/dcs_liberation#dcs-bugs.
**Describe the bug** **Describe the bug**
A clear and concise description of what the bug is. A clear and concise description of what the bug is.

View File

@ -9,6 +9,8 @@ assignees: ''
Before filing, please search the issue tracker to see if this feature has already been requested. Before filing, please search the issue tracker to see if this feature has already been requested.
If requesting a DCS AI feature, check If reporting a DCS AI bug, check https://github.com/dcs-liberation/dcs_liberation#dcs-bugs.
**Is your feature request related to a problem? Please describe.** **Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

View File

@ -22,6 +22,14 @@ Latest release is available here : https://github.com/dcs-liberation/dcs_liberat
To download preview builds of the next version of DCS Liberation, see https://github.com/dcs-liberation/dcs_liberation/wiki/Preview-builds. To download preview builds of the next version of DCS Liberation, see https://github.com/dcs-liberation/dcs_liberation/wiki/Preview-builds.
## DCS bugs
These DCS bugs prevent us from improving AI behavior. Please upvote them! (But please
_don't_ spam them with comments):
* [A2A and SEAD escorts don't escort](https://forums.eagle.ru/topic/251798-options-for-alternate-ai-escort-behavior/?tab=comments#comment-4668033)
* [DEAD can't use mixed loadouts effectively](https://forums.eagle.ru/topic/271941-ai-rtbs-after-firing-decoys-despite-full-load-of-bombs/)
## Bugs and feature requests ## Bugs and feature requests
If you need to report a bug or want to suggest a new feature, you can do this on our [bug tracker](https://github.com/dcs-liberation/dcs_liberation/issues). In either case, please use the search bar at the top of the page to see if it has already been reported. Note that you may need to remove the filter for open bugs if it's something we've recently fixed. If you need to report a bug or want to suggest a new feature, you can do this on our [bug tracker](https://github.com/dcs-liberation/dcs_liberation/issues). In either case, please use the search bar at the top of the page to see if it has already been reported. Note that you may need to remove the filter for open bugs if it's something we've recently fixed.

View File

@ -7,10 +7,13 @@ Saves from 2.5 are not compatible with 3.0.
* **[Campaign]** Ground units can now be transferred by road, airlift, and cargo ship. See https://github.com/dcs-liberation/dcs_liberation/wiki/Unit-Transfers for more information. * **[Campaign]** Ground units can now be transferred by road, airlift, and cargo ship. See https://github.com/dcs-liberation/dcs_liberation/wiki/Unit-Transfers for more information.
* **[Campaign]** Ground units can no longer be sold. To move units to a new location, transfer them. * **[Campaign]** Ground units can no longer be sold. To move units to a new location, transfer them.
* **[Campaign]** Ground units must now be recruited at a base with a factory and transferred to their destination. When buying units in the UI, the purchase will automatically be fulfilled at the closest factory, and a transfer will be created on the next turn. * **[Campaign]** Ground units must now be recruited at a base with a factory and transferred to their destination. When buying units in the UI, the purchase will automatically be fulfilled at the closest factory, and a transfer will be created on the next turn.
* **[Campaign]** Non-control point FOBs will no longer spawn.
* **[Campaign AI]** Every 30 minutes the AI will plan a CAP, so players can customize their mission better. * **[Campaign AI]** Every 30 minutes the AI will plan a CAP, so players can customize their mission better.
* **[Campaign AI]** AI now considers Ju-88s for CAS, strike, and DEAD missions. * **[Campaign AI]** AI now considers Ju-88s for CAS, strike, and DEAD missions.
* **[Campaign AI]** Fix purchase of aircraft by priority (the faction's list was being used as the priority list rather than the game's). * **[Campaign AI]** Fix purchase of aircraft by priority (the faction's list was being used as the priority list rather than the game's).
* **[Flight Planner]** AI strike flight plans now include the correct target actions for building groups. * **[Flight Planner]** AI strike flight plans now include the correct target actions for building groups.
* **[Flight Planner]** Flight plans now include bullseye waypoints.
* **[Kneeboard]** ATC table overflow alleviated by wrapping long airfield names and splitting ATC frequency and channel into separate rows.
* **[UI]** Added new web based map UI. This is mostly functional but many of the old display options are a WIP. Revert to the old map with --old-map. * **[UI]** Added new web based map UI. This is mostly functional but many of the old display options are a WIP. Revert to the old map with --old-map.
* **[UI]** Campaigns generated for an older or newer version of the game will now be marked as incompatible. They can still be played, but bugs may be present. * **[UI]** Campaigns generated for an older or newer version of the game will now be marked as incompatible. They can still be played, but bugs may be present.
* **[UI]** DCS loadouts are now selectable in the loadout setup menu. * **[UI]** DCS loadouts are now selectable in the loadout setup menu.

View File

@ -8,7 +8,6 @@ DEFAULT_AVAILABLE_BUILDINGS = [
"oil", "oil",
"ware", "ware",
"farp", "farp",
"fob",
"power", "power",
"derrick", "derrick",
] ]
@ -21,7 +20,6 @@ WW2_GERMANY_BUILDINGS = [
"ww2bunker", "ww2bunker",
"allycamp", "allycamp",
"allycamp", "allycamp",
"fob",
] ]
WW2_ALLIES_BUILDINGS = [ WW2_ALLIES_BUILDINGS = [
"fuel", "fuel",
@ -30,7 +28,6 @@ WW2_ALLIES_BUILDINGS = [
"allycamp", "allycamp",
"allycamp", "allycamp",
"allycamp", "allycamp",
"fob",
] ]
FORTIFICATION_BUILDINGS = [ FORTIFICATION_BUILDINGS = [

View File

@ -145,20 +145,26 @@ _WEAPON_FALLBACKS = [
Weapons.LAU_117_with_AGM_65E___Maverick_E__Laser_ASM___Lg_Whd_, Weapons.LAU_117_with_AGM_65E___Maverick_E__Laser_ASM___Lg_Whd_,
), # internal pylons harrier ), # internal pylons harrier
# AGM-154 JSOW # AGM-154 JSOW
(Weapons.AGM_154A___JSOW_CEB__CBU_type_, Weapons.GBU_12), (
Weapons.AGM_154A___JSOW_CEB__CBU_type_,
Weapons.AGM_62_Walleye_II___Guided_Weapon_Mk_5__TV_Guided_,
),
( (
Weapons.BRU_55_with_2_x_AGM_154A___JSOW_CEB__CBU_type_, Weapons.BRU_55_with_2_x_AGM_154A___JSOW_CEB__CBU_type_,
Weapons.BRU_33_with_2_x_GBU_12___500lb_Laser_Guided_Bomb, Weapons.AGM_62_Walleye_II___Guided_Weapon_Mk_5__TV_Guided_,
), ),
( (
Weapons.BRU_57_with_2_x_AGM_154A___JSOW_CEB__CBU_type_, Weapons.BRU_57_with_2_x_AGM_154A___JSOW_CEB__CBU_type_,
None, Weapons.AGM_62_Walleye_II___Guided_Weapon_Mk_5__TV_Guided_,
), # doesn't exist on any aircraft yet ), # doesn't exist on any aircraft yet
(Weapons.AGM_154B___JSOW_Anti_Armour, Weapons.CBU_105___10_x_SFW__CBU_with_WCMD), (Weapons.AGM_154B___JSOW_Anti_Armour, Weapons.CBU_105___10_x_SFW__CBU_with_WCMD),
(Weapons.AGM_154C___JSOW_Unitary_BROACH, Weapons.GBU_12), (
Weapons.AGM_154C___JSOW_Unitary_BROACH,
Weapons.AGM_62_Walleye_II___Guided_Weapon_Mk_5__TV_Guided_,
),
( (
Weapons.BRU_55_with_2_x_AGM_154C___JSOW_Unitary_BROACH, Weapons.BRU_55_with_2_x_AGM_154C___JSOW_Unitary_BROACH,
Weapons.BRU_33_with_2_x_GBU_12___500lb_Laser_Guided_Bomb, Weapons.AGM_62_Walleye_II___Guided_Weapon_Mk_5__TV_Guided_,
), ),
# AGM-45 Shrike # AGM-45 Shrike
(Weapons.AGM_45A_Shrike_ARM, None), (Weapons.AGM_45A_Shrike_ARM, None),

View File

@ -498,12 +498,14 @@ PRICES = {
Armor.IFV_BMP_1: 14, Armor.IFV_BMP_1: 14,
Armor.IFV_BMP_2: 16, Armor.IFV_BMP_2: 16,
Armor.IFV_BMP_3: 18, Armor.IFV_BMP_3: 18,
Armor.LT_PT_76: 9,
Armor.ZBD_04A: 12, Armor.ZBD_04A: 12,
Armor.ZTZ_96B: 30, Armor.ZTZ_96B: 30,
Armor.Scout_Cobra: 4, Armor.Scout_Cobra: 4,
Armor.APC_M113: 6, Armor.APC_M113: 6,
Armor.Scout_HMMWV: 2, Armor.Scout_HMMWV: 2,
Armor.ATGM_HMMWV: 8, Armor.ATGM_HMMWV: 8,
Armor.ATGM_VAB_Mephisto: 12,
Armor.IFV_M2A2_Bradley: 12, Armor.IFV_M2A2_Bradley: 12,
Armor.IFV_M1126_Stryker_ICV: 10, Armor.IFV_M1126_Stryker_ICV: 10,
Armor.SPG_Stryker_MGS: 14, Armor.SPG_Stryker_MGS: 14,
@ -519,6 +521,7 @@ PRICES = {
Armor.MBT_Merkava_IV: 25, Armor.MBT_Merkava_IV: 25,
Armor.APC_TPz_Fuchs: 5, Armor.APC_TPz_Fuchs: 5,
Armor.MBT_Challenger_II: 25, Armor.MBT_Challenger_II: 25,
Armor.MBT_Chieftain_Mk_3: 20,
Armor.IFV_Marder: 10, Armor.IFV_Marder: 10,
Armor.IFV_Warrior: 10, Armor.IFV_Warrior: 10,
Armor.IFV_LAV_25: 7, Armor.IFV_LAV_25: 7,
@ -534,6 +537,7 @@ PRICES = {
Artillery.Mortar_2B11_120mm: 4, Artillery.Mortar_2B11_120mm: 4,
Artillery.SPH_Dana_vz77_152mm: 26, Artillery.SPH_Dana_vz77_152mm: 26,
Artillery.PLZ_05: 25, Artillery.PLZ_05: 25,
Artillery.SPH_T155_Firtina_155mm: 28,
Unarmed.LUV_UAZ_469_Jeep: 3, Unarmed.LUV_UAZ_469_Jeep: 3,
Unarmed.Truck_Ural_375: 3, Unarmed.Truck_Ural_375: 3,
Infantry.Infantry_M4: 1, Infantry.Infantry_M4: 1,
@ -885,6 +889,7 @@ UNIT_BY_TASK = {
Armor.IFV_BMP_3, Armor.IFV_BMP_3,
Armor.IFV_BMP_3, Armor.IFV_BMP_3,
Armor.IFV_BMD_1, Armor.IFV_BMD_1,
Armor.LT_PT_76,
Armor.ZBD_04A, Armor.ZBD_04A,
Armor.ZBD_04A, Armor.ZBD_04A,
Armor.ZBD_04A, Armor.ZBD_04A,
@ -913,6 +918,8 @@ UNIT_BY_TASK = {
Armor.APC_TPz_Fuchs, Armor.APC_TPz_Fuchs,
Armor.ATGM_HMMWV, Armor.ATGM_HMMWV,
Armor.ATGM_HMMWV, Armor.ATGM_HMMWV,
Armor.ATGM_VAB_Mephisto,
Armor.ATGM_VAB_Mephisto,
Armor.Scout_HMMWV, Armor.Scout_HMMWV,
Armor.Scout_HMMWV, Armor.Scout_HMMWV,
Armor.IFV_M2A2_Bradley, Armor.IFV_M2A2_Bradley,
@ -941,6 +948,7 @@ UNIT_BY_TASK = {
Armor.MBT_Leclerc, Armor.MBT_Leclerc,
Armor.MBT_Leopard_2A6M, Armor.MBT_Leopard_2A6M,
Armor.MBT_Challenger_II, Armor.MBT_Challenger_II,
Armor.MBT_Chieftain_Mk_3,
Armor.MBT_Merkava_IV, Armor.MBT_Merkava_IV,
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G, Armor.MT_Pz_Kpfw_V_Panther_Ausf_G,
Armor.Tk_PzIV_H, Armor.Tk_PzIV_H,
@ -1002,6 +1010,7 @@ UNIT_BY_TASK = {
Artillery.MLRS_9K57_Uragan_BM_27_220mm, Artillery.MLRS_9K57_Uragan_BM_27_220mm,
Artillery.MLRS_9A52_Smerch_HE_300mm, Artillery.MLRS_9A52_Smerch_HE_300mm,
Artillery.SPH_Dana_vz77_152mm, Artillery.SPH_Dana_vz77_152mm,
Artillery.SPH_T155_Firtina_155mm,
Artillery.PLZ_05, Artillery.PLZ_05,
Artillery.SPG_M12_GMC_155mm, Artillery.SPG_M12_GMC_155mm,
Armor.SPG_Sturmpanzer_IV_Brummbar, Armor.SPG_Sturmpanzer_IV_Brummbar,
@ -1241,7 +1250,6 @@ REWARDS = {
"fuel": 2, "fuel": 2,
"ammo": 2, "ammo": 2,
"farp": 1, "farp": 1,
"fob": 1,
# TODO: Should generate no cash once they generate units. # TODO: Should generate no cash once they generate units.
"factory": 10, "factory": 10,
"comms": 10, "comms": 10,

View File

@ -34,6 +34,7 @@ from .procurement import AircraftProcurementRequest, ProcurementAi
from .profiling import logged_duration from .profiling import logged_duration
from .settings import Settings from .settings import Settings
from .theater import ConflictTheater from .theater import ConflictTheater
from .theater.bullseye import Bullseye
from .theater.transitnetwork import TransitNetwork, TransitNetworkBuilder from .theater.transitnetwork import TransitNetwork, TransitNetworkBuilder
from .threatzones import ThreatZones from .threatzones import ThreatZones
from .transfers import PendingTransfers from .transfers import PendingTransfers
@ -130,6 +131,9 @@ class Game:
self.blue_ato = AirTaskingOrder() self.blue_ato = AirTaskingOrder()
self.red_ato = AirTaskingOrder() self.red_ato = AirTaskingOrder()
self.blue_bullseye = Bullseye(Point(0, 0))
self.red_bullseye = Bullseye(Point(0, 0))
self.aircraft_inventory = GlobalAircraftInventory(self.theater.controlpoints) self.aircraft_inventory = GlobalAircraftInventory(self.theater.controlpoints)
self.transfers = PendingTransfers(self) self.transfers = PendingTransfers(self)
@ -201,6 +205,11 @@ class Game:
return self.player_faction return self.player_faction
return self.enemy_faction return self.enemy_faction
def bullseye_for(self, player: bool) -> Bullseye:
if player:
return self.blue_bullseye
return self.red_bullseye
def _roll(self, prob, mult): def _roll(self, prob, mult):
if self.settings.version == "dev": if self.settings.version == "dev":
# always generate all events for dev # always generate all events for dev
@ -337,10 +346,17 @@ class Game:
return TurnState.CONTINUE return TurnState.CONTINUE
def set_bullseye(self) -> None:
player_cp, enemy_cp = self.theater.closest_opposing_control_points()
self.blue_bullseye = Bullseye(enemy_cp.position)
self.red_bullseye = Bullseye(player_cp.position)
def initialize_turn(self) -> None: def initialize_turn(self) -> None:
self.events = [] self.events = []
self._generate_events() self._generate_events()
self.set_bullseye()
# Update statistics # Update statistics
self.game_stats.update(self) self.game_stats.update(self)

View File

@ -108,8 +108,12 @@ class Operation:
@classmethod @classmethod
def _setup_mission_coalitions(cls): def _setup_mission_coalitions(cls):
cls.current_mission.coalition["blue"] = Coalition("blue") cls.current_mission.coalition["blue"] = Coalition(
cls.current_mission.coalition["red"] = Coalition("red") "blue", bullseye=cls.game.blue_bullseye.to_pydcs()
)
cls.current_mission.coalition["red"] = Coalition(
"red", bullseye=cls.game.red_bullseye.to_pydcs()
)
p_country = cls.game.player_country p_country = cls.game.player_country
e_country = cls.game.enemy_country e_country = cls.game.enemy_country
@ -171,13 +175,16 @@ class Operation:
gen.add_dynamic_runway(dynamic_runway) gen.add_dynamic_runway(dynamic_runway)
for tanker in airsupportgen.air_support.tankers: for tanker in airsupportgen.air_support.tankers:
gen.add_tanker(tanker) if tanker.blue:
gen.add_tanker(tanker)
for aewc in airsupportgen.air_support.awacs: for aewc in airsupportgen.air_support.awacs:
gen.add_awacs(aewc) if aewc.blue:
gen.add_awacs(aewc)
for jtac in jtacs: for jtac in jtacs:
gen.add_jtac(jtac) if jtac.blue:
gen.add_jtac(jtac)
for flight in airgen.flights: for flight in airgen.flights:
gen.add_flight(flight) gen.add_flight(flight)
@ -317,13 +324,8 @@ class Operation:
# Setup combined arms parameters # Setup combined arms parameters
cls.current_mission.groundControl.pilot_can_control_vehicles = cls.ca_slots > 0 cls.current_mission.groundControl.pilot_can_control_vehicles = cls.ca_slots > 0
if cls.game.player_country in [ cls.current_mission.groundControl.blue_tactical_commander = cls.ca_slots
country.name cls.current_mission.groundControl.blue_observer = 1
for country in cls.current_mission.coalition["blue"].countries.values()
]:
cls.current_mission.groundControl.blue_tactical_commander = cls.ca_slots
else:
cls.current_mission.groundControl.red_tactical_commander = cls.ca_slots
# Options # Options
forcedoptionsgen = ForcedOptionsGenerator(cls.current_mission, cls.game) forcedoptionsgen = ForcedOptionsGenerator(cls.current_mission, cls.game)
@ -453,7 +455,7 @@ class Operation:
for tanker in airsupportgen.air_support.tankers: for tanker in airsupportgen.air_support.tankers:
luaData["Tankers"][tanker.callsign] = { luaData["Tankers"][tanker.callsign] = {
"dcsGroupName": tanker.dcsGroupName, "dcsGroupName": tanker.group_name,
"callsign": tanker.callsign, "callsign": tanker.callsign,
"variant": tanker.variant, "variant": tanker.variant,
"radio": tanker.freq.mhz, "radio": tanker.freq.mhz,
@ -463,14 +465,14 @@ class Operation:
if airsupportgen.air_support.awacs: if airsupportgen.air_support.awacs:
for awacs in airsupportgen.air_support.awacs: for awacs in airsupportgen.air_support.awacs:
luaData["AWACs"][awacs.callsign] = { luaData["AWACs"][awacs.callsign] = {
"dcsGroupName": awacs.dcsGroupName, "dcsGroupName": awacs.group_name,
"callsign": awacs.callsign, "callsign": awacs.callsign,
"radio": awacs.freq.mhz, "radio": awacs.freq.mhz,
} }
for jtac in jtacs: for jtac in jtacs:
luaData["JTACs"][jtac.callsign] = { luaData["JTACs"][jtac.callsign] = {
"dcsGroupName": jtac.dcsGroupName, "dcsGroupName": jtac.group_name,
"callsign": jtac.callsign, "callsign": jtac.callsign,
"zone": jtac.region, "zone": jtac.region,
"dcsUnit": jtac.unit_name, "dcsUnit": jtac.unit_name,

26
game/theater/bullseye.py Normal file
View File

@ -0,0 +1,26 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Dict, TYPE_CHECKING
from dcs import Point
from game.theater import LatLon
if TYPE_CHECKING:
from game.theater import ConflictTheater
@dataclass
class Bullseye:
position: Point
@classmethod
def from_pydcs(cls, bulls: Dict[str, float]) -> Bullseye:
return cls(Point(bulls["x"], bulls["y"]))
def to_pydcs(self) -> Dict[str, float]:
return {"x": self.position.x, "y": self.position.y}
def to_lat_lon(self, theater: ConflictTheater) -> LatLon:
return theater.point_to_ll(self.position)

View File

@ -41,6 +41,7 @@ from dcs.unitgroup import (
) )
from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed
from .latlon import LatLon
from ..helipad import Helipad from ..helipad import Helipad
from ..scenery_group import SceneryGroup from ..scenery_group import SceneryGroup
from pyproj import CRS, Transformer from pyproj import CRS, Transformer
@ -583,15 +584,6 @@ class ReferencePoint:
image_coordinates: Point image_coordinates: Point
@dataclass(frozen=True)
class LatLon:
latitude: float
longitude: float
def as_list(self) -> List[float]:
return [self.latitude, self.longitude]
class ConflictTheater: class ConflictTheater:
terrain: Terrain terrain: Terrain

11
game/theater/latlon.py Normal file
View File

@ -0,0 +1,11 @@
from dataclasses import dataclass
from typing import List
@dataclass(frozen=True)
class LatLon:
latitude: float
longitude: float
def as_list(self) -> List[float]:
return [self.latitude, self.longitude]

View File

@ -845,12 +845,7 @@ class FobGroundObjectGenerator(AirbaseGroundObjectGenerator):
return True return True
def generate_fob(self) -> None: def generate_fob(self) -> None:
try: category = "fob"
category = self.faction.building_set[self.faction.building_set.index("fob")]
except IndexError:
logging.exception("Faction has no fob buildings defined")
return
obj_name = self.control_point.name obj_name = self.control_point.name
template = random.choice(list(self.templates[category].values())) template = random.choice(list(self.templates[category].values()))
point = self.control_point.position point = self.control_point.position

View File

@ -815,12 +815,13 @@ class AircraftConflictGenerator:
self.air_support.awacs.append( self.air_support.awacs.append(
AwacsInfo( AwacsInfo(
dcsGroupName=str(group.name), group_name=str(group.name),
callsign=callsign, callsign=callsign,
freq=channel, freq=channel,
depature_location=flight.departure.name, depature_location=flight.departure.name,
end_time=flight.flight_plan.mission_departure_time, end_time=flight.flight_plan.mission_departure_time,
start_time=flight.flight_plan.mission_start_time, start_time=flight.flight_plan.mission_start_time,
blue=flight.departure.captured,
) )
) )
@ -1275,7 +1276,7 @@ class AircraftConflictGenerator:
group, group,
react_on_threat=OptReactOnThreat.Values.EvadeFire, react_on_threat=OptReactOnThreat.Values.EvadeFire,
roe=OptROE.Values.OpenFire, roe=OptROE.Values.OpenFire,
rtb_winchester=OptRTBOnOutOfAmmo.Values.ASM, rtb_winchester=OptRTBOnOutOfAmmo.Values.All,
restrict_jettison=True, restrict_jettison=True,
) )
@ -1410,9 +1411,6 @@ class AircraftConflictGenerator:
flight: Flight, flight: Flight,
dynamic_runways: Dict[str, RunwayData], dynamic_runways: Dict[str, RunwayData],
) -> None: ) -> None:
# Escort groups are actually given the CAP task so they can perform the
# Search Then Engage task, which we have to use instead of the Escort
# task for the reasons explained in JoinPointBuilder.
group.task = Transport.name group.task = Transport.name
self._setup_group(group, package, flight, dynamic_runways) self._setup_group(group, package, flight, dynamic_runways)
self.configure_behavior( self.configure_behavior(
@ -1761,7 +1759,7 @@ class DeadIngressBuilder(PydcsWaypointBuilder):
if isinstance(target_group, TheaterGroundObject): if isinstance(target_group, TheaterGroundObject):
tgroup = self.mission.find_group(target_group.group_name) tgroup = self.mission.find_group(target_group.group_name)
if tgroup is not None: if tgroup is not None:
task = AttackGroup(tgroup.id, weapon_type=WeaponType.Guided) task = AttackGroup(tgroup.id, weapon_type=WeaponType.Auto)
task.params["expend"] = "All" task.params["expend"] = "All"
task.params["attackQtyLimit"] = False task.params["attackQtyLimit"] = False
task.params["directionEnabled"] = False task.params["directionEnabled"] = False
@ -1866,12 +1864,11 @@ class StrikeIngressBuilder(PydcsWaypointBuilder):
center.y += target.position.y center.y += target.position.y
center.x /= len(targets) center.x /= len(targets)
center.y /= len(targets) center.y /= len(targets)
bombing = Bombing(center) bombing = Bombing(center, weapon_type=WeaponType.Bombs)
bombing.params["expend"] = "All" bombing.params["expend"] = "All"
bombing.params["attackQtyLimit"] = False bombing.params["attackQtyLimit"] = False
bombing.params["directionEnabled"] = False bombing.params["directionEnabled"] = False
bombing.params["altitudeEnabled"] = False bombing.params["altitudeEnabled"] = False
bombing.params["weaponType"] = WeaponType.Bombs.value
bombing.params["groupAttack"] = True bombing.params["groupAttack"] = True
waypoint.tasks.append(bombing) waypoint.tasks.append(bombing)
return waypoint return waypoint
@ -1879,11 +1876,10 @@ class StrikeIngressBuilder(PydcsWaypointBuilder):
def build_strike(self) -> MovingPoint: def build_strike(self) -> MovingPoint:
waypoint = super().build() waypoint = super().build()
for target in self.waypoint.targets: for target in self.waypoint.targets:
bombing = Bombing(target.position) bombing = Bombing(target.position, weapon_type=WeaponType.Auto)
# If there is only one target, drop all ordnance in one pass. # If there is only one target, drop all ordnance in one pass.
if len(self.waypoint.targets) == 1: if len(self.waypoint.targets) == 1:
bombing.params["expend"] = "All" bombing.params["expend"] = "All"
bombing.params["weaponType"] = WeaponType.Auto.value
bombing.params["groupAttack"] = True bombing.params["groupAttack"] = True
waypoint.tasks.append(bombing) waypoint.tasks.append(bombing)
@ -1954,7 +1950,10 @@ class JoinPointBuilder(PydcsWaypointBuilder):
EngageTargets( EngageTargets(
# TODO: From doctrine. # TODO: From doctrine.
max_distance=int(nautical_miles(30).meters), max_distance=int(nautical_miles(30).meters),
targets=[Targets.All.Air.Planes.Fighters], targets=[
Targets.All.Air.Planes.Fighters,
Targets.All.Air.Planes.MultiroleFighters,
],
) )
) )
) )

View File

@ -35,23 +35,25 @@ AWACS_ALT = 13000
class AwacsInfo: class AwacsInfo:
"""AWACS information for the kneeboard.""" """AWACS information for the kneeboard."""
dcsGroupName: str group_name: str
callsign: str callsign: str
freq: RadioFrequency freq: RadioFrequency
depature_location: Optional[str] depature_location: Optional[str]
start_time: Optional[timedelta] start_time: Optional[timedelta]
end_time: Optional[timedelta] end_time: Optional[timedelta]
blue: bool
@dataclass @dataclass
class TankerInfo: class TankerInfo:
"""Tanker information for the kneeboard.""" """Tanker information for the kneeboard."""
dcsGroupName: str group_name: str
callsign: str callsign: str
variant: str variant: str
freq: RadioFrequency freq: RadioFrequency
tacan: TacanChannel tacan: TacanChannel
blue: bool
@dataclass @dataclass
@ -165,7 +167,14 @@ class AirSupportConflictGenerator:
tanker_group.points[0].tasks.append(SetImmortalCommand(True)) tanker_group.points[0].tasks.append(SetImmortalCommand(True))
self.air_support.tankers.append( self.air_support.tankers.append(
TankerInfo(str(tanker_group.name), callsign, variant, freq, tacan) TankerInfo(
str(tanker_group.name),
callsign,
variant,
freq,
tacan,
blue=True,
)
) )
if not self.game.settings.disable_legacy_aewc: if not self.game.settings.disable_legacy_aewc:
@ -196,12 +205,13 @@ class AirSupportConflictGenerator:
self.air_support.awacs.append( self.air_support.awacs.append(
AwacsInfo( AwacsInfo(
dcsGroupName=str(awacs_flight.name), group_name=str(awacs_flight.name),
callsign=callsign_for_support_unit(awacs_flight), callsign=callsign_for_support_unit(awacs_flight),
freq=freq, freq=freq,
depature_location=None, depature_location=None,
start_time=None, start_time=None,
end_time=None, end_time=None,
blue=True,
) )
) )
else: else:

View File

@ -68,11 +68,12 @@ INFANTRY_GROUP_SIZE = 5
class JtacInfo: class JtacInfo:
"""JTAC information.""" """JTAC information."""
dcsGroupName: str group_name: str
unit_name: str unit_name: str
callsign: str callsign: str
region: str region: str
code: str code: str
blue: bool
# TODO: Radio info? Type? # TODO: Radio info? Type?
@ -196,7 +197,14 @@ class GroundConflictGenerator:
# Note: Will need to change if we ever add ground based JTAC. # Note: Will need to change if we ever add ground based JTAC.
callsign = callsign_for_support_unit(jtac) callsign = callsign_for_support_unit(jtac)
self.jtacs.append( self.jtacs.append(
JtacInfo(str(jtac.name), n, callsign, frontline, str(code)) JtacInfo(
str(jtac.name),
n,
callsign,
frontline,
str(code),
blue=True,
)
) )
def gen_infantry_group_for_group( def gen_infantry_group_for_group(

View File

@ -0,0 +1,12 @@
from dcs.ships import FAC_La_Combattante_IIa
from game.factions.faction import Faction
from game.theater import TheaterGroundObject
from gen.fleet.dd_group import DDGroupGenerator
class LaCombattanteIIGroupGenerator(DDGroupGenerator):
def __init__(self, game, ground_object: TheaterGroundObject, faction: Faction):
super(LaCombattanteIIGroupGenerator, self).__init__(
game, ground_object, faction, FAC_La_Combattante_IIa
)

View File

@ -8,6 +8,7 @@ from gen.fleet.dd_group import (
ArleighBurkeGroupGenerator, ArleighBurkeGroupGenerator,
OliverHazardPerryGroupGenerator, OliverHazardPerryGroupGenerator,
) )
from gen.fleet.lacombattanteII import LaCombattanteIIGroupGenerator
from gen.fleet.lha_group import LHAGroupGenerator from gen.fleet.lha_group import LHAGroupGenerator
from gen.fleet.ru_dd_group import ( from gen.fleet.ru_dd_group import (
RussianNavyGroupGenerator, RussianNavyGroupGenerator,
@ -34,6 +35,7 @@ SHIP_MAP = {
"KiloSubGroupGenerator": KiloSubGroupGenerator, "KiloSubGroupGenerator": KiloSubGroupGenerator,
"TangoSubGroupGenerator": TangoSubGroupGenerator, "TangoSubGroupGenerator": TangoSubGroupGenerator,
"Type54GroupGenerator": Type54GroupGenerator, "Type54GroupGenerator": Type54GroupGenerator,
"LaCombattanteIIGroupGenerator": LaCombattanteIIGroupGenerator,
} }

View File

@ -132,7 +132,6 @@ CAP_CAPABLE = [
MiG_29A, MiG_29A,
F_16C_50, F_16C_50,
FA_18C_hornet, FA_18C_hornet,
F_15E,
F_16A, F_16A,
F_4E, F_4E,
JF_17, JF_17,
@ -140,6 +139,7 @@ CAP_CAPABLE = [
MiG_21Bis, MiG_21Bis,
Mirage_2000_5, Mirage_2000_5,
M_2000C, M_2000C,
F_15E,
F_5E_3, F_5E_3,
MiG_19P, MiG_19P,
A_4E_C, A_4E_C,

View File

@ -79,6 +79,7 @@ class FlightWaypointType(Enum):
INGRESS_OCA_AIRCRAFT = 25 INGRESS_OCA_AIRCRAFT = 25
PICKUP = 26 PICKUP = 26
DROP_OFF = 27 DROP_OFF = 27
BULLSEYE = 28
class FlightWaypoint: class FlightWaypoint:

View File

@ -426,6 +426,7 @@ class BarCapFlightPlan(PatrollingFlightPlan):
takeoff: FlightWaypoint takeoff: FlightWaypoint
land: FlightWaypoint land: FlightWaypoint
divert: Optional[FlightWaypoint] divert: Optional[FlightWaypoint]
bullseye: FlightWaypoint
def iter_waypoints(self) -> Iterator[FlightWaypoint]: def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.takeoff yield self.takeoff
@ -438,6 +439,7 @@ class BarCapFlightPlan(PatrollingFlightPlan):
yield self.land yield self.land
if self.divert is not None: if self.divert is not None:
yield self.divert yield self.divert
yield self.bullseye
@dataclass(frozen=True) @dataclass(frozen=True)
@ -446,6 +448,7 @@ class CasFlightPlan(PatrollingFlightPlan):
target: FlightWaypoint target: FlightWaypoint
land: FlightWaypoint land: FlightWaypoint
divert: Optional[FlightWaypoint] divert: Optional[FlightWaypoint]
bullseye: FlightWaypoint
def iter_waypoints(self) -> Iterator[FlightWaypoint]: def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.takeoff yield self.takeoff
@ -459,6 +462,7 @@ class CasFlightPlan(PatrollingFlightPlan):
yield self.land yield self.land
if self.divert is not None: if self.divert is not None:
yield self.divert yield self.divert
yield self.bullseye
def request_escort_at(self) -> Optional[FlightWaypoint]: def request_escort_at(self) -> Optional[FlightWaypoint]:
return self.patrol_start return self.patrol_start
@ -472,6 +476,7 @@ class TarCapFlightPlan(PatrollingFlightPlan):
takeoff: FlightWaypoint takeoff: FlightWaypoint
land: FlightWaypoint land: FlightWaypoint
divert: Optional[FlightWaypoint] divert: Optional[FlightWaypoint]
bullseye: FlightWaypoint
lead_time: timedelta lead_time: timedelta
def iter_waypoints(self) -> Iterator[FlightWaypoint]: def iter_waypoints(self) -> Iterator[FlightWaypoint]:
@ -485,6 +490,7 @@ class TarCapFlightPlan(PatrollingFlightPlan):
yield self.land yield self.land
if self.divert is not None: if self.divert is not None:
yield self.divert yield self.divert
yield self.bullseye
@property @property
def tot_offset(self) -> timedelta: def tot_offset(self) -> timedelta:
@ -523,6 +529,7 @@ class StrikeFlightPlan(FormationFlightPlan):
nav_from: List[FlightWaypoint] nav_from: List[FlightWaypoint]
land: FlightWaypoint land: FlightWaypoint
divert: Optional[FlightWaypoint] divert: Optional[FlightWaypoint]
bullseye: FlightWaypoint
def iter_waypoints(self) -> Iterator[FlightWaypoint]: def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.takeoff yield self.takeoff
@ -537,6 +544,7 @@ class StrikeFlightPlan(FormationFlightPlan):
yield self.land yield self.land
if self.divert is not None: if self.divert is not None:
yield self.divert yield self.divert
yield self.bullseye
@property @property
def package_speed_waypoints(self) -> Set[FlightWaypoint]: def package_speed_waypoints(self) -> Set[FlightWaypoint]:
@ -641,6 +649,7 @@ class SweepFlightPlan(LoiterFlightPlan):
nav_from: List[FlightWaypoint] nav_from: List[FlightWaypoint]
land: FlightWaypoint land: FlightWaypoint
divert: Optional[FlightWaypoint] divert: Optional[FlightWaypoint]
bullseye: FlightWaypoint
lead_time: timedelta lead_time: timedelta
def iter_waypoints(self) -> Iterator[FlightWaypoint]: def iter_waypoints(self) -> Iterator[FlightWaypoint]:
@ -653,6 +662,7 @@ class SweepFlightPlan(LoiterFlightPlan):
yield self.land yield self.land
if self.divert is not None: if self.divert is not None:
yield self.divert yield self.divert
yield self.bullseye
@property @property
def tot_waypoint(self) -> Optional[FlightWaypoint]: def tot_waypoint(self) -> Optional[FlightWaypoint]:
@ -704,6 +714,7 @@ class AwacsFlightPlan(LoiterFlightPlan):
nav_from: List[FlightWaypoint] nav_from: List[FlightWaypoint]
land: FlightWaypoint land: FlightWaypoint
divert: Optional[FlightWaypoint] divert: Optional[FlightWaypoint]
bullseye: FlightWaypoint
def iter_waypoints(self) -> Iterator[FlightWaypoint]: def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.takeoff yield self.takeoff
@ -713,6 +724,7 @@ class AwacsFlightPlan(LoiterFlightPlan):
yield self.land yield self.land
if self.divert is not None: if self.divert is not None:
yield self.divert yield self.divert
yield self.bullseye
@property @property
def mission_start_time(self) -> Optional[timedelta]: def mission_start_time(self) -> Optional[timedelta]:
@ -746,6 +758,7 @@ class AirliftFlightPlan(FlightPlan):
nav_to_home: List[FlightWaypoint] nav_to_home: List[FlightWaypoint]
land: FlightWaypoint land: FlightWaypoint
divert: Optional[FlightWaypoint] divert: Optional[FlightWaypoint]
bullseye: FlightWaypoint
def iter_waypoints(self) -> Iterator[FlightWaypoint]: def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.takeoff yield self.takeoff
@ -758,6 +771,7 @@ class AirliftFlightPlan(FlightPlan):
yield self.land yield self.land
if self.divert is not None: if self.divert is not None:
yield self.divert yield self.divert
yield self.bullseye
@property @property
def tot_waypoint(self) -> Optional[FlightWaypoint]: def tot_waypoint(self) -> Optional[FlightWaypoint]:
@ -1053,6 +1067,7 @@ class FlightPlanBuilder:
), ),
land=builder.land(flight.arrival), land=builder.land(flight.arrival),
divert=builder.divert(flight.divert), divert=builder.divert(flight.divert),
bullseye=builder.bullseye(),
hold=start, hold=start,
hold_duration=timedelta(hours=4), hold_duration=timedelta(hours=4),
) )
@ -1151,6 +1166,7 @@ class FlightPlanBuilder:
patrol_end=end, patrol_end=end,
land=builder.land(flight.arrival), land=builder.land(flight.arrival),
divert=builder.divert(flight.divert), divert=builder.divert(flight.divert),
bullseye=builder.bullseye(),
) )
def generate_sweep(self, flight: Flight) -> SweepFlightPlan: def generate_sweep(self, flight: Flight) -> SweepFlightPlan:
@ -1187,6 +1203,7 @@ class FlightPlanBuilder:
sweep_end=end, sweep_end=end,
land=builder.land(flight.arrival), land=builder.land(flight.arrival),
divert=builder.divert(flight.divert), divert=builder.divert(flight.divert),
bullseye=builder.bullseye(),
) )
def generate_transport(self, flight: Flight) -> AirliftFlightPlan: def generate_transport(self, flight: Flight) -> AirliftFlightPlan:
@ -1238,6 +1255,7 @@ class FlightPlanBuilder:
), ),
land=builder.land(flight.arrival), land=builder.land(flight.arrival),
divert=builder.divert(flight.divert), divert=builder.divert(flight.divert),
bullseye=builder.bullseye(),
) )
def racetrack_for_objective( def racetrack_for_objective(
@ -1389,6 +1407,7 @@ class FlightPlanBuilder:
patrol_end=end, patrol_end=end,
land=builder.land(flight.arrival), land=builder.land(flight.arrival),
divert=builder.divert(flight.divert), divert=builder.divert(flight.divert),
bullseye=builder.bullseye(),
) )
def generate_dead( def generate_dead(
@ -1517,6 +1536,7 @@ class FlightPlanBuilder:
), ),
land=builder.land(flight.arrival), land=builder.land(flight.arrival),
divert=builder.divert(flight.divert), divert=builder.divert(flight.divert),
bullseye=builder.bullseye(),
) )
def generate_cas(self, flight: Flight) -> CasFlightPlan: def generate_cas(self, flight: Flight) -> CasFlightPlan:
@ -1562,6 +1582,7 @@ class FlightPlanBuilder:
patrol_end=builder.egress(egress, location), patrol_end=builder.egress(egress, location),
land=builder.land(flight.arrival), land=builder.land(flight.arrival),
divert=builder.divert(flight.divert), divert=builder.divert(flight.divert),
bullseye=builder.bullseye(),
) )
@staticmethod @staticmethod
@ -1696,6 +1717,7 @@ class FlightPlanBuilder:
), ),
land=builder.land(flight.arrival), land=builder.land(flight.arrival),
divert=builder.divert(flight.divert), divert=builder.divert(flight.divert),
bullseye=builder.bullseye(),
) )
def _retreating_rendezvous_point(self, attack_transition: Point) -> Point: def _retreating_rendezvous_point(self, attack_transition: Point) -> Point:

View File

@ -35,6 +35,9 @@ class Loadout:
new_pylons = dict(self.pylons) new_pylons = dict(self.pylons)
for pylon_number, weapon in self.pylons.items(): for pylon_number, weapon in self.pylons.items():
if weapon is None:
del new_pylons[pylon_number]
continue
if not weapon.available_on(date): if not weapon.available_on(date):
pylon = Pylon.for_aircraft(unit_type, pylon_number) pylon = Pylon.for_aircraft(unit_type, pylon_number)
for fallback in weapon.fallbacks: for fallback in weapon.fallbacks:
@ -44,6 +47,8 @@ class Loadout:
continue continue
new_pylons[pylon_number] = fallback new_pylons[pylon_number] = fallback
break break
else:
del new_pylons[pylon_number]
return Loadout(f"{self.name} ({date.year})", new_pylons, date) return Loadout(f"{self.name} ({date.year})", new_pylons, date)
@classmethod @classmethod

View File

@ -50,6 +50,7 @@ class WaypointBuilder:
self.threat_zones = game.threat_zone_for(not player) self.threat_zones = game.threat_zone_for(not player)
self.navmesh = game.navmesh_for(player) self.navmesh = game.navmesh_for(player)
self.targets = targets self.targets = targets
self._bullseye = game.bullseye_for(player)
@property @property
def is_helo(self) -> bool: def is_helo(self) -> bool:
@ -145,6 +146,19 @@ class WaypointBuilder:
waypoint.only_for_player = True waypoint.only_for_player = True
return waypoint return waypoint
def bullseye(self) -> FlightWaypoint:
waypoint = FlightWaypoint(
FlightWaypointType.BULLSEYE,
self._bullseye.position.x,
self._bullseye.position.y,
meters(0),
)
waypoint.pretty_name = "Bullseye"
waypoint.description = "Bullseye"
waypoint.name = "BULLSEYE"
waypoint.only_for_player = True
return waypoint
def hold(self, position: Point) -> FlightWaypoint: def hold(self, position: Point) -> FlightWaypoint:
waypoint = FlightWaypoint( waypoint = FlightWaypoint(
FlightWaypointType.LOITER, FlightWaypointType.LOITER,

View File

@ -15,10 +15,12 @@ TYPE_TANKS = [
Armor.MBT_Leopard_1A3, Armor.MBT_Leopard_1A3,
Armor.MBT_Leclerc, Armor.MBT_Leclerc,
Armor.MBT_Challenger_II, Armor.MBT_Challenger_II,
Armor.MBT_Chieftain_Mk_3,
Armor.MBT_M1A2_Abrams, Armor.MBT_M1A2_Abrams,
Armor.MBT_M60A3_Patton, Armor.MBT_M60A3_Patton,
Armor.MBT_Merkava_IV, Armor.MBT_Merkava_IV,
Armor.ZTZ_96B, Armor.ZTZ_96B,
Armor.LT_PT_76,
# WW2 # WW2
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G, Armor.MT_Pz_Kpfw_V_Panther_Ausf_G,
Armor.Tk_PzIV_H, Armor.Tk_PzIV_H,
@ -45,6 +47,7 @@ TYPE_TANKS = [
TYPE_ATGM = [ TYPE_ATGM = [
Armor.ATGM_HMMWV, Armor.ATGM_HMMWV,
Armor.ATGM_VAB_Mephisto,
Armor.ATGM_Stryker, Armor.ATGM_Stryker,
Armor.IFV_BMP_2, Armor.IFV_BMP_2,
# WW2 (Tank Destroyers) # WW2 (Tank Destroyers)
@ -114,6 +117,7 @@ TYPE_ARTILLERY = [
Artillery.MLRS_M270_227mm, Artillery.MLRS_M270_227mm,
Artillery.SPM_2S9_Nona_120mm_M, Artillery.SPM_2S9_Nona_120mm_M,
Artillery.SPH_Dana_vz77_152mm, Artillery.SPH_Dana_vz77_152mm,
Artillery.SPH_T155_Firtina_155mm,
Artillery.PLZ_05, Artillery.PLZ_05,
Artillery.SPH_2S19_Msta_152mm, Artillery.SPH_2S19_Msta_152mm,
Artillery.MLRS_9A52_Smerch_CM_300mm, Artillery.MLRS_9A52_Smerch_CM_300mm,

View File

@ -23,6 +23,7 @@ only be added per airframe, so PvP missions where each side have the same
aircraft will be able to see the enemy's kneeboard for the same airframe. aircraft will be able to see the enemy's kneeboard for the same airframe.
""" """
import datetime import datetime
import textwrap
from collections import defaultdict from collections import defaultdict
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
@ -37,6 +38,7 @@ from tabulate import tabulate
from game.data.alic import AlicCodes from game.data.alic import AlicCodes
from game.db import find_unittype, unit_type_from_name from game.db import find_unittype, unit_type_from_name
from game.theater import ConflictTheater, TheaterGroundObject, LatLon from game.theater import ConflictTheater, TheaterGroundObject, LatLon
from game.theater.bullseye import Bullseye
from game.utils import meters from game.utils import meters
from .aircraft import AIRCRAFT_DATA, FlightData from .aircraft import AIRCRAFT_DATA, FlightData
from .airsupportgen import AwacsInfo, TankerInfo from .airsupportgen import AwacsInfo, TankerInfo
@ -257,21 +259,16 @@ class BriefingPage(KneeboardPage):
def __init__( def __init__(
self, self,
flight: FlightData, flight: FlightData,
comms: List[CommInfo], bullseye: Bullseye,
awacs: List[AwacsInfo], theater: ConflictTheater,
tankers: List[TankerInfo],
jtacs: List[JtacInfo],
start_time: datetime.datetime, start_time: datetime.datetime,
dark_kneeboard: bool, dark_kneeboard: bool,
) -> None: ) -> None:
self.flight = flight self.flight = flight
self.comms = list(comms) self.bullseye = bullseye
self.awacs = awacs self.theater = theater
self.tankers = tankers
self.jtacs = jtacs
self.start_time = start_time self.start_time = start_time
self.dark_kneeboard = dark_kneeboard self.dark_kneeboard = dark_kneeboard
self.comms.append(CommInfo("Flight", self.flight.intra_flight_channel))
def write(self, path: Path) -> None: def write(self, path: Path) -> None:
writer = KneeboardPageWriter(dark_theme=self.dark_kneeboard) writer = KneeboardPageWriter(dark_theme=self.dark_kneeboard)
@ -301,6 +298,10 @@ class BriefingPage(KneeboardPage):
headers=["#", "Action", "Alt", "Dist", "GSPD", "Time", "Departure"], headers=["#", "Action", "Alt", "Dist", "GSPD", "Time", "Departure"],
) )
writer.text(
f"Bullseye: {self.format_ll(self.bullseye.to_lat_lon(self.theater))}"
)
writer.table( writer.table(
[ [
[ [
@ -311,6 +312,86 @@ class BriefingPage(KneeboardPage):
["Bingo", "Joker"], ["Bingo", "Joker"],
) )
writer.write(path)
def airfield_info_row(
self, row_title: str, runway: Optional[RunwayData]
) -> List[str]:
"""Creates a table row for a given airfield.
Args:
row_title: Purpose of the airfield. e.g. "Departure", "Arrival" or
"Divert".
runway: The runway described by this row.
Returns:
A list of strings to be used as a row of the airfield table.
"""
if runway is None:
return [row_title, "", "", "", "", ""]
atc = ""
if runway.atc is not None:
atc = self.format_frequency(runway.atc)
if runway.tacan is None:
tacan = ""
else:
tacan = str(runway.tacan)
if runway.ils is not None:
ils = str(runway.ils)
elif runway.icls is not None:
ils = str(runway.icls)
else:
ils = ""
return [
row_title,
"\n".join(textwrap.wrap(runway.airfield_name, width=24)),
atc,
tacan,
ils,
runway.runway_name,
]
def format_frequency(self, frequency: RadioFrequency) -> str:
channel = self.flight.channel_for(frequency)
if channel is None:
return str(frequency)
namer = AIRCRAFT_DATA[self.flight.aircraft_type.id].channel_namer
channel_name = namer.channel_name(channel.radio_id, channel.channel)
return f"{channel_name}\n{frequency}"
class SupportPage(KneeboardPage):
"""A kneeboard page containing information about support units."""
def __init__(
self,
flight: FlightData,
comms: List[CommInfo],
awacs: List[AwacsInfo],
tankers: List[TankerInfo],
jtacs: List[JtacInfo],
start_time: datetime.datetime,
dark_kneeboard: bool,
) -> None:
self.flight = flight
self.comms = list(comms)
self.awacs = awacs
self.tankers = tankers
self.jtacs = jtacs
self.start_time = start_time
self.dark_kneeboard = dark_kneeboard
self.comms.append(CommInfo("Flight", self.flight.intra_flight_channel))
def write(self, path: Path) -> None:
writer = KneeboardPageWriter(dark_theme=self.dark_kneeboard)
if self.flight.custom_name is not None:
custom_name_title = ' ("{}")'.format(self.flight.custom_name)
else:
custom_name_title = ""
writer.title(f"{self.flight.callsign} Support Info{custom_name_title}")
# AEW&C # AEW&C
writer.heading("AEW&C") writer.heading("AEW&C")
aewc_ladder = [] aewc_ladder = []
@ -368,44 +449,6 @@ class BriefingPage(KneeboardPage):
writer.write(path) writer.write(path)
def airfield_info_row(
self, row_title: str, runway: Optional[RunwayData]
) -> List[str]:
"""Creates a table row for a given airfield.
Args:
row_title: Purpose of the airfield. e.g. "Departure", "Arrival" or
"Divert".
runway: The runway described by this row.
Returns:
A list of strings to be used as a row of the airfield table.
"""
if runway is None:
return [row_title, "", "", "", "", ""]
atc = ""
if runway.atc is not None:
atc = self.format_frequency(runway.atc)
if runway.tacan is None:
tacan = ""
else:
tacan = str(runway.tacan)
if runway.ils is not None:
ils = str(runway.ils)
elif runway.icls is not None:
ils = str(runway.icls)
else:
ils = ""
return [
row_title,
runway.airfield_name,
atc,
tacan,
ils,
runway.runway_name,
]
def format_frequency(self, frequency: RadioFrequency) -> str: def format_frequency(self, frequency: RadioFrequency) -> str:
channel = self.flight.channel_for(frequency) channel = self.flight.channel_for(frequency)
if channel is None: if channel is None:
@ -558,6 +601,13 @@ class KneeboardGenerator(MissionInfoGenerator):
"""Returns a list of kneeboard pages for the given flight.""" """Returns a list of kneeboard pages for the given flight."""
pages: List[KneeboardPage] = [ pages: List[KneeboardPage] = [
BriefingPage( BriefingPage(
flight,
self.game.bullseye_for(flight.friendly),
self.game.theater,
self.mission.start_time,
self.dark_kneeboard,
),
SupportPage(
flight, flight,
self.comms, self.comms,
self.awacs, self.awacs,

View File

@ -209,16 +209,6 @@ class TriggersGenerator:
player_coalition = "blue" player_coalition = "blue"
enemy_coalition = "red" enemy_coalition = "red"
player_cp, enemy_cp = self.game.theater.closest_opposing_control_points()
self.mission.coalition["blue"].bullseye = {
"x": enemy_cp.position.x,
"y": enemy_cp.position.y,
}
self.mission.coalition["red"].bullseye = {
"x": player_cp.position.x,
"y": player_cp.position.y,
}
self._set_skill(player_coalition, enemy_coalition) self._set_skill(player_coalition, enemy_coalition)
self._set_allegiances(player_coalition, enemy_coalition) self._set_allegiances(player_coalition, enemy_coalition)
self._gen_markers() self._gen_markers()

2
pydcs

@ -1 +1 @@
Subproject commit dec648d27f74c394dd6e85e83cc09e4cd823653d Subproject commit 4972988c978f2057e7aa06919c4de71ee9a06ea5

View File

@ -375,6 +375,7 @@ class WaypointJs(QObject):
timingChanged = Signal() timingChanged = Signal()
isTakeoffChanged = Signal() isTakeoffChanged = Signal()
isDivertChanged = Signal() isDivertChanged = Signal()
isBullseyeChanged = Signal()
def __init__( def __init__(
self, self,
@ -439,6 +440,10 @@ class WaypointJs(QObject):
def isDivert(self) -> bool: def isDivert(self) -> bool:
return self.waypoint.waypoint_type is FlightWaypointType.DIVERT return self.waypoint.waypoint_type is FlightWaypointType.DIVERT
@Property(bool, notify=isBullseyeChanged)
def isBullseye(self) -> bool:
return self.waypoint.waypoint_type is FlightWaypointType.BULLSEYE
@Slot(list, result=str) @Slot(list, result=str)
def setPosition(self, position: LeafletLatLon) -> str: def setPosition(self, position: LeafletLatLon) -> str:
point = self.theater.ll_to_point(LatLon(*position)) point = self.theater.ll_to_point(LatLon(*position))

View File

@ -47,7 +47,8 @@
"MBT_Challenger_II", "MBT_Challenger_II",
"MBT_M60A3_Patton", "MBT_M60A3_Patton",
"SPG_Stryker_MGS", "SPG_Stryker_MGS",
"SAM_Avenger__Stinger" "SAM_Avenger__Stinger",
"ATGM_VAB_Mephisto"
], ],
"artillery_units": [ "artillery_units": [
"MLRS_M270_227mm", "MLRS_M270_227mm",

View File

@ -46,7 +46,8 @@
"Scout_HMMWV", "Scout_HMMWV",
"ATGM_HMMWV", "ATGM_HMMWV",
"SAM_Linebacker___Bradley_M6", "SAM_Linebacker___Bradley_M6",
"SAM_Avenger__Stinger" "SAM_Avenger__Stinger",
"ATGM_VAB_Mephisto"
], ],
"artillery_units": [ "artillery_units": [
"MLRS_M270_227mm", "MLRS_M270_227mm",

View File

@ -23,13 +23,10 @@
"ERC_90", "ERC_90",
"TRM_2000_PAMELA", "TRM_2000_PAMELA",
"VAB__50", "VAB__50",
"VAB_MEPHISTO", "ATGM_VAB_Mephisto",
"VAB_T20_13",
"VAB_T20_13", "VAB_T20_13",
"VBL__50", "VBL__50",
"VBL_AANF1", "VBL_AANF1",
"VBAE_CRAB",
"VBAE_CRAB_MMP",
"AMX_30B2", "AMX_30B2",
"SAM_Roland_ADS" "SAM_Roland_ADS"
], ],

View File

@ -21,10 +21,8 @@
"MBT_Leclerc", "MBT_Leclerc",
"APC_TPz_Fuchs", "APC_TPz_Fuchs",
"Scout_Cobra", "Scout_Cobra",
"ATGM_Stryker",
"IFV_LAV_25", "IFV_LAV_25",
"Scout_HMMWV", "ATGM_VAB_Mephisto",
"ATGM_HMMWV",
"SAM_Roland_ADS" "SAM_Roland_ADS"
], ],
"artillery_units": [ "artillery_units": [

View File

@ -24,7 +24,6 @@
"ERC_90", "ERC_90",
"TRM_2000_PAMELA", "TRM_2000_PAMELA",
"VAB__50", "VAB__50",
"VAB_MEPHISTO",
"VAB_T20_13", "VAB_T20_13",
"VAB_T20_13", "VAB_T20_13",
"VBL__50", "VBL__50",
@ -33,7 +32,8 @@
"VBAE_CRAB_MMP", "VBAE_CRAB_MMP",
"AMX_30B2", "AMX_30B2",
"Leclerc_Serie_XXI", "Leclerc_Serie_XXI",
"SAM_Roland_ADS" "SAM_Roland_ADS",
"ATGM_VAB_Mephisto"
], ],
"artillery_units": [ "artillery_units": [
"MLRS_M270_227mm", "MLRS_M270_227mm",

View File

@ -45,6 +45,9 @@
"ZU23Generator", "ZU23Generator",
"ZU23UralGenerator" "ZU23UralGenerator"
], ],
"navy_generators": [
"LaCombattanteIIGroupGenerator"
],
"requirements": {}, "requirements": {},
"has_jtac": true, "has_jtac": true,
"jtac_unit": "MQ_9_Reaper" "jtac_unit": "MQ_9_Reaper"

View File

@ -61,7 +61,8 @@
"helicopter_carrier_names": [ "helicopter_carrier_names": [
], ],
"navy_generators": [ "navy_generators": [
"OliverHazardPerryGroupGenerator" "OliverHazardPerryGroupGenerator",
"LaCombattanteIIGroupGenerator"
], ],
"has_jtac": true, "has_jtac": true,
"jtac_unit": "MQ_9_Reaper" "jtac_unit": "MQ_9_Reaper"

View File

@ -46,6 +46,9 @@
"HawkEwrGenerator", "HawkEwrGenerator",
"FlatFaceGenerator" "FlatFaceGenerator"
], ],
"navy_generators": [
"LaCombattanteIIGroupGenerator"
],
"has_jtac": true, "has_jtac": true,
"jtac_unit": "MQ_9_Reaper" "jtac_unit": "MQ_9_Reaper"
} }

View File

@ -9,6 +9,7 @@
"Scout_Cobra", "Scout_Cobra",
"APC_MTLB", "APC_MTLB",
"Scout_BRDM_2", "Scout_BRDM_2",
"LT_PT_76",
"SPAAA_ZU_23_2_Insurgent_Mounted_Ural_375" "SPAAA_ZU_23_2_Insurgent_Mounted_Ural_375"
], ],
"artillery_units": [ "artillery_units": [

View File

@ -11,6 +11,7 @@
"Scout_BRDM_2", "Scout_BRDM_2",
"APC_BTR_80", "APC_BTR_80",
"APC_BTR_RD", "APC_BTR_RD",
"LT_PT_76",
"IFV_BMP_1", "IFV_BMP_1",
"MBT_T_55", "MBT_T_55",
"SPAAA_ZU_23_2_Insurgent_Mounted_Ural_375", "SPAAA_ZU_23_2_Insurgent_Mounted_Ural_375",

View File

@ -20,6 +20,7 @@
"APC_M113", "APC_M113",
"APC_BTR_80", "APC_BTR_80",
"MBT_M60A3_Patton", "MBT_M60A3_Patton",
"MBT_Chieftain_Mk_3",
"IFV_BMP_1", "IFV_BMP_1",
"SPAAA_ZSU_23_4_Shilka_Gun_Dish", "SPAAA_ZSU_23_4_Shilka_Gun_Dish",
"SPAAA_ZSU_57_2", "SPAAA_ZSU_57_2",
@ -75,7 +76,7 @@
], ],
"coastal_group_count": 2, "coastal_group_count": 2,
"navy_generators": [ "navy_generators": [
"GrishaGroupGenerator", "LaCombattanteIIGroupGenerator",
"MolniyaGroupGenerator" "MolniyaGroupGenerator"
], ],
"has_jtac": true, "has_jtac": true,

View File

@ -26,6 +26,7 @@
"APC_M113", "APC_M113",
"APC_BTR_80", "APC_BTR_80",
"MBT_M60A3_Patton", "MBT_M60A3_Patton",
"MBT_Chieftain_Mk_3",
"IFV_BMP_1", "IFV_BMP_1",
"MBT_T_72B", "MBT_T_72B",
"SPAAA_ZSU_23_4_Shilka_Gun_Dish", "SPAAA_ZSU_23_4_Shilka_Gun_Dish",
@ -85,7 +86,8 @@
"coastal_group_count": 3, "coastal_group_count": 3,
"navy_generators": [ "navy_generators": [
"GrishaGroupGenerator", "GrishaGroupGenerator",
"MolniyaGroupGenerator" "MolniyaGroupGenerator",
"LaCombattanteIIGroupGenerator"
], ],
"has_jtac": true, "has_jtac": true,
"jtac_unit": "MQ_9_Reaper" "jtac_unit": "MQ_9_Reaper"

View File

@ -29,8 +29,10 @@
"APC_MTLB", "APC_MTLB",
"MBT_T_55", "MBT_T_55",
"MBT_T_72B", "MBT_T_72B",
"MBT_Chieftain_Mk_3",
"APC_BTR_80", "APC_BTR_80",
"Scout_BRDM_2", "Scout_BRDM_2",
"LT_PT_76",
"SPH_2S1_Gvozdika_122mm", "SPH_2S1_Gvozdika_122mm",
"SPAAA_ZSU_57_2", "SPAAA_ZSU_57_2",
"SPAAA_ZSU_23_4_Shilka_Gun_Dish" "SPAAA_ZSU_23_4_Shilka_Gun_Dish"

View File

@ -21,6 +21,7 @@
"Scout_BRDM_2", "Scout_BRDM_2",
"MBT_T_72B", "MBT_T_72B",
"MBT_T_55", "MBT_T_55",
"LT_PT_76",
"SPAAA_ZSU_23_4_Shilka_Gun_Dish", "SPAAA_ZSU_23_4_Shilka_Gun_Dish",
"SAM_SA_8_Osa_Gecko_TEL" "SAM_SA_8_Osa_Gecko_TEL"
], ],
@ -71,6 +72,6 @@
"carrier_names": [ "carrier_names": [
], ],
"navy_generators": [ "navy_generators": [
"GrishaGroupGenerator", "MolniyaGroupGenerator" "GrishaGroupGenerator", "MolniyaGroupGenerator", "LaCombattanteIIGroupGenerator"
] ]
} }

View File

@ -25,6 +25,7 @@
"MBT_T_55", "MBT_T_55",
"MBT_T_72B", "MBT_T_72B",
"MBT_T_80U", "MBT_T_80U",
"LT_PT_76",
"SPAAA_ZSU_57_2", "SPAAA_ZSU_57_2",
"SAM_SA_9_Strela_1_Gaskin_TEL" "SAM_SA_9_Strela_1_Gaskin_TEL"
], ],

View File

@ -17,6 +17,7 @@
"Grad_MRL_FDDM__FC", "Grad_MRL_FDDM__FC",
"APC_MTLB", "APC_MTLB",
"MBT_T_55", "MBT_T_55",
"LT_PT_76",
"SPAAA_ZU_23_2_Mounted_Ural_375", "SPAAA_ZU_23_2_Mounted_Ural_375",
"AAA_8_8cm_Flak_18", "AAA_8_8cm_Flak_18",
"AAA_S_60_57mm" "AAA_S_60_57mm"

View File

@ -22,6 +22,7 @@
"APC_BTR_RD", "APC_BTR_RD",
"IFV_BMD_1", "IFV_BMD_1",
"IFV_BMP_1", "IFV_BMP_1",
"LT_PT_76",
"MBT_T_55", "MBT_T_55",
"SPAAA_ZU_23_2_Mounted_Ural_375", "SPAAA_ZU_23_2_Mounted_Ural_375",
"SPAAA_ZSU_57_2", "SPAAA_ZSU_57_2",

View File

@ -14,6 +14,7 @@
"APC_BTR_80", "APC_BTR_80",
"IFV_BMD_1", "IFV_BMD_1",
"IFV_BMP_1", "IFV_BMP_1",
"LT_PT_76",
"MBT_T_55", "MBT_T_55",
"SPAAA_ZSU_57_2" "SPAAA_ZSU_57_2"
], ],

View File

@ -27,6 +27,7 @@
"APC_BTR_80", "APC_BTR_80",
"IFV_BMD_1", "IFV_BMD_1",
"IFV_BMP_1", "IFV_BMP_1",
"LT_PT_76",
"MBT_T_55", "MBT_T_55",
"SAM_SA_8_Osa_Gecko_TEL" "SAM_SA_8_Osa_Gecko_TEL"
], ],

View File

@ -20,6 +20,7 @@
"Scout_BRDM_2", "Scout_BRDM_2",
"Tk_PzIV_H", "Tk_PzIV_H",
"MBT_T_55", "MBT_T_55",
"LT_PT_76",
"SPAAA_ZU_23_2_Mounted_Ural_375", "SPAAA_ZU_23_2_Mounted_Ural_375",
"SPAAA_ZSU_57_2", "SPAAA_ZSU_57_2",
"AAA_S_60_57mm" "AAA_S_60_57mm"

View File

@ -20,6 +20,7 @@
"frontline_units": [ "frontline_units": [
"Scout_BRDM_2", "Scout_BRDM_2",
"MBT_T_55", "MBT_T_55",
"LT_PT_76",
"Tk_PzIV_H", "Tk_PzIV_H",
"SPG_StuG_III_Ausf__G", "SPG_StuG_III_Ausf__G",
"SPG_Jagdpanzer_IV", "SPG_Jagdpanzer_IV",

View File

@ -20,6 +20,7 @@
"IFV_BMP_1", "IFV_BMP_1",
"APC_MTLB", "APC_MTLB",
"MBT_T_55", "MBT_T_55",
"LT_PT_76",
"SPAAA_ZU_23_2_Mounted_Ural_375", "SPAAA_ZU_23_2_Mounted_Ural_375",
"SPAAA_ZSU_57_2", "SPAAA_ZSU_57_2",
"AAA_S_60_57mm" "AAA_S_60_57mm"

View File

@ -21,6 +21,7 @@
"IFV_BMP_1", "IFV_BMP_1",
"APC_MTLB", "APC_MTLB",
"MBT_T_55", "MBT_T_55",
"LT_PT_76",
"MBT_T_72B", "MBT_T_72B",
"SPAAA_ZU_23_2_Mounted_Ural_375", "SPAAA_ZU_23_2_Mounted_Ural_375",
"SPAAA_ZSU_57_2", "SPAAA_ZSU_57_2",

View File

@ -27,6 +27,7 @@
"IFV_BMP_2", "IFV_BMP_2",
"APC_BTR_80", "APC_BTR_80",
"Scout_BRDM_2", "Scout_BRDM_2",
"LT_PT_76",
"APC_MTLB", "APC_MTLB",
"Scout_Cobra", "Scout_Cobra",
"MBT_T_55", "MBT_T_55",

View File

@ -25,7 +25,7 @@
"SAM_Avenger__Stinger" "SAM_Avenger__Stinger"
], ],
"artillery_units": [ "artillery_units": [
"SPH_M109_Paladin_155mm" "SPH_T155_Firtina_155mm"
], ],
"logistics_units": [ "logistics_units": [
"Truck_M818_6x6" "Truck_M818_6x6"

View File

@ -19,6 +19,7 @@
], ],
"frontline_units": [ "frontline_units": [
"MBT_Challenger_II", "MBT_Challenger_II",
"MBT_Chieftain_Mk_3",
"IFV_Warrior", "IFV_Warrior",
"Scout_HMMWV", "Scout_HMMWV",
"ATGM_HMMWV", "ATGM_HMMWV",

View File

@ -570,7 +570,7 @@ class Waypoint {
} }
includeInPath() { includeInPath() {
return !this.waypoint.isDivert; return !this.waypoint.isDivert && !this.waypoint.isBullseye;
} }
} }