diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 6c5955b8..fa261ee8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -9,6 +9,8 @@ assignees: '' 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** A clear and concise description of what the bug is. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index ddf2c8f3..4176751f 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -9,6 +9,8 @@ assignees: '' 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.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] diff --git a/README.md b/README.md index 67f55b18..7c487395 100644 --- a/README.md +++ b/README.md @@ -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. +## 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 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. diff --git a/changelog.md b/changelog.md index 50252a96..b0f8e3c9 100644 --- a/changelog.md +++ b/changelog.md @@ -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 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]** 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]** 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). * **[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]** 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. diff --git a/game/data/building_data.py b/game/data/building_data.py index e6cd4ba9..5688fcb8 100644 --- a/game/data/building_data.py +++ b/game/data/building_data.py @@ -8,7 +8,6 @@ DEFAULT_AVAILABLE_BUILDINGS = [ "oil", "ware", "farp", - "fob", "power", "derrick", ] @@ -21,7 +20,6 @@ WW2_GERMANY_BUILDINGS = [ "ww2bunker", "allycamp", "allycamp", - "fob", ] WW2_ALLIES_BUILDINGS = [ "fuel", @@ -30,7 +28,6 @@ WW2_ALLIES_BUILDINGS = [ "allycamp", "allycamp", "allycamp", - "fob", ] FORTIFICATION_BUILDINGS = [ diff --git a/game/data/weapons.py b/game/data/weapons.py index b7dd530b..7aa21beb 100644 --- a/game/data/weapons.py +++ b/game/data/weapons.py @@ -145,20 +145,26 @@ _WEAPON_FALLBACKS = [ Weapons.LAU_117_with_AGM_65E___Maverick_E__Laser_ASM___Lg_Whd_, ), # internal pylons harrier # 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_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_, - None, + Weapons.AGM_62_Walleye_II___Guided_Weapon_Mk_5__TV_Guided_, ), # doesn't exist on any aircraft yet (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_33_with_2_x_GBU_12___500lb_Laser_Guided_Bomb, + Weapons.AGM_62_Walleye_II___Guided_Weapon_Mk_5__TV_Guided_, ), # AGM-45 Shrike (Weapons.AGM_45A_Shrike_ARM, None), diff --git a/game/db.py b/game/db.py index eb2b692f..2f297cbf 100644 --- a/game/db.py +++ b/game/db.py @@ -498,12 +498,14 @@ PRICES = { Armor.IFV_BMP_1: 14, Armor.IFV_BMP_2: 16, Armor.IFV_BMP_3: 18, + Armor.LT_PT_76: 9, Armor.ZBD_04A: 12, Armor.ZTZ_96B: 30, Armor.Scout_Cobra: 4, Armor.APC_M113: 6, Armor.Scout_HMMWV: 2, Armor.ATGM_HMMWV: 8, + Armor.ATGM_VAB_Mephisto: 12, Armor.IFV_M2A2_Bradley: 12, Armor.IFV_M1126_Stryker_ICV: 10, Armor.SPG_Stryker_MGS: 14, @@ -519,6 +521,7 @@ PRICES = { Armor.MBT_Merkava_IV: 25, Armor.APC_TPz_Fuchs: 5, Armor.MBT_Challenger_II: 25, + Armor.MBT_Chieftain_Mk_3: 20, Armor.IFV_Marder: 10, Armor.IFV_Warrior: 10, Armor.IFV_LAV_25: 7, @@ -534,6 +537,7 @@ PRICES = { Artillery.Mortar_2B11_120mm: 4, Artillery.SPH_Dana_vz77_152mm: 26, Artillery.PLZ_05: 25, + Artillery.SPH_T155_Firtina_155mm: 28, Unarmed.LUV_UAZ_469_Jeep: 3, Unarmed.Truck_Ural_375: 3, Infantry.Infantry_M4: 1, @@ -885,6 +889,7 @@ UNIT_BY_TASK = { Armor.IFV_BMP_3, Armor.IFV_BMP_3, Armor.IFV_BMD_1, + Armor.LT_PT_76, Armor.ZBD_04A, Armor.ZBD_04A, Armor.ZBD_04A, @@ -913,6 +918,8 @@ UNIT_BY_TASK = { Armor.APC_TPz_Fuchs, Armor.ATGM_HMMWV, Armor.ATGM_HMMWV, + Armor.ATGM_VAB_Mephisto, + Armor.ATGM_VAB_Mephisto, Armor.Scout_HMMWV, Armor.Scout_HMMWV, Armor.IFV_M2A2_Bradley, @@ -941,6 +948,7 @@ UNIT_BY_TASK = { Armor.MBT_Leclerc, Armor.MBT_Leopard_2A6M, Armor.MBT_Challenger_II, + Armor.MBT_Chieftain_Mk_3, Armor.MBT_Merkava_IV, Armor.MT_Pz_Kpfw_V_Panther_Ausf_G, Armor.Tk_PzIV_H, @@ -1002,6 +1010,7 @@ UNIT_BY_TASK = { Artillery.MLRS_9K57_Uragan_BM_27_220mm, Artillery.MLRS_9A52_Smerch_HE_300mm, Artillery.SPH_Dana_vz77_152mm, + Artillery.SPH_T155_Firtina_155mm, Artillery.PLZ_05, Artillery.SPG_M12_GMC_155mm, Armor.SPG_Sturmpanzer_IV_Brummbar, @@ -1241,7 +1250,6 @@ REWARDS = { "fuel": 2, "ammo": 2, "farp": 1, - "fob": 1, # TODO: Should generate no cash once they generate units. "factory": 10, "comms": 10, diff --git a/game/game.py b/game/game.py index 637c15b3..770c849a 100644 --- a/game/game.py +++ b/game/game.py @@ -34,6 +34,7 @@ from .procurement import AircraftProcurementRequest, ProcurementAi from .profiling import logged_duration from .settings import Settings from .theater import ConflictTheater +from .theater.bullseye import Bullseye from .theater.transitnetwork import TransitNetwork, TransitNetworkBuilder from .threatzones import ThreatZones from .transfers import PendingTransfers @@ -130,6 +131,9 @@ class Game: self.blue_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.transfers = PendingTransfers(self) @@ -201,6 +205,11 @@ class Game: return self.player_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): if self.settings.version == "dev": # always generate all events for dev @@ -337,10 +346,17 @@ class Game: 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: self.events = [] self._generate_events() + self.set_bullseye() + # Update statistics self.game_stats.update(self) diff --git a/game/operation/operation.py b/game/operation/operation.py index 0b43e45c..3ca893e5 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -108,8 +108,12 @@ class Operation: @classmethod def _setup_mission_coalitions(cls): - cls.current_mission.coalition["blue"] = Coalition("blue") - cls.current_mission.coalition["red"] = Coalition("red") + cls.current_mission.coalition["blue"] = Coalition( + "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 e_country = cls.game.enemy_country @@ -171,13 +175,16 @@ class Operation: gen.add_dynamic_runway(dynamic_runway) 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: - gen.add_awacs(aewc) + if aewc.blue: + gen.add_awacs(aewc) for jtac in jtacs: - gen.add_jtac(jtac) + if jtac.blue: + gen.add_jtac(jtac) for flight in airgen.flights: gen.add_flight(flight) @@ -317,13 +324,8 @@ class Operation: # Setup combined arms parameters cls.current_mission.groundControl.pilot_can_control_vehicles = cls.ca_slots > 0 - if cls.game.player_country in [ - country.name - 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 + cls.current_mission.groundControl.blue_tactical_commander = cls.ca_slots + cls.current_mission.groundControl.blue_observer = 1 # Options forcedoptionsgen = ForcedOptionsGenerator(cls.current_mission, cls.game) @@ -453,7 +455,7 @@ class Operation: for tanker in airsupportgen.air_support.tankers: luaData["Tankers"][tanker.callsign] = { - "dcsGroupName": tanker.dcsGroupName, + "dcsGroupName": tanker.group_name, "callsign": tanker.callsign, "variant": tanker.variant, "radio": tanker.freq.mhz, @@ -463,14 +465,14 @@ class Operation: if airsupportgen.air_support.awacs: for awacs in airsupportgen.air_support.awacs: luaData["AWACs"][awacs.callsign] = { - "dcsGroupName": awacs.dcsGroupName, + "dcsGroupName": awacs.group_name, "callsign": awacs.callsign, "radio": awacs.freq.mhz, } for jtac in jtacs: luaData["JTACs"][jtac.callsign] = { - "dcsGroupName": jtac.dcsGroupName, + "dcsGroupName": jtac.group_name, "callsign": jtac.callsign, "zone": jtac.region, "dcsUnit": jtac.unit_name, diff --git a/game/theater/bullseye.py b/game/theater/bullseye.py new file mode 100644 index 00000000..6d39821c --- /dev/null +++ b/game/theater/bullseye.py @@ -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) diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index f4b52de2..807041d8 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -41,6 +41,7 @@ from dcs.unitgroup import ( ) from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed +from .latlon import LatLon from ..helipad import Helipad from ..scenery_group import SceneryGroup from pyproj import CRS, Transformer @@ -583,15 +584,6 @@ class ReferencePoint: 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: terrain: Terrain diff --git a/game/theater/latlon.py b/game/theater/latlon.py new file mode 100644 index 00000000..dde2e442 --- /dev/null +++ b/game/theater/latlon.py @@ -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] diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index 5fd5f882..2fbb7c57 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -845,12 +845,7 @@ class FobGroundObjectGenerator(AirbaseGroundObjectGenerator): return True def generate_fob(self) -> None: - try: - category = self.faction.building_set[self.faction.building_set.index("fob")] - except IndexError: - logging.exception("Faction has no fob buildings defined") - return - + category = "fob" obj_name = self.control_point.name template = random.choice(list(self.templates[category].values())) point = self.control_point.position diff --git a/gen/aircraft.py b/gen/aircraft.py index 19391835..259ea5e1 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -815,12 +815,13 @@ class AircraftConflictGenerator: self.air_support.awacs.append( AwacsInfo( - dcsGroupName=str(group.name), + group_name=str(group.name), callsign=callsign, freq=channel, depature_location=flight.departure.name, end_time=flight.flight_plan.mission_departure_time, start_time=flight.flight_plan.mission_start_time, + blue=flight.departure.captured, ) ) @@ -1275,7 +1276,7 @@ class AircraftConflictGenerator: group, react_on_threat=OptReactOnThreat.Values.EvadeFire, roe=OptROE.Values.OpenFire, - rtb_winchester=OptRTBOnOutOfAmmo.Values.ASM, + rtb_winchester=OptRTBOnOutOfAmmo.Values.All, restrict_jettison=True, ) @@ -1410,9 +1411,6 @@ class AircraftConflictGenerator: flight: Flight, dynamic_runways: Dict[str, RunwayData], ) -> 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 self._setup_group(group, package, flight, dynamic_runways) self.configure_behavior( @@ -1761,7 +1759,7 @@ class DeadIngressBuilder(PydcsWaypointBuilder): if isinstance(target_group, TheaterGroundObject): tgroup = self.mission.find_group(target_group.group_name) 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["attackQtyLimit"] = False task.params["directionEnabled"] = False @@ -1866,12 +1864,11 @@ class StrikeIngressBuilder(PydcsWaypointBuilder): center.y += target.position.y center.x /= len(targets) center.y /= len(targets) - bombing = Bombing(center) + bombing = Bombing(center, weapon_type=WeaponType.Bombs) bombing.params["expend"] = "All" bombing.params["attackQtyLimit"] = False bombing.params["directionEnabled"] = False bombing.params["altitudeEnabled"] = False - bombing.params["weaponType"] = WeaponType.Bombs.value bombing.params["groupAttack"] = True waypoint.tasks.append(bombing) return waypoint @@ -1879,11 +1876,10 @@ class StrikeIngressBuilder(PydcsWaypointBuilder): def build_strike(self) -> MovingPoint: waypoint = super().build() 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 len(self.waypoint.targets) == 1: bombing.params["expend"] = "All" - bombing.params["weaponType"] = WeaponType.Auto.value bombing.params["groupAttack"] = True waypoint.tasks.append(bombing) @@ -1954,7 +1950,10 @@ class JoinPointBuilder(PydcsWaypointBuilder): EngageTargets( # TODO: From doctrine. max_distance=int(nautical_miles(30).meters), - targets=[Targets.All.Air.Planes.Fighters], + targets=[ + Targets.All.Air.Planes.Fighters, + Targets.All.Air.Planes.MultiroleFighters, + ], ) ) ) diff --git a/gen/airsupportgen.py b/gen/airsupportgen.py index 81d900aa..3b39a37b 100644 --- a/gen/airsupportgen.py +++ b/gen/airsupportgen.py @@ -35,23 +35,25 @@ AWACS_ALT = 13000 class AwacsInfo: """AWACS information for the kneeboard.""" - dcsGroupName: str + group_name: str callsign: str freq: RadioFrequency depature_location: Optional[str] start_time: Optional[timedelta] end_time: Optional[timedelta] + blue: bool @dataclass class TankerInfo: """Tanker information for the kneeboard.""" - dcsGroupName: str + group_name: str callsign: str variant: str freq: RadioFrequency tacan: TacanChannel + blue: bool @dataclass @@ -165,7 +167,14 @@ class AirSupportConflictGenerator: tanker_group.points[0].tasks.append(SetImmortalCommand(True)) 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: @@ -196,12 +205,13 @@ class AirSupportConflictGenerator: self.air_support.awacs.append( AwacsInfo( - dcsGroupName=str(awacs_flight.name), + group_name=str(awacs_flight.name), callsign=callsign_for_support_unit(awacs_flight), freq=freq, depature_location=None, start_time=None, end_time=None, + blue=True, ) ) else: diff --git a/gen/armor.py b/gen/armor.py index 31447e2c..3b9f93ee 100644 --- a/gen/armor.py +++ b/gen/armor.py @@ -68,11 +68,12 @@ INFANTRY_GROUP_SIZE = 5 class JtacInfo: """JTAC information.""" - dcsGroupName: str + group_name: str unit_name: str callsign: str region: str code: str + blue: bool # TODO: Radio info? Type? @@ -196,7 +197,14 @@ class GroundConflictGenerator: # Note: Will need to change if we ever add ground based JTAC. callsign = callsign_for_support_unit(jtac) 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( diff --git a/gen/fleet/lacombattanteII.py b/gen/fleet/lacombattanteII.py new file mode 100644 index 00000000..9d1ba6e1 --- /dev/null +++ b/gen/fleet/lacombattanteII.py @@ -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 + ) diff --git a/gen/fleet/ship_group_generator.py b/gen/fleet/ship_group_generator.py index 024d6e03..03ab852f 100644 --- a/gen/fleet/ship_group_generator.py +++ b/gen/fleet/ship_group_generator.py @@ -8,6 +8,7 @@ from gen.fleet.dd_group import ( ArleighBurkeGroupGenerator, OliverHazardPerryGroupGenerator, ) +from gen.fleet.lacombattanteII import LaCombattanteIIGroupGenerator from gen.fleet.lha_group import LHAGroupGenerator from gen.fleet.ru_dd_group import ( RussianNavyGroupGenerator, @@ -34,6 +35,7 @@ SHIP_MAP = { "KiloSubGroupGenerator": KiloSubGroupGenerator, "TangoSubGroupGenerator": TangoSubGroupGenerator, "Type54GroupGenerator": Type54GroupGenerator, + "LaCombattanteIIGroupGenerator": LaCombattanteIIGroupGenerator, } diff --git a/gen/flights/ai_flight_planner_db.py b/gen/flights/ai_flight_planner_db.py index 32ee279d..730d4722 100644 --- a/gen/flights/ai_flight_planner_db.py +++ b/gen/flights/ai_flight_planner_db.py @@ -132,7 +132,6 @@ CAP_CAPABLE = [ MiG_29A, F_16C_50, FA_18C_hornet, - F_15E, F_16A, F_4E, JF_17, @@ -140,6 +139,7 @@ CAP_CAPABLE = [ MiG_21Bis, Mirage_2000_5, M_2000C, + F_15E, F_5E_3, MiG_19P, A_4E_C, diff --git a/gen/flights/flight.py b/gen/flights/flight.py index 6f843c40..5cf2f402 100644 --- a/gen/flights/flight.py +++ b/gen/flights/flight.py @@ -79,6 +79,7 @@ class FlightWaypointType(Enum): INGRESS_OCA_AIRCRAFT = 25 PICKUP = 26 DROP_OFF = 27 + BULLSEYE = 28 class FlightWaypoint: diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index b1b0cc70..308c8f5c 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -426,6 +426,7 @@ class BarCapFlightPlan(PatrollingFlightPlan): takeoff: FlightWaypoint land: FlightWaypoint divert: Optional[FlightWaypoint] + bullseye: FlightWaypoint def iter_waypoints(self) -> Iterator[FlightWaypoint]: yield self.takeoff @@ -438,6 +439,7 @@ class BarCapFlightPlan(PatrollingFlightPlan): yield self.land if self.divert is not None: yield self.divert + yield self.bullseye @dataclass(frozen=True) @@ -446,6 +448,7 @@ class CasFlightPlan(PatrollingFlightPlan): target: FlightWaypoint land: FlightWaypoint divert: Optional[FlightWaypoint] + bullseye: FlightWaypoint def iter_waypoints(self) -> Iterator[FlightWaypoint]: yield self.takeoff @@ -459,6 +462,7 @@ class CasFlightPlan(PatrollingFlightPlan): yield self.land if self.divert is not None: yield self.divert + yield self.bullseye def request_escort_at(self) -> Optional[FlightWaypoint]: return self.patrol_start @@ -472,6 +476,7 @@ class TarCapFlightPlan(PatrollingFlightPlan): takeoff: FlightWaypoint land: FlightWaypoint divert: Optional[FlightWaypoint] + bullseye: FlightWaypoint lead_time: timedelta def iter_waypoints(self) -> Iterator[FlightWaypoint]: @@ -485,6 +490,7 @@ class TarCapFlightPlan(PatrollingFlightPlan): yield self.land if self.divert is not None: yield self.divert + yield self.bullseye @property def tot_offset(self) -> timedelta: @@ -523,6 +529,7 @@ class StrikeFlightPlan(FormationFlightPlan): nav_from: List[FlightWaypoint] land: FlightWaypoint divert: Optional[FlightWaypoint] + bullseye: FlightWaypoint def iter_waypoints(self) -> Iterator[FlightWaypoint]: yield self.takeoff @@ -537,6 +544,7 @@ class StrikeFlightPlan(FormationFlightPlan): yield self.land if self.divert is not None: yield self.divert + yield self.bullseye @property def package_speed_waypoints(self) -> Set[FlightWaypoint]: @@ -641,6 +649,7 @@ class SweepFlightPlan(LoiterFlightPlan): nav_from: List[FlightWaypoint] land: FlightWaypoint divert: Optional[FlightWaypoint] + bullseye: FlightWaypoint lead_time: timedelta def iter_waypoints(self) -> Iterator[FlightWaypoint]: @@ -653,6 +662,7 @@ class SweepFlightPlan(LoiterFlightPlan): yield self.land if self.divert is not None: yield self.divert + yield self.bullseye @property def tot_waypoint(self) -> Optional[FlightWaypoint]: @@ -704,6 +714,7 @@ class AwacsFlightPlan(LoiterFlightPlan): nav_from: List[FlightWaypoint] land: FlightWaypoint divert: Optional[FlightWaypoint] + bullseye: FlightWaypoint def iter_waypoints(self) -> Iterator[FlightWaypoint]: yield self.takeoff @@ -713,6 +724,7 @@ class AwacsFlightPlan(LoiterFlightPlan): yield self.land if self.divert is not None: yield self.divert + yield self.bullseye @property def mission_start_time(self) -> Optional[timedelta]: @@ -746,6 +758,7 @@ class AirliftFlightPlan(FlightPlan): nav_to_home: List[FlightWaypoint] land: FlightWaypoint divert: Optional[FlightWaypoint] + bullseye: FlightWaypoint def iter_waypoints(self) -> Iterator[FlightWaypoint]: yield self.takeoff @@ -758,6 +771,7 @@ class AirliftFlightPlan(FlightPlan): yield self.land if self.divert is not None: yield self.divert + yield self.bullseye @property def tot_waypoint(self) -> Optional[FlightWaypoint]: @@ -1053,6 +1067,7 @@ class FlightPlanBuilder: ), land=builder.land(flight.arrival), divert=builder.divert(flight.divert), + bullseye=builder.bullseye(), hold=start, hold_duration=timedelta(hours=4), ) @@ -1151,6 +1166,7 @@ class FlightPlanBuilder: patrol_end=end, land=builder.land(flight.arrival), divert=builder.divert(flight.divert), + bullseye=builder.bullseye(), ) def generate_sweep(self, flight: Flight) -> SweepFlightPlan: @@ -1187,6 +1203,7 @@ class FlightPlanBuilder: sweep_end=end, land=builder.land(flight.arrival), divert=builder.divert(flight.divert), + bullseye=builder.bullseye(), ) def generate_transport(self, flight: Flight) -> AirliftFlightPlan: @@ -1238,6 +1255,7 @@ class FlightPlanBuilder: ), land=builder.land(flight.arrival), divert=builder.divert(flight.divert), + bullseye=builder.bullseye(), ) def racetrack_for_objective( @@ -1389,6 +1407,7 @@ class FlightPlanBuilder: patrol_end=end, land=builder.land(flight.arrival), divert=builder.divert(flight.divert), + bullseye=builder.bullseye(), ) def generate_dead( @@ -1517,6 +1536,7 @@ class FlightPlanBuilder: ), land=builder.land(flight.arrival), divert=builder.divert(flight.divert), + bullseye=builder.bullseye(), ) def generate_cas(self, flight: Flight) -> CasFlightPlan: @@ -1562,6 +1582,7 @@ class FlightPlanBuilder: patrol_end=builder.egress(egress, location), land=builder.land(flight.arrival), divert=builder.divert(flight.divert), + bullseye=builder.bullseye(), ) @staticmethod @@ -1696,6 +1717,7 @@ class FlightPlanBuilder: ), land=builder.land(flight.arrival), divert=builder.divert(flight.divert), + bullseye=builder.bullseye(), ) def _retreating_rendezvous_point(self, attack_transition: Point) -> Point: diff --git a/gen/flights/loadouts.py b/gen/flights/loadouts.py index 2c30954b..c9ae20c4 100644 --- a/gen/flights/loadouts.py +++ b/gen/flights/loadouts.py @@ -35,6 +35,9 @@ class Loadout: new_pylons = dict(self.pylons) for pylon_number, weapon in self.pylons.items(): + if weapon is None: + del new_pylons[pylon_number] + continue if not weapon.available_on(date): pylon = Pylon.for_aircraft(unit_type, pylon_number) for fallback in weapon.fallbacks: @@ -44,6 +47,8 @@ class Loadout: continue new_pylons[pylon_number] = fallback break + else: + del new_pylons[pylon_number] return Loadout(f"{self.name} ({date.year})", new_pylons, date) @classmethod diff --git a/gen/flights/waypointbuilder.py b/gen/flights/waypointbuilder.py index ffb1e6ab..670cdc01 100644 --- a/gen/flights/waypointbuilder.py +++ b/gen/flights/waypointbuilder.py @@ -50,6 +50,7 @@ class WaypointBuilder: self.threat_zones = game.threat_zone_for(not player) self.navmesh = game.navmesh_for(player) self.targets = targets + self._bullseye = game.bullseye_for(player) @property def is_helo(self) -> bool: @@ -145,6 +146,19 @@ class WaypointBuilder: waypoint.only_for_player = True 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: waypoint = FlightWaypoint( FlightWaypointType.LOITER, diff --git a/gen/ground_forces/ai_ground_planner_db.py b/gen/ground_forces/ai_ground_planner_db.py index 3fee5901..fe200fbd 100644 --- a/gen/ground_forces/ai_ground_planner_db.py +++ b/gen/ground_forces/ai_ground_planner_db.py @@ -15,10 +15,12 @@ TYPE_TANKS = [ Armor.MBT_Leopard_1A3, Armor.MBT_Leclerc, Armor.MBT_Challenger_II, + Armor.MBT_Chieftain_Mk_3, Armor.MBT_M1A2_Abrams, Armor.MBT_M60A3_Patton, Armor.MBT_Merkava_IV, Armor.ZTZ_96B, + Armor.LT_PT_76, # WW2 Armor.MT_Pz_Kpfw_V_Panther_Ausf_G, Armor.Tk_PzIV_H, @@ -45,6 +47,7 @@ TYPE_TANKS = [ TYPE_ATGM = [ Armor.ATGM_HMMWV, + Armor.ATGM_VAB_Mephisto, Armor.ATGM_Stryker, Armor.IFV_BMP_2, # WW2 (Tank Destroyers) @@ -114,6 +117,7 @@ TYPE_ARTILLERY = [ Artillery.MLRS_M270_227mm, Artillery.SPM_2S9_Nona_120mm_M, Artillery.SPH_Dana_vz77_152mm, + Artillery.SPH_T155_Firtina_155mm, Artillery.PLZ_05, Artillery.SPH_2S19_Msta_152mm, Artillery.MLRS_9A52_Smerch_CM_300mm, diff --git a/gen/kneeboard.py b/gen/kneeboard.py index c0035b06..e28ded01 100644 --- a/gen/kneeboard.py +++ b/gen/kneeboard.py @@ -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. """ import datetime +import textwrap from collections import defaultdict from dataclasses import dataclass from pathlib import Path @@ -37,6 +38,7 @@ from tabulate import tabulate from game.data.alic import AlicCodes from game.db import find_unittype, unit_type_from_name from game.theater import ConflictTheater, TheaterGroundObject, LatLon +from game.theater.bullseye import Bullseye from game.utils import meters from .aircraft import AIRCRAFT_DATA, FlightData from .airsupportgen import AwacsInfo, TankerInfo @@ -257,21 +259,16 @@ class BriefingPage(KneeboardPage): def __init__( self, flight: FlightData, - comms: List[CommInfo], - awacs: List[AwacsInfo], - tankers: List[TankerInfo], - jtacs: List[JtacInfo], + bullseye: Bullseye, + theater: ConflictTheater, 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.bullseye = bullseye + self.theater = theater 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) @@ -301,6 +298,10 @@ class BriefingPage(KneeboardPage): headers=["#", "Action", "Alt", "Dist", "GSPD", "Time", "Departure"], ) + writer.text( + f"Bullseye: {self.format_ll(self.bullseye.to_lat_lon(self.theater))}" + ) + writer.table( [ [ @@ -311,6 +312,86 @@ class BriefingPage(KneeboardPage): ["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 writer.heading("AEW&C") aewc_ladder = [] @@ -368,44 +449,6 @@ class BriefingPage(KneeboardPage): 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: channel = self.flight.channel_for(frequency) if channel is None: @@ -558,6 +601,13 @@ class KneeboardGenerator(MissionInfoGenerator): """Returns a list of kneeboard pages for the given flight.""" pages: List[KneeboardPage] = [ BriefingPage( + flight, + self.game.bullseye_for(flight.friendly), + self.game.theater, + self.mission.start_time, + self.dark_kneeboard, + ), + SupportPage( flight, self.comms, self.awacs, diff --git a/gen/triggergen.py b/gen/triggergen.py index 7df4838f..fc68f96e 100644 --- a/gen/triggergen.py +++ b/gen/triggergen.py @@ -209,16 +209,6 @@ class TriggersGenerator: player_coalition = "blue" 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_allegiances(player_coalition, enemy_coalition) self._gen_markers() diff --git a/pydcs b/pydcs index dec648d2..4972988c 160000 --- a/pydcs +++ b/pydcs @@ -1 +1 @@ -Subproject commit dec648d27f74c394dd6e85e83cc09e4cd823653d +Subproject commit 4972988c978f2057e7aa06919c4de71ee9a06ea5 diff --git a/qt_ui/widgets/map/mapmodel.py b/qt_ui/widgets/map/mapmodel.py index 0a77094f..bdb379d3 100644 --- a/qt_ui/widgets/map/mapmodel.py +++ b/qt_ui/widgets/map/mapmodel.py @@ -375,6 +375,7 @@ class WaypointJs(QObject): timingChanged = Signal() isTakeoffChanged = Signal() isDivertChanged = Signal() + isBullseyeChanged = Signal() def __init__( self, @@ -439,6 +440,10 @@ class WaypointJs(QObject): def isDivert(self) -> bool: 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) def setPosition(self, position: LeafletLatLon) -> str: point = self.theater.ll_to_point(LatLon(*position)) diff --git a/resources/factions/NATO_Desert_Storm.json b/resources/factions/NATO_Desert_Storm.json index 30a6e30b..0c02a3d1 100644 --- a/resources/factions/NATO_Desert_Storm.json +++ b/resources/factions/NATO_Desert_Storm.json @@ -47,7 +47,8 @@ "MBT_Challenger_II", "MBT_M60A3_Patton", "SPG_Stryker_MGS", - "SAM_Avenger__Stinger" + "SAM_Avenger__Stinger", + "ATGM_VAB_Mephisto" ], "artillery_units": [ "MLRS_M270_227mm", diff --git a/resources/factions/bluefor_modern.json b/resources/factions/bluefor_modern.json index 2794fe88..a07ba747 100644 --- a/resources/factions/bluefor_modern.json +++ b/resources/factions/bluefor_modern.json @@ -46,7 +46,8 @@ "Scout_HMMWV", "ATGM_HMMWV", "SAM_Linebacker___Bradley_M6", - "SAM_Avenger__Stinger" + "SAM_Avenger__Stinger", + "ATGM_VAB_Mephisto" ], "artillery_units": [ "MLRS_M270_227mm", diff --git a/resources/factions/france_1985_frenchpack.json b/resources/factions/france_1985_frenchpack.json index 86bad8bc..9d9a80b9 100644 --- a/resources/factions/france_1985_frenchpack.json +++ b/resources/factions/france_1985_frenchpack.json @@ -23,13 +23,10 @@ "ERC_90", "TRM_2000_PAMELA", "VAB__50", - "VAB_MEPHISTO", - "VAB_T20_13", + "ATGM_VAB_Mephisto", "VAB_T20_13", "VBL__50", "VBL_AANF1", - "VBAE_CRAB", - "VBAE_CRAB_MMP", "AMX_30B2", "SAM_Roland_ADS" ], diff --git a/resources/factions/france_1995.json b/resources/factions/france_1995.json index e81fca28..de7d313f 100644 --- a/resources/factions/france_1995.json +++ b/resources/factions/france_1995.json @@ -21,10 +21,8 @@ "MBT_Leclerc", "APC_TPz_Fuchs", "Scout_Cobra", - "ATGM_Stryker", "IFV_LAV_25", - "Scout_HMMWV", - "ATGM_HMMWV", + "ATGM_VAB_Mephisto", "SAM_Roland_ADS" ], "artillery_units": [ diff --git a/resources/factions/france_2005_frenchpack.json b/resources/factions/france_2005_frenchpack.json index bae4fbf3..77eb2dce 100644 --- a/resources/factions/france_2005_frenchpack.json +++ b/resources/factions/france_2005_frenchpack.json @@ -24,7 +24,6 @@ "ERC_90", "TRM_2000_PAMELA", "VAB__50", - "VAB_MEPHISTO", "VAB_T20_13", "VAB_T20_13", "VBL__50", @@ -33,7 +32,8 @@ "VBAE_CRAB_MMP", "AMX_30B2", "Leclerc_Serie_XXI", - "SAM_Roland_ADS" + "SAM_Roland_ADS", + "ATGM_VAB_Mephisto" ], "artillery_units": [ "MLRS_M270_227mm", diff --git a/resources/factions/georgia_2008.json b/resources/factions/georgia_2008.json index 08825296..f1937456 100644 --- a/resources/factions/georgia_2008.json +++ b/resources/factions/georgia_2008.json @@ -45,6 +45,9 @@ "ZU23Generator", "ZU23UralGenerator" ], + "navy_generators": [ + "LaCombattanteIIGroupGenerator" + ], "requirements": {}, "has_jtac": true, "jtac_unit": "MQ_9_Reaper" diff --git a/resources/factions/germany_1990.json b/resources/factions/germany_1990.json index 57e5b36e..1d7e9872 100644 --- a/resources/factions/germany_1990.json +++ b/resources/factions/germany_1990.json @@ -61,7 +61,8 @@ "helicopter_carrier_names": [ ], "navy_generators": [ - "OliverHazardPerryGroupGenerator" + "OliverHazardPerryGroupGenerator", + "LaCombattanteIIGroupGenerator" ], "has_jtac": true, "jtac_unit": "MQ_9_Reaper" diff --git a/resources/factions/greece_2005.json b/resources/factions/greece_2005.json index 5e20bf24..b4586083 100644 --- a/resources/factions/greece_2005.json +++ b/resources/factions/greece_2005.json @@ -46,6 +46,9 @@ "HawkEwrGenerator", "FlatFaceGenerator" ], + "navy_generators": [ + "LaCombattanteIIGroupGenerator" + ], "has_jtac": true, "jtac_unit": "MQ_9_Reaper" } diff --git a/resources/factions/insurgents.json b/resources/factions/insurgents.json index 9cbec87b..cbf7b3b3 100644 --- a/resources/factions/insurgents.json +++ b/resources/factions/insurgents.json @@ -9,6 +9,7 @@ "Scout_Cobra", "APC_MTLB", "Scout_BRDM_2", + "LT_PT_76", "SPAAA_ZU_23_2_Insurgent_Mounted_Ural_375" ], "artillery_units": [ diff --git a/resources/factions/insurgents_hard.json b/resources/factions/insurgents_hard.json index b90da386..14825184 100644 --- a/resources/factions/insurgents_hard.json +++ b/resources/factions/insurgents_hard.json @@ -11,6 +11,7 @@ "Scout_BRDM_2", "APC_BTR_80", "APC_BTR_RD", + "LT_PT_76", "IFV_BMP_1", "MBT_T_55", "SPAAA_ZU_23_2_Insurgent_Mounted_Ural_375", diff --git a/resources/factions/iran_1988.json b/resources/factions/iran_1988.json index 21fa2707..b73a2f76 100644 --- a/resources/factions/iran_1988.json +++ b/resources/factions/iran_1988.json @@ -20,6 +20,7 @@ "APC_M113", "APC_BTR_80", "MBT_M60A3_Patton", + "MBT_Chieftain_Mk_3", "IFV_BMP_1", "SPAAA_ZSU_23_4_Shilka_Gun_Dish", "SPAAA_ZSU_57_2", @@ -75,7 +76,7 @@ ], "coastal_group_count": 2, "navy_generators": [ - "GrishaGroupGenerator", + "LaCombattanteIIGroupGenerator", "MolniyaGroupGenerator" ], "has_jtac": true, diff --git a/resources/factions/iran_2015.json b/resources/factions/iran_2015.json index 15a0771e..8efd7301 100644 --- a/resources/factions/iran_2015.json +++ b/resources/factions/iran_2015.json @@ -26,6 +26,7 @@ "APC_M113", "APC_BTR_80", "MBT_M60A3_Patton", + "MBT_Chieftain_Mk_3", "IFV_BMP_1", "MBT_T_72B", "SPAAA_ZSU_23_4_Shilka_Gun_Dish", @@ -85,7 +86,8 @@ "coastal_group_count": 3, "navy_generators": [ "GrishaGroupGenerator", - "MolniyaGroupGenerator" + "MolniyaGroupGenerator", + "LaCombattanteIIGroupGenerator" ], "has_jtac": true, "jtac_unit": "MQ_9_Reaper" diff --git a/resources/factions/iraq_1991.json b/resources/factions/iraq_1991.json index 591b886a..ecb9579d 100644 --- a/resources/factions/iraq_1991.json +++ b/resources/factions/iraq_1991.json @@ -29,8 +29,10 @@ "APC_MTLB", "MBT_T_55", "MBT_T_72B", + "MBT_Chieftain_Mk_3", "APC_BTR_80", "Scout_BRDM_2", + "LT_PT_76", "SPH_2S1_Gvozdika_122mm", "SPAAA_ZSU_57_2", "SPAAA_ZSU_23_4_Shilka_Gun_Dish" diff --git a/resources/factions/libya_2011.json b/resources/factions/libya_2011.json index 0b83dc6e..cf6b45cd 100644 --- a/resources/factions/libya_2011.json +++ b/resources/factions/libya_2011.json @@ -21,6 +21,7 @@ "Scout_BRDM_2", "MBT_T_72B", "MBT_T_55", + "LT_PT_76", "SPAAA_ZSU_23_4_Shilka_Gun_Dish", "SAM_SA_8_Osa_Gecko_TEL" ], @@ -71,6 +72,6 @@ "carrier_names": [ ], "navy_generators": [ - "GrishaGroupGenerator", "MolniyaGroupGenerator" + "GrishaGroupGenerator", "MolniyaGroupGenerator", "LaCombattanteIIGroupGenerator" ] } diff --git a/resources/factions/north_korea_2000.json b/resources/factions/north_korea_2000.json index cbd3d040..ffd01032 100644 --- a/resources/factions/north_korea_2000.json +++ b/resources/factions/north_korea_2000.json @@ -25,6 +25,7 @@ "MBT_T_55", "MBT_T_72B", "MBT_T_80U", + "LT_PT_76", "SPAAA_ZSU_57_2", "SAM_SA_9_Strela_1_Gaskin_TEL" ], diff --git a/resources/factions/russia_1955.json b/resources/factions/russia_1955.json index 624daaae..dd34fd26 100644 --- a/resources/factions/russia_1955.json +++ b/resources/factions/russia_1955.json @@ -17,6 +17,7 @@ "Grad_MRL_FDDM__FC", "APC_MTLB", "MBT_T_55", + "LT_PT_76", "SPAAA_ZU_23_2_Mounted_Ural_375", "AAA_8_8cm_Flak_18", "AAA_S_60_57mm" diff --git a/resources/factions/russia_1965.json b/resources/factions/russia_1965.json index 39685e7b..b7b28e60 100644 --- a/resources/factions/russia_1965.json +++ b/resources/factions/russia_1965.json @@ -22,6 +22,7 @@ "APC_BTR_RD", "IFV_BMD_1", "IFV_BMP_1", + "LT_PT_76", "MBT_T_55", "SPAAA_ZU_23_2_Mounted_Ural_375", "SPAAA_ZSU_57_2", diff --git a/resources/factions/russia_1970_limited_air.json b/resources/factions/russia_1970_limited_air.json index 3f8cf0aa..82214183 100644 --- a/resources/factions/russia_1970_limited_air.json +++ b/resources/factions/russia_1970_limited_air.json @@ -14,6 +14,7 @@ "APC_BTR_80", "IFV_BMD_1", "IFV_BMP_1", + "LT_PT_76", "MBT_T_55", "SPAAA_ZSU_57_2" ], diff --git a/resources/factions/russia_1975.json b/resources/factions/russia_1975.json index 6fe0f30c..a98541bb 100644 --- a/resources/factions/russia_1975.json +++ b/resources/factions/russia_1975.json @@ -27,6 +27,7 @@ "APC_BTR_80", "IFV_BMD_1", "IFV_BMP_1", + "LT_PT_76", "MBT_T_55", "SAM_SA_8_Osa_Gecko_TEL" ], diff --git a/resources/factions/syria_1967.json b/resources/factions/syria_1967.json index 5e092ed9..cab0ceb8 100644 --- a/resources/factions/syria_1967.json +++ b/resources/factions/syria_1967.json @@ -20,6 +20,7 @@ "Scout_BRDM_2", "Tk_PzIV_H", "MBT_T_55", + "LT_PT_76", "SPAAA_ZU_23_2_Mounted_Ural_375", "SPAAA_ZSU_57_2", "AAA_S_60_57mm" diff --git a/resources/factions/syria_1967_with_ww2_weapons.json b/resources/factions/syria_1967_with_ww2_weapons.json index cfab65fc..cda98f1d 100644 --- a/resources/factions/syria_1967_with_ww2_weapons.json +++ b/resources/factions/syria_1967_with_ww2_weapons.json @@ -20,6 +20,7 @@ "frontline_units": [ "Scout_BRDM_2", "MBT_T_55", + "LT_PT_76", "Tk_PzIV_H", "SPG_StuG_III_Ausf__G", "SPG_Jagdpanzer_IV", diff --git a/resources/factions/syria_1973.json b/resources/factions/syria_1973.json index 302283fe..f5467de4 100644 --- a/resources/factions/syria_1973.json +++ b/resources/factions/syria_1973.json @@ -20,6 +20,7 @@ "IFV_BMP_1", "APC_MTLB", "MBT_T_55", + "LT_PT_76", "SPAAA_ZU_23_2_Mounted_Ural_375", "SPAAA_ZSU_57_2", "AAA_S_60_57mm" diff --git a/resources/factions/syria_1982.json b/resources/factions/syria_1982.json index d8beda8e..022fe6ea 100644 --- a/resources/factions/syria_1982.json +++ b/resources/factions/syria_1982.json @@ -21,6 +21,7 @@ "IFV_BMP_1", "APC_MTLB", "MBT_T_55", + "LT_PT_76", "MBT_T_72B", "SPAAA_ZU_23_2_Mounted_Ural_375", "SPAAA_ZSU_57_2", diff --git a/resources/factions/syria_2011.json b/resources/factions/syria_2011.json index 3a5a3d21..c5a3e375 100644 --- a/resources/factions/syria_2011.json +++ b/resources/factions/syria_2011.json @@ -27,6 +27,7 @@ "IFV_BMP_2", "APC_BTR_80", "Scout_BRDM_2", + "LT_PT_76", "APC_MTLB", "Scout_Cobra", "MBT_T_55", diff --git a/resources/factions/turkey_2005.json b/resources/factions/turkey_2005.json index dfe8df1e..f5ab355d 100644 --- a/resources/factions/turkey_2005.json +++ b/resources/factions/turkey_2005.json @@ -25,7 +25,7 @@ "SAM_Avenger__Stinger" ], "artillery_units": [ - "SPH_M109_Paladin_155mm" + "SPH_T155_Firtina_155mm" ], "logistics_units": [ "Truck_M818_6x6" diff --git a/resources/factions/uk_1990.json b/resources/factions/uk_1990.json index 492a3d99..0ecf564e 100644 --- a/resources/factions/uk_1990.json +++ b/resources/factions/uk_1990.json @@ -19,6 +19,7 @@ ], "frontline_units": [ "MBT_Challenger_II", + "MBT_Chieftain_Mk_3", "IFV_Warrior", "Scout_HMMWV", "ATGM_HMMWV", diff --git a/resources/ui/map/map.js b/resources/ui/map/map.js index 3aade142..1b421520 100644 --- a/resources/ui/map/map.js +++ b/resources/ui/map/map.js @@ -570,7 +570,7 @@ class Waypoint { } includeInPath() { - return !this.waypoint.isDivert; + return !this.waypoint.isDivert && !this.waypoint.isBullseye; } }