diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f8346069..e146388b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,11 +53,6 @@ jobs: env: TAG_NAME: ${{ github.ref }} run: | - $version = ($env:TAG_NAME -split "/") | Select-Object -Last 1 - (Get-Content .\installer\dcs_liberation.iss) -replace "{{version}}",$version | Out-File .\build\installer.iss - cd .\installer - iscc.exe ..\build\installer.iss - cd .. Copy-Item .\changelog.md .\dist - uses: actions/upload-artifact@v2 @@ -100,15 +95,7 @@ jobs: body_path: releasenotes.md draft: false prerelease: ${{ steps.version.outputs.prerelease }} - - - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./dcs_liberation.exe - asset_name: dcs_liberation.${{ steps.version.outputs.number }}.exe - asset_content_type: application/exe + - uses: actions/upload-release-asset@v1 env: diff --git a/changelog.md b/changelog.md index 094b8adc..d919e094 100644 --- a/changelog.md +++ b/changelog.md @@ -1,12 +1,17 @@ # 2.3.1 -## Fixes: +## Features/Improvements +* **[UX]** Added a warning message when the player is attempting to buy more planes at an already full airbase. +* **[Campaigns]** Migrated Syria full map to new format. (Thanks to Hawkmoon) +* **[Faction]** Added NATO desert Storm faction (Thanks to Hawkmoon) +## Fixes: * **[AI]** CAP flights will engage enemies again. +* **[Campaigns]** Fixed a missing path on the Caucasus Full Map campaign # 2.3.0 -# Features/Improvements +## Features/Improvements * **[Campaign Map]** Overhauled the campaign model * **[Campaign Map]** Possible to add FOB as control points * **[Campaign Map]** Added off-map spawn locations @@ -17,6 +22,7 @@ * **[Mission Generator]** Infantry squads on frontline can have manpads * **[Mission Generator]** Unused aircraft now spawned to allow for OCA strikes * **[Mission Generator]** Opfor now obeys parking limits +* **[Mission Generator]** Support for Anubis C-130 Hercules mod * **[Flight Planner]** Added fighter sweep missions. * **[Flight Planner]** Added BAI missions. * **[Flight Planner]** Added anti-ship missions. @@ -27,6 +33,7 @@ * **[QOL]** On liberation startup, your latest save game is loaded automatically * **[Units]** Reduced starting fuel load for C101 * **[UI]** Inform the user of the weather +* **[UI]** Added toolbar buttons to change map display settings * **[Game]** Added new Economy options for adjusting income multipliers and starting budgets. ## Fixes : @@ -40,7 +47,7 @@ # 2.2.1 -# Features/Improvements +## Features/Improvements * **[Factions]** Added factions : Georgia 2008, USN 1985, France 2005 Frenchpack by HerrTom * **[Factions]** Added map Persian Gulf full by Plob * **[Flight Planner]** Player flights with start delays under ten minutes will spawn immediately. diff --git a/game/db.py b/game/db.py index 5889e9c7..372ba93c 100644 --- a/game/db.py +++ b/game/db.py @@ -142,6 +142,7 @@ from dcs.task import ( SEAD, Task, Transport, + RunwayAttack, ) from dcs.terrain.terrain import Airport from dcs.unit import Ship, Unit, Vehicle @@ -823,6 +824,8 @@ UNIT_BY_TASK = { Armor.StuG_III_Ausf__G, Artillery.M12_GMC, Artillery.Sturmpanzer_IV_Brummbär, + Armor.Daimler_Armoured_Car, + Armor.LT_Mk_VII_Tetrarch, Artillery.MLRS_M270, Artillery.SPH_M109_Paladin, @@ -986,6 +989,7 @@ COMMON_OVERRIDE = { AntishipStrike: "ANTISHIP", GroundAttack: "STRIKE", Escort: "CAP", + RunwayAttack: "RUNWAY_ATTACK" } PLANE_PAYLOAD_OVERRIDES: Dict[Type[PlaneType], Dict[Type[Task], str]] = { diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index d38e62a6..4e180e7a 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -9,6 +9,9 @@ from itertools import tee from pathlib import Path from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union, cast +from shapely import geometry +from shapely import ops + from dcs import Mission from dcs.countries import ( CombinedJointTaskForcesBlue, @@ -472,6 +475,34 @@ class ConflictTheater: return True + def nearest_land_pos(self, point: Point, extend_dist: int = 50) -> Point: + """Returns the nearest point inside a land exclusion zone from point + `extend_dist` determines how far inside the zone the point should be placed""" + if self.is_on_land(point): + return point + point = geometry.Point(point.x, point.y) + nearest_points = [] + if not self.landmap: + raise RuntimeError("Landmap not initialized") + for inclusion_zone in self.landmap[0]: + nearest_pair = ops.nearest_points(point, inclusion_zone) + nearest_points.append(nearest_pair[1]) + min_distance = point.distance(nearest_points[0]) # type: geometry.Point + nearest_point = nearest_points[0] # type: geometry.Point + for pt in nearest_points[1:]: + distance = point.distance(pt) + if distance < min_distance: + min_distance = distance + nearest_point = pt + assert isinstance(nearest_point, geometry.Point) + point = Point(point.x, point.y) + nearest_point = Point(nearest_point.x, nearest_point.y) + new_point = point.point_from_heading( + point.heading_between_point(nearest_point), + point.distance_to_point(nearest_point) + extend_dist + ) + return new_point + def player_points(self) -> List[ControlPoint]: return [point for point in self.controlpoints if point.captured] diff --git a/game/version.py b/game/version.py index 5955ac0a..69a9f185 100644 --- a/game/version.py +++ b/game/version.py @@ -2,7 +2,7 @@ from pathlib import Path def _build_version_string() -> str: - components = ["2.3.0"] + components = ["2.3.1"] build_number_path = Path("resources/buildnumber") if build_number_path.exists(): with build_number_path.open("r") as build_number_file: diff --git a/gen/aircraft.py b/gen/aircraft.py index 5a602ece..e51b0f2e 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -65,6 +65,7 @@ from dcs.task import ( Targets, Task, WeaponType, + PinpointStrike, ) from dcs.terrain.terrain import Airport, NoParkingSlotError from dcs.translation import String @@ -728,6 +729,11 @@ class AircraftConflictGenerator: if for_task in db.PLANE_PAYLOAD_OVERRIDES[unit_type]: payload_name = db.PLANE_PAYLOAD_OVERRIDES[unit_type][for_task] group.load_loadout(payload_name) + if not group.units[0].pylons and for_task == RunwayAttack: + if PinpointStrike in db.PLANE_PAYLOAD_OVERRIDES[unit_type]: + logging.warning("No loadout for \"Runway Attack\" for the {}, defaulting to Strike loadout".format(str(unit_type))) + payload_name = db.PLANE_PAYLOAD_OVERRIDES[unit_type][PinpointStrike] + group.load_loadout(payload_name) did_load_loadout = True logging.info("Loaded overridden payload for {} - {} for task {}".format(unit_type, payload_name, for_task)) diff --git a/gen/airsupportgen.py b/gen/airsupportgen.py index fef24cc6..ed13d830 100644 --- a/gen/airsupportgen.py +++ b/gen/airsupportgen.py @@ -1,9 +1,10 @@ import logging from dataclasses import dataclass, field -from typing import List, Type +from typing import List, Type, Tuple from dcs.mission import Mission, StartType -from dcs.planes import IL_78M +from dcs.planes import IL_78M, KC130, KC135MPRS, KC_135 +from dcs.unittype import UnitType from dcs.task import ( AWACS, ActivateBeaconCommand, @@ -68,13 +69,24 @@ class AirSupportConflictGenerator: def support_tasks(cls) -> List[Type[MainTask]]: return [Refueling, AWACS] + @staticmethod + def _get_tanker_params(unit_type: Type[UnitType]) -> Tuple[int, int]: + if unit_type is KC130: + return (TANKER_ALT - 500, 596) + elif unit_type is KC_135: + return (TANKER_ALT, 770) + elif unit_type is KC135MPRS: + return (TANKER_ALT + 500, 596) + return (TANKER_ALT, 574) + def generate(self): player_cp = self.conflict.from_cp if self.conflict.from_cp.captured else self.conflict.to_cp fallback_tanker_number = 0 for i, tanker_unit_type in enumerate(db.find_unittype(Refueling, self.conflict.attackers_side)): - variant = db.unit_type_name(tanker_unit_type) + alt, airspeed = self._get_tanker_params(tanker_unit_type) + variant = db.unit_type_name(tanker_unit_type) freq = self.radio_registry.alloc_uhf() tacan = self.tacan_registry.alloc_for_band(TacanBand.Y) tanker_heading = self.conflict.to_cp.position.heading_between_point(self.conflict.from_cp.position) + TANKER_HEADING_OFFSET * i @@ -85,11 +97,11 @@ class AirSupportConflictGenerator: airport=None, plane_type=tanker_unit_type, position=tanker_position, - altitude=TANKER_ALT, + altitude=alt, race_distance=58000, frequency=freq.mhz, start_type=StartType.Warm, - speed=574, + speed=airspeed, tacanchannel=str(tacan), ) tanker_group.set_frequency(freq.mhz) diff --git a/gen/armor.py b/gen/armor.py index 79b009ef..c02abecf 100644 --- a/gen/armor.py +++ b/gen/armor.py @@ -177,8 +177,15 @@ class GroundConflictGenerator: forward_heading: int ) -> None: - infantry_position = group.points[0].position.random_point_within(250, 50) - + infantry_position = self.conflict.find_ground_position( + group.points[0].position.random_point_within(250, 50), + 500, + forward_heading, + self.conflict.theater + ) + if not infantry_position: + logging.warning("Could not find infantry position") + return if side == self.conflict.attackers_country: cp = self.conflict.from_cp else: @@ -230,6 +237,17 @@ class GroundConflictGenerator: heading=forward_heading, move_formation=PointAction.OffRoad) + def _set_reform_waypoint( + self, + dcs_group: VehicleGroup, + forward_heading: int + ) -> None: + """Setting a waypoint close to the spawn position allows the group to reform gracefully + rather than spin + """ + reform_point = dcs_group.position.point_from_heading(forward_heading, 50) + dcs_group.add_waypoint(reform_point) + def _plan_artillery_action( self, stance: CombatStance, @@ -242,6 +260,7 @@ class GroundConflictGenerator: Handles adding the DCS tasks for artillery groups for all combat stances. Returns True if tasking was added, returns False if the stance was not a combat stance. """ + self._set_reform_waypoint(dcs_group, forward_heading) if stance != CombatStance.RETREAT: hold_task = Hold() hold_task.number = 1 @@ -261,10 +280,10 @@ class GroundConflictGenerator: if stance != CombatStance.RETREAT: # Hold position - dcs_group.points[0].tasks.append(Hold()) - retreat = self.find_retreat_point(dcs_group, forward_heading, (int)(RETREAT_DISTANCE/3)) - dcs_group.add_waypoint(dcs_group.position.point_from_heading(forward_heading, 1), PointAction.OffRoad) dcs_group.points[1].tasks.append(Hold()) + retreat = self.find_retreat_point(dcs_group, heading_sum(forward_heading, 180), (int)(RETREAT_DISTANCE/3)) + dcs_group.add_waypoint(dcs_group.position.point_from_heading(forward_heading, 1), PointAction.OffRoad) + dcs_group.points[2].tasks.append(Hold()) dcs_group.add_waypoint(retreat, PointAction.OffRoad) artillery_fallback = TriggerOnce(Event.NoEvent, "ArtilleryRetreat #" + str(dcs_group.id)) @@ -302,6 +321,7 @@ class GroundConflictGenerator: Handles adding the DCS tasks for tank and IFV groups for all combat stances. Returns True if tasking was added, returns False if the stance was not a combat stance. """ + self._set_reform_waypoint(dcs_group, forward_heading) if stance == CombatStance.AGGRESSIVE: # Attack nearest enemy if any # Then move forward OR Attack enemy base if it is not too far away @@ -315,15 +335,20 @@ class GroundConflictGenerator: -RANDOM_OFFSET_ATTACK, RANDOM_OFFSET_ATTACK ) ) - dcs_group.add_waypoint(target.points[0].position + rand_offset, PointAction.OffRoad) - dcs_group.points[1].tasks.append(AttackGroup(target.id)) + target_point = self.conflict.theater.nearest_land_pos( + target.points[0].position + rand_offset + ) + dcs_group.add_waypoint(target_point) + dcs_group.points[2].tasks.append(AttackGroup(target.id)) if ( to_cp.position.distance_to_point(dcs_group.points[0].position) <= AGGRESIVE_MOVE_DISTANCE ): - attack_point = to_cp.position.random_point_within(500, 0) + attack_point = self.conflict.theater.nearest_land_pos( + to_cp.position.random_point_within(500, 0) + ) else: attack_point = self.find_offensive_point( dcs_group, @@ -336,7 +361,9 @@ class GroundConflictGenerator: # If the enemy base is close enough, the units will attack the base if to_cp.position.distance_to_point( dcs_group.points[0].position) <= BREAKTHROUGH_OFFENSIVE_DISTANCE: - attack_point = to_cp.position.random_point_within(500, 0) + attack_point = self.conflict.theater.nearest_land_pos( + to_cp.position.random_point_within(500, 0) + ) else: attack_point = self.find_offensive_point(dcs_group, forward_heading, BREAKTHROUGH_OFFENSIVE_DISTANCE) dcs_group.add_waypoint(attack_point, PointAction.OffRoad) @@ -353,10 +380,15 @@ class GroundConflictGenerator: RANDOM_OFFSET_ATTACK ) ) - dcs_group.add_waypoint(target.points[0].position+rand_offset, PointAction.OffRoad) - dcs_group.points[i].tasks.append(AttackGroup(target.id)) + target_point = self.conflict.theater.nearest_land_pos( + target.points[0].position+rand_offset + ) + dcs_group.add_waypoint(target_point, PointAction.OffRoad) + dcs_group.points[i + 1].tasks.append(AttackGroup(target.id)) if to_cp.position.distance_to_point(dcs_group.points[0].position) <= AGGRESIVE_MOVE_DISTANCE: - attack_point = to_cp.position.random_point_within(500, 0) + attack_point = self.conflict.theater.nearest_land_pos( + to_cp.position.random_point_within(500, 0) + ) dcs_group.add_waypoint(attack_point) if stance != CombatStance.RETREAT: @@ -375,10 +407,11 @@ class GroundConflictGenerator: Handles adding the DCS tasks for APC and ATGM groups for all combat stances. Returns True if tasking was added, returns False if the stance was not a combat stance. """ + self._set_reform_waypoint(dcs_group, forward_heading) if stance in [CombatStance.AGGRESSIVE, CombatStance.BREAKTHROUGH, CombatStance.ELIMINATION]: # APC & ATGM will never move too much forward, but will follow along any offensive if to_cp.position.distance_to_point(dcs_group.points[0].position) <= AGGRESIVE_MOVE_DISTANCE: - attack_point = to_cp.position.random_point_within(500, 0) + attack_point = self.conflict.theater.nearest_land_pos(to_cp.position.random_point_within(500, 0)) else: attack_point = self.find_offensive_point(dcs_group, forward_heading, AGGRESIVE_MOVE_DISTANCE) dcs_group.add_waypoint(attack_point, PointAction.OffRoad) @@ -466,8 +499,8 @@ class GroundConflictGenerator: self.mission.triggerrules.triggers.append(fallback) - @staticmethod def find_retreat_point( + self, dcs_group: VehicleGroup, frontline_heading: int, distance: int = RETREAT_DISTANCE @@ -478,10 +511,14 @@ class GroundConflictGenerator: :param frontline_heading: Heading of the frontline :return: dcs.mapping.Point object with the desired position """ - return dcs_group.points[0].position.point_from_heading(frontline_heading-180, distance) + desired_point = dcs_group.points[0].position.point_from_heading(heading_sum(frontline_heading, +180), distance) + if self.conflict.theater.is_on_land(desired_point): + return desired_point + return self.conflict.theater.nearest_land_pos(desired_point) + - @staticmethod def find_offensive_point( + self, dcs_group: VehicleGroup, frontline_heading: int, distance: int @@ -493,7 +530,10 @@ class GroundConflictGenerator: :param distance: Distance of the offensive (how far unit should move) :return: dcs.mapping.Point object with the desired position """ - return dcs_group.points[0].position.point_from_heading(frontline_heading, distance) + desired_point = dcs_group.points[0].position.point_from_heading(frontline_heading, distance) + if self.conflict.theater.is_on_land(desired_point): + return desired_point + return self.conflict.theater.nearest_land_pos(desired_point) @staticmethod def find_n_nearest_enemy_groups( @@ -559,15 +599,22 @@ class GroundConflictGenerator: return potential_target.points[0].position return None - def get_artilery_group_distance_from_frontline(self, group): + @staticmethod + def get_artilery_group_distance_from_frontline(group: CombatGroup) -> int: """ For artilery group, decide the distance from frontline with the range of the unit """ rg = group.units[0].threat_range - 7500 - if rg > DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY]: - rg = DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY] - if rg < DISTANCE_FROM_FRONTLINE[CombatGroupRole.TANK]: - rg = DISTANCE_FROM_FRONTLINE[CombatGroupRole.TANK] + 100 + if rg > DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][1]: + rg = random.randint( + DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][0], + DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][1] + ) + elif rg < DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][1]: + rg = random.randint( + DISTANCE_FROM_FRONTLINE[CombatGroupRole.TANK][0], + DISTANCE_FROM_FRONTLINE[CombatGroupRole.TANK][1] + ) return rg def get_valid_position_for_group( @@ -578,16 +625,13 @@ class GroundConflictGenerator: heading: int, spawn_heading: int ): - i = 0 - while i < 1000: - shifted = conflict_position.point_from_heading(heading, random.randint(0, combat_width)) - final_position = shifted.point_from_heading(spawn_heading, distance_from_frontline) + shifted = conflict_position.point_from_heading(heading, random.randint(0, combat_width)) + desired_point = shifted.point_from_heading( + spawn_heading, + distance_from_frontline + ) + return Conflict.find_ground_position(desired_point, combat_width, heading, self.conflict.theater) - if self.conflict.theater.is_on_land(final_position): - return final_position - i += 1 - continue - return None def _generate_groups( self, @@ -604,7 +648,10 @@ class GroundConflictGenerator: if group.role == CombatGroupRole.ARTILLERY: distance_from_frontline = self.get_artilery_group_distance_from_frontline(group) else: - distance_from_frontline = DISTANCE_FROM_FRONTLINE[group.role] + distance_from_frontline = random.randint( + DISTANCE_FROM_FRONTLINE[group.role][0], + DISTANCE_FROM_FRONTLINE[group.role][1] + ) final_position = self.get_valid_position_for_group( position, @@ -659,7 +706,7 @@ class GroundConflictGenerator: group = self.mission.vehicle_group( side, namegen.next_unit_name(side, cp.id, unit), unit, - position=self._group_point(at, distance_from_frontline), + position=at, group_size=count, heading=heading, move_formation=move_formation) diff --git a/gen/conflictgen.py b/gen/conflictgen.py index 65ea4058..68946ab2 100644 --- a/gen/conflictgen.py +++ b/gen/conflictgen.py @@ -91,8 +91,12 @@ class Conflict: return pos @classmethod - def find_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point: - """Finds the nearest valid ground position along a provided heading and it's inverse""" + def find_ground_position(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater, coerce=True) -> Optional[Point]: + """ + Finds the nearest valid ground position along a provided heading and it's inverse up to max_distance. + `coerce=True` will return the closest land position to `initial` regardless of heading or distance + `coerce=False` will return None if a point isn't found + """ pos = initial if theater.is_on_land(pos): return pos @@ -101,5 +105,9 @@ class Conflict: if theater.is_on_land(pos): return pos pos = initial.point_from_heading(opposite_heading(heading), distance) + if coerce: + pos = theater.nearest_land_pos(initial) + return pos logging.error("Didn't find ground position ({})!".format(initial)) - return initial + return None + diff --git a/gen/flights/waypointbuilder.py b/gen/flights/waypointbuilder.py index e8bcce7d..85a83d08 100644 --- a/gen/flights/waypointbuilder.py +++ b/gen/flights/waypointbuilder.py @@ -234,7 +234,7 @@ class WaypointBuilder: return self._target_area(f"STRIKE {target.name}", target) def sead_area(self, target: MissionTarget) -> FlightWaypoint: - return self._target_area(f"SEAD on {target.name}", target) + return self._target_area(f"SEAD on {target.name}", target, flyover=True) def dead_area(self, target: MissionTarget) -> FlightWaypoint: return self._target_area(f"DEAD on {target.name}", target) diff --git a/gen/ground_forces/ai_ground_planner.py b/gen/ground_forces/ai_ground_planner.py index b0f14df4..12df519f 100644 --- a/gen/ground_forces/ai_ground_planner.py +++ b/gen/ground_forces/ai_ground_planner.py @@ -187,14 +187,14 @@ class CombatGroupRole(Enum): DISTANCE_FROM_FRONTLINE = { - CombatGroupRole.TANK:3200, - CombatGroupRole.APC:8000, - CombatGroupRole.IFV:3700, - CombatGroupRole.ARTILLERY:18000, - CombatGroupRole.SHORAD:13000, - CombatGroupRole.LOGI:20000, - CombatGroupRole.INFANTRY:3000, - CombatGroupRole.ATGM:6200 + CombatGroupRole.TANK: (2200, 3200), + CombatGroupRole.APC: (7500, 8500), + CombatGroupRole.IFV: (2700, 3700), + CombatGroupRole.ARTILLERY: (16000, 18000), + CombatGroupRole.SHORAD: (12000, 13000), + CombatGroupRole.LOGI: (18000, 20000), + CombatGroupRole.INFANTRY: (2800, 3300), + CombatGroupRole.ATGM: (5200, 6200), } GROUP_SIZES_BY_COMBAT_STANCE = { diff --git a/gen/triggergen.py b/gen/triggergen.py index a0ccd641..6a24d703 100644 --- a/gen/triggergen.py +++ b/gen/triggergen.py @@ -82,7 +82,13 @@ class TriggersGenerator: airport.operating_level_air = 0 airport.operating_level_equipment = 0 airport.operating_level_fuel = 0 - + + for airport in self.mission.terrain.airport_list(): + if airport.id not in cp_ids: + airport.unlimited_fuel = True + airport.unlimited_munitions = True + airport.unlimited_aircrafts = True + for cp in self.game.theater.controlpoints: if isinstance(cp, Airfield): self.mission.terrain.airport_by_id(cp.at.id).set_coalition(cp.captured and player_coalition or enemy_coalition) diff --git a/qt_ui/windows/QLiberationWindow.py b/qt_ui/windows/QLiberationWindow.py index b1104f47..fbce72c1 100644 --- a/qt_ui/windows/QLiberationWindow.py +++ b/qt_ui/windows/QLiberationWindow.py @@ -284,12 +284,13 @@ class QLiberationWindow(QMainWindow): "

Authors

" + \ "

DCS Liberation was originally developed by shdwp, DCS Liberation 2.0 is a partial rewrite based on this work by Khopa." \ "

Contributors

" + \ - "shdwp, Khopa, ColonelPanic, Roach, Wrycu, calvinmorrow, JohanAberg, Deus, root0fall, Captain Cody, steveveepee, pedromagueija, parithon, bwRavencl, davidp57, Plob" + \ + "shdwp, Khopa, ColonelPanic, Roach, Wrycu, calvinmorrow, JohanAberg, Deus, root0fall, Captain Cody, steveveepee, pedromagueija, parithon, bwRavencl, davidp57, Plob, Hawkmoon" + \ "

Special Thanks :

" \ "rp- for the pydcs framework
"\ "Grimes (mrSkortch) & Speed for the MIST framework
"\ "Ciribob for the JTACAutoLase.lua script
"\ - "Walder for the Skynet-IADS script
" + "Walder for the Skynet-IADS script
"\ + "Anubis Yinepu for the Hercules Cargo script
" about = QMessageBox() about.setWindowTitle("About DCS Liberation") about.setIcon(QMessageBox.Icon.Information) diff --git a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py index 82c7033d..79ba0d1f 100644 --- a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py @@ -88,6 +88,9 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): if self.maximum_units > 0: if self.cp.unclaimed_parking(self.game_model.game) <= 0: logging.debug(f"No space for additional aircraft at {self.cp}.") + QMessageBox.warning( + self, "No space for additional aircraft", + f"There is no parking space left at {self.cp.name} to accommodate another plane.", QMessageBox.Ok) return super().buy(unit_type) diff --git a/resources/campaigns/syria_full_map.json b/resources/campaigns/syria_full_map.json deleted file mode 100644 index aa45dae0..00000000 --- a/resources/campaigns/syria_full_map.json +++ /dev/null @@ -1,185 +0,0 @@ -{ - "name": "Syria - Full Map", - "theater": "Syria", - "authors": "Khopa", - "description": "

Full map of Syria

Note: This scenario is heavy on performance, enabling \"culling\" in settings is highly recommended.

", - "player_points": [ - { - "type": "airbase", - "id": "Ramat David", - "size": 1000, - "importance": 1.4 - }, - { - "type": "carrier", - "id": 1001, - "x": -151000, - "y": -106000, - "captured_invert": true - }, - { - "type": "lha", - "id": 1002, - "x": -131000, - "y": -161000, - "captured_invert": true - } - ], - "enemy_points": [ - { - "type": "airbase", - "id": "King Hussein Air College", - "size": 1000, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Khalkhalah", - "size": 1000, - "importance": 1.2 - }, - { - "type": "airbase", - "id": "Al-Dumayr", - "size": 1000, - "importance": 1.2 - }, - { - "type": "airbase", - "id": "Al Qusayr", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Rene Mouawad", - "size": 1000, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Hama", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Bassel Al-Assad", - "size": 1000, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Palmyra", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Tabqa", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Jirah", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Aleppo", - "size": 1000, - "importance": 1.2 - }, - { - "type": "airbase", - "id": "Minakh", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Hatay", - "size": 1000, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Incirlik", - "size": 1000, - "importance": 1.4, - "captured_invert": true - } - ], - "links": [ - [ - "King Hussein Air College", - "Ramat David" - ], - [ - "Khalkhalah", - "King Hussein Air College" - ], - [ - "Al-Dumayr", - "Khalkhalah" - ], - [ - "Al Qusayr", - "Al-Dumayr" - ], - [ - "Al Qusayr", - "Hama" - ], - [ - "Al Qusayr", - "Palmyra" - ], - [ - "Al Qusayr", - "Rene Mouawad" - ], - [ - "Bassel Al-Assad", - "Rene Mouawad" - ], - [ - "Aleppo", - "Hama" - ], - [ - "Bassel Al-Assad", - "Hama" - ], - [ - "Bassel Al-Assad", - "Hatay" - ], - [ - "Palmyra", - "Tabqa" - ], - [ - "Jirah", - "Tabqa" - ], - [ - "Aleppo", - "Jirah" - ], - [ - "Aleppo", - "Minakh" - ], - [ - "Hatay", - "Minakh" - ], - [ - "Incirlik", - "Minakh" - ] - ] -} \ No newline at end of file diff --git a/resources/campaigns/syria_full_map_remastered.json b/resources/campaigns/syria_full_map_remastered.json index 7b327c1b..d0df2b54 100644 --- a/resources/campaigns/syria_full_map_remastered.json +++ b/resources/campaigns/syria_full_map_remastered.json @@ -1,7 +1,7 @@ { - "name": "Syria - Full Map Remastered", + "name": "Syria - Full Map", "theater": "Syria", "authors": "Hawkmoon", - "description": "

Full map of Syria remastered for 2.3

Note:For a better early game experience is suggested to give the AI an high amount of starting money This scenario is heavy on performance, enabling \"culling\" in settings is highly recommended.

", + "description": "

Full map of Syria

Note:For a better early game experience is suggested to give the AI an high amount of starting money This scenario is heavy on performance, enabling \"culling\" in settings is highly recommended.

", "miz": "syria_full_map_remastered.miz" } \ No newline at end of file diff --git a/resources/caulandmap.p b/resources/caulandmap.p index a65459cd..c9f77f43 100644 Binary files a/resources/caulandmap.p and b/resources/caulandmap.p differ diff --git a/resources/customized_payloads/F-22A.lua b/resources/customized_payloads/F-22A.lua index a14761df..3d1d2b82 100644 --- a/resources/customized_payloads/F-22A.lua +++ b/resources/customized_payloads/F-22A.lua @@ -282,7 +282,7 @@ local unitPayloads = { }, }, [8] = { - ["name"] = "RUNWAY STRIKE", + ["name"] = "RUNWAY_ATTACK", ["pylons"] = { [1] = { ["CLSID"] = "{5335D97A-35A5-4643-9D9B-026C75961E52}", diff --git a/resources/customized_payloads/Hercules.lua b/resources/customized_payloads/Hercules.lua index 2fc8590e..9f2012f6 100644 --- a/resources/customized_payloads/Hercules.lua +++ b/resources/customized_payloads/Hercules.lua @@ -2,27 +2,15 @@ local unitPayloads = { ["name"] = "Hercules", ["payloads"] = { [1] = { - ["name"] = "CAS", + ["name"] = "DEAD", ["pylons"] = { [1] = { - ["CLSID"] = "Herc_BattleStation", - ["num"] = 9, + ["CLSID"] = "Herc_GBU-43/B(MOAB)", + ["num"] = 11, }, [2] = { - ["CLSID"] = "{Herc_105mm_Howitzer}", - ["num"] = 8, - }, - [3] = { - ["CLSID"] = "{Herc_GAU_23A_Chain_Gun}", - ["num"] = 7, - }, - [4] = { - ["CLSID"] = "{Herc_M61_Vulcan_Rotary_Cannon}", - ["num"] = 6, - }, - [5] = { - ["CLSID"] = "Herc_JATO", - ["num"] = 1, + ["CLSID"] = "Herc_Soldier_Squad", + ["num"] = 12, }, }, ["tasks"] = { @@ -33,24 +21,12 @@ local unitPayloads = { ["name"] = "STRIKE", ["pylons"] = { [1] = { - ["CLSID"] = "Herc_BattleStation", - ["num"] = 9, + ["CLSID"] = "Herc_Soldier_Squad", + ["num"] = 12, }, [2] = { - ["CLSID"] = "{Herc_105mm_Howitzer}", - ["num"] = 8, - }, - [3] = { - ["CLSID"] = "{Herc_GAU_23A_Chain_Gun}", - ["num"] = 7, - }, - [4] = { - ["CLSID"] = "{Herc_M61_Vulcan_Rotary_Cannon}", - ["num"] = 6, - }, - [5] = { - ["CLSID"] = "Herc_JATO", - ["num"] = 1, + ["CLSID"] = "Herc_GBU-43/B(MOAB)", + ["num"] = 11, }, }, ["tasks"] = { @@ -61,24 +37,12 @@ local unitPayloads = { ["name"] = "ANTISHIP", ["pylons"] = { [1] = { - ["CLSID"] = "Herc_BattleStation", - ["num"] = 9, + ["CLSID"] = "Herc_GBU-43/B(MOAB)", + ["num"] = 11, }, [2] = { - ["CLSID"] = "{Herc_105mm_Howitzer}", - ["num"] = 8, - }, - [3] = { - ["CLSID"] = "{Herc_GAU_23A_Chain_Gun}", - ["num"] = 7, - }, - [4] = { - ["CLSID"] = "{Herc_M61_Vulcan_Rotary_Cannon}", - ["num"] = 6, - }, - [5] = { - ["CLSID"] = "Herc_JATO", - ["num"] = 1, + ["CLSID"] = "Herc_Soldier_Squad", + ["num"] = 12, }, }, ["tasks"] = { @@ -86,27 +50,15 @@ local unitPayloads = { }, }, [4] = { - ["name"] = "SEAD", + ["name"] = "OCA", ["pylons"] = { [1] = { - ["CLSID"] = "Herc_BattleStation", - ["num"] = 9, + ["CLSID"] = "Herc_Soldier_Squad", + ["num"] = 12, }, [2] = { - ["CLSID"] = "{Herc_105mm_Howitzer}", - ["num"] = 8, - }, - [3] = { - ["CLSID"] = "{Herc_GAU_23A_Chain_Gun}", - ["num"] = 7, - }, - [4] = { - ["CLSID"] = "{Herc_M61_Vulcan_Rotary_Cannon}", - ["num"] = 6, - }, - [5] = { - ["CLSID"] = "Herc_JATO", - ["num"] = 1, + ["CLSID"] = "Herc_GBU-43/B(MOAB)", + ["num"] = 11, }, }, ["tasks"] = { @@ -114,27 +66,15 @@ local unitPayloads = { }, }, [5] = { - ["name"] = "DEAD", + ["name"] = "CAS", ["pylons"] = { [1] = { - ["CLSID"] = "Herc_BattleStation", - ["num"] = 9, + ["CLSID"] = "Herc_GBU-43/B(MOAB)", + ["num"] = 11, }, [2] = { - ["CLSID"] = "{Herc_105mm_Howitzer}", - ["num"] = 8, - }, - [3] = { - ["CLSID"] = "{Herc_GAU_23A_Chain_Gun}", - ["num"] = 7, - }, - [4] = { - ["CLSID"] = "{Herc_M61_Vulcan_Rotary_Cannon}", - ["num"] = 6, - }, - [5] = { - ["CLSID"] = "Herc_JATO", - ["num"] = 1, + ["CLSID"] = "Herc_Soldier_Squad", + ["num"] = 12, }, }, ["tasks"] = { @@ -142,27 +82,15 @@ local unitPayloads = { }, }, [6] = { - ["name"] = "OCA", + ["name"] = "SEAD", ["pylons"] = { [1] = { - ["CLSID"] = "Herc_BattleStation", - ["num"] = 9, + ["CLSID"] = "Herc_GBU-43/B(MOAB)", + ["num"] = 11, }, [2] = { - ["CLSID"] = "{Herc_105mm_Howitzer}", - ["num"] = 8, - }, - [3] = { - ["CLSID"] = "{Herc_GAU_23A_Chain_Gun}", - ["num"] = 7, - }, - [4] = { - ["CLSID"] = "{Herc_M61_Vulcan_Rotary_Cannon}", - ["num"] = 6, - }, - [5] = { - ["CLSID"] = "Herc_JATO", - ["num"] = 1, + ["CLSID"] = "Herc_Soldier_Squad", + ["num"] = 12, }, }, ["tasks"] = { @@ -171,10 +99,22 @@ local unitPayloads = { }, [7] = { ["name"] = "CAP", + ["pylons"] = { + }, + ["tasks"] = { + [1] = 31, + }, + }, + [8] = { + ["name"] = "RUNWAY_STRIKE", ["pylons"] = { [1] = { - ["CLSID"] = "Herc_JATO", - ["num"] = 1, + ["CLSID"] = "Herc_Soldier_Squad", + ["num"] = 12, + }, + [2] = { + ["CLSID"] = "Herc_GBU-43/B(MOAB)", + ["num"] = 11, }, }, ["tasks"] = { diff --git a/resources/customized_payloads/JF-17.lua b/resources/customized_payloads/JF-17.lua index bed8abb3..8e135655 100644 --- a/resources/customized_payloads/JF-17.lua +++ b/resources/customized_payloads/JF-17.lua @@ -2,44 +2,6 @@ local unitPayloads = { ["name"] = "JF-17", ["payloads"] = { [1] = { - ["name"] = "CAP", - ["pylons"] = { - [1] = { - ["CLSID"] = "DIS_TANK800", - ["num"] = 5, - }, - [2] = { - ["CLSID"] = "DIS_TANK800", - ["num"] = 3, - }, - [3] = { - ["CLSID"] = "DIS_WMD7", - ["num"] = 4, - }, - [4] = { - ["CLSID"] = "DIS_PL-5EII", - ["num"] = 1, - }, - [5] = { - ["CLSID"] = "DIS_PL-5EII", - ["num"] = 7, - }, - [6] = { - ["CLSID"] = "DIS_SD-10_DUAL_R", - ["num"] = 6, - }, - [7] = { - ["CLSID"] = "DIS_SD-10_DUAL_L", - ["num"] = 2, - }, - }, - ["tasks"] = { - [1] = 10, - [2] = 11, - [3] = 19, - }, - }, - [2] = { ["name"] = "CAS", ["pylons"] = { [1] = { @@ -69,7 +31,7 @@ local unitPayloads = { [3] = 19, }, }, - [3] = { + [2] = { ["name"] = "STRIKE", ["pylons"] = { [1] = { @@ -103,6 +65,44 @@ local unitPayloads = { [3] = 19, }, }, + [3] = { + ["name"] = "CAP", + ["pylons"] = { + [1] = { + ["CLSID"] = "DIS_TANK800", + ["num"] = 5, + }, + [2] = { + ["CLSID"] = "DIS_TANK800", + ["num"] = 3, + }, + [3] = { + ["CLSID"] = "DIS_WMD7", + ["num"] = 4, + }, + [4] = { + ["CLSID"] = "DIS_PL-5EII", + ["num"] = 1, + }, + [5] = { + ["CLSID"] = "DIS_PL-5EII", + ["num"] = 7, + }, + [6] = { + ["CLSID"] = "DIS_SD-10_DUAL_R", + ["num"] = 6, + }, + [7] = { + ["CLSID"] = "DIS_SD-10_DUAL_L", + ["num"] = 2, + }, + }, + ["tasks"] = { + [1] = 10, + [2] = 11, + [3] = 19, + }, + }, [4] = { ["name"] = "SEAD", ["pylons"] = { @@ -179,6 +179,44 @@ local unitPayloads = { [3] = 19, }, }, + [6] = { + ["name"] = "RUNWAY_ATTACK", + ["pylons"] = { + [1] = { + ["CLSID"] = "DIS_TYPE200_DUAL_R", + ["num"] = 6, + }, + [2] = { + ["CLSID"] = "DIS_TANK800", + ["num"] = 5, + }, + [3] = { + ["CLSID"] = "DIS_TANK800", + ["num"] = 3, + }, + [4] = { + ["CLSID"] = "DIS_TYPE200_DUAL_L", + ["num"] = 2, + }, + [5] = { + ["CLSID"] = "DIS_PL-5EII", + ["num"] = 1, + }, + [6] = { + ["CLSID"] = "DIS_PL-5EII", + ["num"] = 7, + }, + [7] = { + ["CLSID"] = "DIS_WMD7", + ["num"] = 4, + }, + }, + ["tasks"] = { + [1] = 10, + [2] = 11, + [3] = 19, + }, + }, }, ["unitType"] = "JF-17", } diff --git a/resources/tools/cau_terrain.miz b/resources/tools/cau_terrain.miz index 01b84d44..5211c374 100644 Binary files a/resources/tools/cau_terrain.miz and b/resources/tools/cau_terrain.miz differ