diff --git a/changelog.md b/changelog.md
index 86772bff..c5016a58 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,14 +1,19 @@
# 2.1.1
-## Features/Imrpovements :
-* **[Other]** Added an installer option
+## Features/Improvements :
+* **[Other]** Added an installer option (thanks to contributor parithon)
+* **[Units/Factions]** Added F-16C to USA 1990
+* **[Units/Factions]** Added MQ-9 Reaper as CAS unit for USA 2005
+* **[Units/Factions]** Added Mig-21, Mig-23, SA-342L to Syria 2011
## Fixed issues :
-* **[UI/UX]** Spelling issues
-* **[Campaign Generator]** Tarawa was placed on land in Syrian Civil War campaign
+* **[UI/UX]** Spelling issues (Thanks to Github contributor steveveepee)
+* **[Campaign Generator]** LHA was placed on land in Syrian Civil War campaign
* **[Campaign Generator]** Fixed inverted configuration for Syria full map
-* **[Units/Factions]** Minor changes to USA 1990
-* **[Units/Factions]** AH-64A now has default payloads. AH-64D has payloads for more mission types.
+* **[Campaign Generator]** Syria "Inherent Resolve" campaign, added Incirlik Air Base
+* **[Mission Generator]** AH-1W was not used by AI to generate CAS mission by default
+* **[Mission Generator]** Fixed F-16C targeting pod not being added to payload
+* **[Mission Generator]** AH-64A and AH-64D payloads fix.
# 2.1.0
diff --git a/game/db.py b/game/db.py
index 6cae3bae..a4cc3e4a 100644
--- a/game/db.py
+++ b/game/db.py
@@ -295,23 +295,6 @@ PRICES = {
Unarmed.Transport_M818: 3,
- AirDefence.AAA_Vulcan_M163: 5,
- AirDefence.SAM_Linebacker_M6: 10,
-
- AirDefence.AAA_ZU_23_Closed: 2,
- AirDefence.SPAAA_ZSU_23_4_Shilka: 4,
- AirDefence.SAM_SA_9_Strela_1_9P31: 8,
- AirDefence.SAM_SA_19_Tunguska_2S6: 15,
- AirDefence.SAM_SA_6_Kub_LN_2P25: 22,
- AirDefence.SAM_SA_8_Osa_9A33: 12,
- AirDefence.SAM_SA_3_S_125_LN_5P73: 20,
- AirDefence.SAM_SA_2_LN_SM_90: 15,
- AirDefence.SAM_SA_11_Buk_LN_9A310M1: 25,
- AirDefence.SAM_Hawk_PCP: 20,
- AirDefence.SAM_Patriot_LN_M901: 60,
- AirDefence.SAM_SA_10_S_300PS_LN_5P85C: 60,
- AirDefence.SAM_Chaparral_M48: 10,
-
# WW2
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G:24,
Armor.MT_Pz_Kpfw_IV_Ausf_H:16,
@@ -332,9 +315,6 @@ PRICES = {
Armor.LAC_M8_Greyhound: 8,
Armor.TD_M10_GMC: 14,
Armor.StuG_III_Ausf__G: 12,
- AirDefence.AAA_Bofors_40mm: 8,
- AirDefence.AAA_8_8cm_Flak_36: 8,
- AirDefence.AAA_8_8cm_Flak_18: 12,
Artillery.M12_GMC: 10,
Artillery.Sturmpanzer_IV_Brummbär: 10,
@@ -348,6 +328,79 @@ PRICES = {
Dry_cargo_ship_Ivanov: 10,
Tanker_Elnya_160: 10,
+ # Air Defence units
+ AirDefence.SAM_SA_19_Tunguska_2S6: 30,
+ AirDefence.SAM_SA_6_Kub_LN_2P25: 20,
+ AirDefence.SAM_SA_3_S_125_LN_5P73: 6,
+ AirDefence.SAM_SA_10_S_300PS_LN_5P85C: 22,
+ AirDefence.SAM_SA_10_S_300PS_LN_5P85D: 22,
+ AirDefence.SAM_SA_11_Buk_LN_9A310M1: 30,
+ AirDefence.SAM_SA_8_Osa_9A33: 28,
+ AirDefence.SAM_SA_15_Tor_9A331: 40,
+ AirDefence.SAM_SA_13_Strela_10M3_9A35M3: 24,
+ AirDefence.SAM_SA_9_Strela_1_9P31: 16,
+ AirDefence.SAM_SA_11_Buk_CC_9S470M1: 25,
+ AirDefence.SAM_SA_8_Osa_LD_9T217: 22,
+ AirDefence.SAM_Patriot_AMG_AN_MRC_137: 35,
+ AirDefence.SAM_Patriot_ECS_AN_MSQ_104: 30,
+ AirDefence.SPAAA_Gepard: 24,
+ AirDefence.SAM_Hawk_PCP: 14,
+ AirDefence.AAA_Vulcan_M163: 12,
+ AirDefence.SAM_Hawk_LN_M192: 8,
+ AirDefence.SAM_Chaparral_M48: 16,
+ AirDefence.SAM_Linebacker_M6: 18,
+ AirDefence.SAM_Patriot_LN_M901: 15,
+ AirDefence.SAM_Avenger_M1097: 20,
+ AirDefence.SAM_Patriot_EPP_III: 15,
+ AirDefence.SAM_Patriot_ICC: 18,
+ AirDefence.SAM_Roland_ADS: 12,
+ AirDefence.SAM_SA_10_S_300PS_CP_54K6: 18,
+ AirDefence.Stinger_MANPADS: 6,
+ AirDefence.SAM_Stinger_comm_dsr: 4,
+ AirDefence.SAM_Stinger_comm: 4,
+ AirDefence.SPAAA_ZSU_23_4_Shilka: 12,
+ AirDefence.AAA_ZU_23_Closed: 6,
+ AirDefence.AAA_ZU_23_Emplacement: 6,
+ AirDefence.AAA_ZU_23_on_Ural_375: 8,
+ AirDefence.AAA_ZU_23_Insurgent_Closed: 6,
+ AirDefence.AAA_ZU_23_Insurgent_on_Ural_375: 8,
+ AirDefence.AAA_ZU_23_Insurgent: 6,
+ AirDefence.SAM_SA_18_Igla_MANPADS: 10,
+ AirDefence.SAM_SA_18_Igla_comm: 8,
+ AirDefence.SAM_SA_18_Igla_S_MANPADS: 12,
+ AirDefence.SAM_SA_18_Igla_S_comm: 8,
+ AirDefence.EWR_1L13: 30,
+ AirDefence.SAM_SA_6_Kub_STR_9S91: 22,
+ AirDefence.SAM_SA_10_S_300PS_TR_30N6: 24,
+ AirDefence.SAM_SA_10_S_300PS_SR_5N66M: 30,
+ AirDefence.EWR_55G6: 30,
+ AirDefence.SAM_SA_10_S_300PS_SR_64H6E: 30,
+ AirDefence.SAM_SA_11_Buk_SR_9S18M1: 28,
+ AirDefence.CP_9S80M1_Sborka: 10,
+ AirDefence.SAM_Hawk_TR_AN_MPQ_46: 14,
+ AirDefence.SAM_Hawk_SR_AN_MPQ_50: 18,
+ AirDefence.SAM_Patriot_STR_AN_MPQ_53: 22,
+ AirDefence.SAM_Hawk_CWAR_AN_MPQ_55: 20,
+ AirDefence.SAM_SR_P_19: 14,
+ AirDefence.SAM_Roland_EWR: 16,
+ AirDefence.SAM_SA_3_S_125_TR_SNR: 14,
+ AirDefence.SAM_SA_2_LN_SM_90: 8,
+ AirDefence.SAM_SA_2_TR_SNR_75_Fan_Song: 12,
+ AirDefence.Rapier_FSA_Launcher: 6,
+ AirDefence.Rapier_FSA_Optical_Tracker: 12,
+ AirDefence.Rapier_FSA_Blindfire_Tracker: 16,
+ AirDefence.HQ_7_Self_Propelled_LN: 20,
+ AirDefence.HQ_7_Self_Propelled_STR: 24,
+ AirDefence.AAA_8_8cm_Flak_18: 6,
+ AirDefence.AAA_Flak_38: 6,
+ AirDefence.AAA_8_8cm_Flak_36: 8,
+ AirDefence.AAA_8_8cm_Flak_37: 10,
+ AirDefence.AAA_Flak_Vierling_38:6,
+ AirDefence.AAA_Kdo_G_40: 8,
+ AirDefence.Flak_Searchlight_37: 4,
+ AirDefence.Maschinensatz_33: 10,
+ AirDefence.AAA_8_8cm_Flak_41: 12,
+ AirDefence.AAA_Bofors_40mm: 8,
# FRENCH PACK MOD
frenchpack.AMX_10RCR: 10,
diff --git a/game/event/event.py b/game/event/event.py
index 6af3d6c9..e064ad94 100644
--- a/game/event/event.py
+++ b/game/event/event.py
@@ -178,9 +178,12 @@ class Event:
for i, ground_object in enumerate(cp.ground_objects):
if ground_object.dcs_identifier in ["AA", "CARRIER", "LHA"]:
for g in ground_object.groups:
+ if not hasattr(g, "units_losts"):
+ g.units_losts = []
for u in g.units:
if u.name == destroyed_ground_unit_name:
g.units.remove(u)
+ g.units_losts.append(u)
destroyed_units = destroyed_units + 1
info.text = u.type
ucount = sum([len(g.units) for g in ground_object.groups])
diff --git a/gen/aircraft.py b/gen/aircraft.py
index 70826ea5..118189ff 100644
--- a/gen/aircraft.py
+++ b/gen/aircraft.py
@@ -516,6 +516,10 @@ class AircraftConflictGenerator:
group.points[0].tasks.append(OptRestrictJettison(True))
for point in flight.points:
- group.add_waypoint(Point(point.x,point.y), point.alt)
+ group.add_waypoint(Point(point.x, point.y), point.alt)
+
+
+ def setup_radio_preset(self, flight, group):
+ pass
diff --git a/gen/flights/radio_generator.py b/gen/flights/radio_generator.py
new file mode 100644
index 00000000..1e647287
--- /dev/null
+++ b/gen/flights/radio_generator.py
@@ -0,0 +1,4 @@
+from dcs.unitgroup import FlyingGroup
+
+
+
diff --git a/gen/sam/sam_group_generator.py b/gen/sam/sam_group_generator.py
index c1c4c95d..7979cb16 100644
--- a/gen/sam/sam_group_generator.py
+++ b/gen/sam/sam_group_generator.py
@@ -65,6 +65,40 @@ SAM_MAP = {
AirDefence.HQ_7_Self_Propelled_LN: HQ7Generator
}
+SAM_PRICES = {
+ AirDefence.SAM_Hawk_PCP: 35,
+ AirDefence.AAA_ZU_23_Emplacement: 10,
+ AirDefence.AAA_ZU_23_Closed: 10,
+ AirDefence.AAA_ZU_23_on_Ural_375: 10,
+ AirDefence.AAA_ZU_23_Insurgent_on_Ural_375: 10,
+ AirDefence.AAA_ZU_23_Insurgent_Closed: 10,
+ AirDefence.AAA_ZU_23_Insurgent: 10,
+ AirDefence.SPAAA_ZSU_23_4_Shilka: 10,
+ AirDefence.AAA_Vulcan_M163: 15,
+ AirDefence.SAM_Linebacker_M6: 20,
+ AirDefence.Rapier_FSA_Launcher: 20,
+ AirDefence.SAM_Avenger_M1097: 22,
+ AirDefence.SPAAA_Gepard: 24,
+ AirDefence.SAM_Roland_ADS: 40,
+ AirDefence.SAM_Patriot_LN_M901: 85,
+ AirDefence.SAM_Patriot_EPP_III: 85,
+ AirDefence.SAM_Chaparral_M48: 25,
+ AirDefence.AAA_Bofors_40mm: 15,
+ AirDefence.AAA_8_8cm_Flak_36: 15,
+ AirDefence.SAM_SA_2_LN_SM_90: 30,
+ AirDefence.SAM_SA_3_S_125_LN_5P73: 35,
+ AirDefence.SAM_SA_6_Kub_LN_2P25: 45,
+ AirDefence.SAM_SA_8_Osa_9A33: 30,
+ AirDefence.SAM_SA_9_Strela_1_9P31: 25,
+ AirDefence.SAM_SA_10_S_300PS_LN_5P85C: 80,
+ AirDefence.SAM_SA_10_S_300PS_CP_54K6: 80,
+ AirDefence.SAM_SA_11_Buk_LN_9A310M1: 60,
+ AirDefence.SAM_SA_13_Strela_10M3_9A35M3: 30,
+ AirDefence.SAM_SA_15_Tor_9A331: 40,
+ AirDefence.SAM_SA_19_Tunguska_2S6: 35,
+ AirDefence.HQ_7_Self_Propelled_LN: 35
+}
+
def generate_anti_air_group(game, parent_cp, ground_object, faction:str):
"""
This generate a SAM group
diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py
index 2e763071..5311fac4 100644
--- a/qt_ui/widgets/map/QLiberationMap.py
+++ b/qt_ui/widgets/map/QLiberationMap.py
@@ -143,7 +143,7 @@ class QLiberationMap(QGraphicsView):
go_pos = self._transform_point(ground_object.position)
if not ground_object.airbase_group:
buildings = self.game.theater.find_ground_objects_by_obj_name(ground_object.obj_name)
- scene.addItem(QMapGroundObject(self, go_pos[0], go_pos[1], 12, 12, cp, ground_object, buildings))
+ scene.addItem(QMapGroundObject(self, go_pos[0], go_pos[1], 12, 12, cp, ground_object, self.game, buildings))
if ground_object.category == "aa" and self.get_display_rule("sam"):
max_range = 0
diff --git a/qt_ui/widgets/map/QMapGroundObject.py b/qt_ui/widgets/map/QMapGroundObject.py
index dbbf4d79..d2950f3a 100644
--- a/qt_ui/widgets/map/QMapGroundObject.py
+++ b/qt_ui/widgets/map/QMapGroundObject.py
@@ -3,17 +3,19 @@ from PySide2.QtGui import QPainter
from PySide2.QtWidgets import QGraphicsRectItem, QGraphicsItem, QGraphicsSceneHoverEvent, QGraphicsSceneMouseEvent
import qt_ui.uiconstants as CONST
-from game import db
+from game import db, Game
+from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu
from theater import TheaterGroundObject, ControlPoint
class QMapGroundObject(QGraphicsRectItem):
- def __init__(self, parent, x: float, y: float, w: float, h: float, cp: ControlPoint, model: TheaterGroundObject, buildings=[]):
+ def __init__(self, parent, x: float, y: float, w: float, h: float, cp: ControlPoint, model: TheaterGroundObject, game:Game, buildings=[]):
super(QMapGroundObject, self).__init__(x, y, w, h)
self.model = model
self.cp = cp
self.parent = parent
+ self.game = game
self.setAcceptHoverEvents(True)
self.setZValue(2)
self.buildings = buildings
@@ -39,6 +41,8 @@ class QMapGroundObject(QGraphicsRectItem):
tooltip = tooltip + str(building.dcs_identifier) + "\n"
self.setToolTip(tooltip[:-1])
+ def mousePressEvent(self, event:QGraphicsSceneMouseEvent):
+ self.openEditionMenu()
def paint(self, painter, option, widget=None):
#super(QMapControlPoint, self).paint(painter, option, widget)
@@ -72,3 +76,7 @@ class QMapGroundObject(QGraphicsRectItem):
def hoverLeaveEvent(self, event: QGraphicsSceneHoverEvent):
self.update()
+ def openEditionMenu(self):
+ self.editionMenu = QGroundObjectMenu(self.window(), self.model, self.cp, self.game)
+ self.editionMenu.show()
+
diff --git a/qt_ui/windows/groundobject/QGroundObjectMenu.py b/qt_ui/windows/groundobject/QGroundObjectMenu.py
new file mode 100644
index 00000000..ad8d0ff7
--- /dev/null
+++ b/qt_ui/windows/groundobject/QGroundObjectMenu.py
@@ -0,0 +1,98 @@
+import logging
+
+from PySide2.QtGui import QCloseEvent
+from PySide2.QtWidgets import QHBoxLayout, QWidget, QDialog, QGridLayout, QLabel, QGroupBox, QVBoxLayout, QPushButton
+from dcs import Point
+
+from game import Game
+from game.db import PRICES, unit_type_of
+from qt_ui.widgets.QBudgetBox import QBudgetBox
+from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
+from theater import ControlPoint, TheaterGroundObject
+
+
+class QGroundObjectMenu(QDialog):
+
+ def __init__(self, parent, ground_object: TheaterGroundObject, cp: ControlPoint, game: Game):
+ super(QGroundObjectMenu, self).__init__(parent)
+ self.setMinimumWidth(350)
+ self.ground_object = ground_object
+ self.cp = cp
+ self.game = game
+ self.setWindowTitle("Location " + self.ground_object.obj_name)
+ self.intelBox = QGroupBox("Units :")
+ self.intelLayout = QGridLayout()
+ self.init_ui()
+
+ def init_ui(self):
+
+ self.mainLayout = QVBoxLayout()
+ self.budget = QBudgetBox(self.game)
+ self.budget.setGame(self.game)
+
+ self.doLayout()
+
+ self.mainLayout.addWidget(self.intelBox)
+ self.setLayout(self.mainLayout)
+
+ def doLayout(self):
+ self.intelBox = QGroupBox("Units :")
+ self.intelLayout = QGridLayout()
+ i = 0
+ for g in self.ground_object.groups:
+ if not hasattr(g, "units_losts"):
+ g.units_losts = []
+ for u in g.units:
+ self.intelLayout.addWidget(QLabel("Unit #" + str(u.id) + " - " + str(u.type) + ""), i, 0)
+ i = i + 1
+
+ for u in g.units_losts:
+
+ utype = unit_type_of(u)
+ if utype in PRICES:
+ price = PRICES[utype]
+ else:
+ price = 6
+
+ self.intelLayout.addWidget(QLabel("Unit #" + str(u.id) + " - " + str(u.type) + " [DEAD]"), i, 0)
+ repair = QPushButton("Repair [" + str(price) + "M]")
+ repair.setProperty("style", "btn-primary")
+ repair.clicked.connect(lambda u=u, g=g, p=price: self.repair_unit(g, u, p))
+ self.intelLayout.addWidget(repair, i, 1)
+ i = i + 1
+ self.intelBox.setLayout(self.intelLayout)
+
+ def do_refresh_layout(self):
+ try:
+ for i in range(self.mainLayout.count()):
+ self.mainLayout.removeItem(self.mainLayout.itemAt(i));
+ self.doLayout()
+ self.mainLayout.addWidget(self.intelBox)
+ except Exception as e:
+ print(e)
+
+ def repair_unit(self, group, unit, price):
+
+ print(group)
+ print(unit.type)
+ [print(u.id) for u in group.units]
+
+ if self.game.budget > price:
+ self.game.budget -= price
+ group.units_losts = [u for u in group.units_losts if u.id != unit.id]
+ group.units.append(unit)
+ GameUpdateSignal.get_instance().updateGame(self.game)
+
+ # Remove destroyed units in the vicinity
+ destroyed_units = self.game.get_destroyed_units()
+ for d in destroyed_units:
+ p = Point(d["x"], d["z"])
+ if p.distance_to_point(unit.position) < 15:
+ destroyed_units.remove(d)
+ logging.info("Removed destroyed units " + str(d))
+ logging.info("Repaired unit : " + str(unit.id) + " " + str(unit.type))
+
+ self.do_refresh_layout()
+
+ def closeEvent(self, closeEvent: QCloseEvent):
+ GameUpdateSignal.get_instance().updateGame(self.game)
diff --git a/qt_ui/windows/groundobject/QGroundObjectReplacementMenu.py b/qt_ui/windows/groundobject/QGroundObjectReplacementMenu.py
new file mode 100644
index 00000000..e69de29b
diff --git a/resources/tools/mkrelease.py b/resources/tools/mkrelease.py
index f7dea204..a76029b9 100644
--- a/resources/tools/mkrelease.py
+++ b/resources/tools/mkrelease.py
@@ -45,7 +45,7 @@ def _mk_archieve():
shutil.rmtree("./dist")
except FileNotFoundError:
pass
- os.system("pyinstaller.exe pyinstaller.spec")
+ os.system("pyinstaller.exe --clean pyinstaller.spec")
#archieve = ZipFile(path, "w")
#archieve.writestr("dcs_liberation.bat", "cd dist\\dcs_liberation\r\nliberation_main \"%UserProfile%\\Saved Games\" \"{}\"".format(VERSION))
#_zip_dir(archieve, "./dist/dcs_liberation")