diff --git a/changelog.md b/changelog.md index 4ee965fd..cbefe9a3 100644 --- a/changelog.md +++ b/changelog.md @@ -2,7 +2,7 @@ ## Features/Improvements : * **[UI/UX]** New dark UI Theme and default theme improvement by Deus -* **[UI/UX]** New satellite map backgrounds +* **[UI/UX]** New "satellite" map backgrounds * **[UX]** Base menu is opened with a single mouse click * **[Units/Factions/Mods]** Added Community A-4E-C support for faction Bluefor Cold War * **[Units/Factions/Mods]** Added MB-339PAN support for faction Bluefor Cold War @@ -13,19 +13,21 @@ * **[Mission Generator]** Artillery units will start firing mission after a random delay. It should reduces lag spikes induced by artillery strikes by spreading them out. * **[Mission Generator]** The briefing will now contain the carrier ATC frequency * **[Mission Generator]** The briefing contains a small section about the war on the ground. -* **[Mission Generator]** DCS Liberation picture added to the in-game briefing +* **[Mission Generator]** Previously destroyed units are visible in the mission. (And added a performance settings to disable this behaviour) ## Fixed issues : * **[Mission Generator]** Carrier will sail into the wind, not in the same direction * **[Mission Generator]** Carrier cold start was not working (flight was starting warm even when cold was selected) * **[Mission Generator]** Carrier group ships are more spread out +* **[Mission Generator]** Fixed radio frequency for german WW2 warbirds +* **[Mission Generator]** BAse defense units were not controllable with Combined Arms * **[Units/Factions]** Remove JF-17 from USA 2005 faction * **[Units/Factions]** Removed Oliver Hazard Perry from cold war factions (too powerful sam system) * **[Bug]** On the persian gulf full map campaign, the two carriers were sharing the same id, this was causing a lot of bugs -* **[Performance]** Tuned the culling setting so that you cannot run into situation where no flights are generated -* **[Mission Generator]** Fixed radio frequency for german WW2 warbirds +* **[Performance]** Tuned the culling setting so that you cannot run into situation where no AI flights are generated +* **[Other]** Application doesn't gracefully exit. * **[Other]** Other minor fixes # 2.0 RC 9 diff --git a/game/db.py b/game/db.py index c85d629c..85c30987 100644 --- a/game/db.py +++ b/game/db.py @@ -1048,6 +1048,8 @@ def find_infantry(country_name: str) -> typing.List[UnitType]: def unit_type_name(unit_type) -> str: return unit_type.id and unit_type.id or unit_type.name +def unit_type_name_2(unit_type) -> str: + return unit_type.name and unit_type.name or unit_type.id def unit_type_from_name(name: str) -> UnitType: if name in vehicle_map: diff --git a/game/event/event.py b/game/event/event.py index 69768d27..66770881 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -253,6 +253,12 @@ class Event: except Exception as e: print(e) + # Destroyed units carcass + # ------------------------- + + for destroyed_unit in debriefing.destroyed_units: + self.game.add_destroyed_units(destroyed_unit) + # ----------------------------------- # Compute damage to bases for cp in self.game.theater.player_points(): diff --git a/game/operation/operation.py b/game/operation/operation.py index a4079373..48fe8dad 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -125,7 +125,11 @@ class Operation: # Generate destroyed units for d in self.game.get_destroyed_units(): - utype = db.unit_type_from_name(d["type"]) + try: + utype = db.unit_type_from_name(d["type"]) + except KeyError: + continue + pos = Point(d["x"], d["z"]) if utype is not None and not self.game.position_culled(pos) and self.game.settings.perf_destroyed_units: self.current_mission.static_group( @@ -138,6 +142,7 @@ class Operation: dead=True, ) + # Air Support (Tanker & Awacs) self.airsupportgen.generate(self.is_awacs_enabled) diff --git a/gen/armor.py b/gen/armor.py index f1ba8921..6cb78a64 100644 --- a/gen/armor.py +++ b/gen/armor.py @@ -1,5 +1,5 @@ -from dcs.action import AITaskPush -from dcs.condition import TimeAfter +from dcs.action import AITaskPush, AITaskSet +from dcs.condition import TimeAfter, UnitDamaged, Or from dcs.task import * from dcs.triggers import TriggerOnce, Event @@ -155,14 +155,53 @@ class GroundConflictGenerator: target = self.get_artillery_target_in_range(dcs_group, group, enemy_groups) if target is not None: + if stance != CombatStance.RETREAT: + hold_task = Hold() + hold_task.number = 1 + dcs_group.add_trigger_action(hold_task) + # Artillery strike random start artillery_trigger = TriggerOnce(Event.NoEvent, "ArtilleryFireTask #" + str(dcs_group.id)) artillery_trigger.add_condition(TimeAfter(seconds=random.randint(1, 45)* 60)) - dcs_group.add_trigger_action(FireAtPoint(target, len(group.units) * 10, 100)) + + fire_task = FireAtPoint(target, len(group.units) * 10, 100) + if stance != CombatStance.RETREAT: + fire_task.number = 2 + else: + fire_task.number = 1 + dcs_group.add_trigger_action(fire_task) artillery_trigger.add_action(AITaskPush(dcs_group.id, len(dcs_group.tasks))) self.mission.triggerrules.triggers.append(artillery_trigger) + # Artillery will fall back when under attack + if stance != CombatStance.RETREAT: + + # Hold position + dcs_group.points[0].tasks.append(Hold()) + retreat = self.find_retreat_point(dcs_group, forward_heading) + dcs_group.add_waypoint(dcs_group.position.point_from_heading(forward_heading, 1), PointAction.OffRoad) + dcs_group.points[1].tasks.append(Hold()) + dcs_group.add_waypoint(retreat, PointAction.OffRoad) + + artillery_fallback = TriggerOnce(Event.NoEvent, "ArtilleryRetreat #" + str(dcs_group.id)) + for i, u in enumerate(dcs_group.units): + artillery_fallback.add_condition(UnitDamaged(u.id)) + if i < len(dcs_group.units) - 1: + artillery_fallback.add_condition(Or()) + + + retreat_task = GoToWaypoint(toIndex=3) + retreat_task.number = 3 + dcs_group.add_trigger_action(retreat_task) + + artillery_fallback.add_action(AITaskSet(dcs_group.id, len(dcs_group.tasks))) + self.mission.triggerrules.triggers.append(artillery_fallback) + + for u in dcs_group.units: + u.initial = True + u.heading = forward_heading + random.randint(-5,5) + elif group.role in [CombatGroupRole.TANK, CombatGroupRole.IFV]: if stance == CombatStance.AGGRESIVE: # Attack nearest enemy if any diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index bef13954..e4236732 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -56,7 +56,7 @@ class FlightPlanner: self.compute_strike_targets() # The priority is to assign air-superiority fighter or interceptor to interception roles, so they can scramble if there is an attacker - #self.commision_interceptors() + self.commision_interceptors() # Then some CAP patrol for the next 2 hours self.commision_cap() @@ -106,6 +106,7 @@ class FlightPlanner: break inventory[unit] = inventory[unit] - 2 flight = Flight(unit, 2, self.from_cp, FlightType.INTERCEPTION) + flight.scheduled_in = 1 flight.points = [] self.interceptor_flights.append(flight) diff --git a/qt_ui/main.py b/qt_ui/main.py index 62254fc0..e6760d51 100644 --- a/qt_ui/main.py +++ b/qt_ui/main.py @@ -83,6 +83,5 @@ if __name__ == "__main__": logging.info("Attempt to restore original mission scripting file") liberation_install.restore_original_mission_scripting() logging.info("QT process exited with code : " + str(qt_execution_code)) - sys.exit(0) diff --git a/qt_ui/widgets/map/QMapControlPoint.py b/qt_ui/widgets/map/QMapControlPoint.py index aed62bb4..939a5677 100644 --- a/qt_ui/widgets/map/QMapControlPoint.py +++ b/qt_ui/widgets/map/QMapControlPoint.py @@ -5,7 +5,6 @@ from PySide2.QtWidgets import QGraphicsRectItem, QGraphicsSceneHoverEvent, QGrap import qt_ui.uiconstants as CONST from game import Game -from qt_ui.windows.basemenu.QBaseMenu import QBaseMenu from qt_ui.windows.basemenu.QBaseMenu2 import QBaseMenu2 from theater import ControlPoint, db diff --git a/qt_ui/windows/QDebriefingWindow.py b/qt_ui/windows/QDebriefingWindow.py index c5e45d0d..752d19d5 100644 --- a/qt_ui/windows/QDebriefingWindow.py +++ b/qt_ui/windows/QDebriefingWindow.py @@ -1,4 +1,4 @@ -from PySide2.QtGui import QIcon +from PySide2.QtGui import QIcon, QPixmap from PySide2.QtWidgets import QLabel, QDialog, QVBoxLayout, QGroupBox, QGridLayout, QPushButton from game.game import Event, db, Game @@ -25,14 +25,21 @@ class QDebriefingWindow(QDialog): self.layout = QVBoxLayout() - # Result + header = QLabel(self) + header.setGeometry(0, 0, 655, 106) + pixmap = QPixmap("./resources/ui/debriefing.png") + header.setPixmap(pixmap) + self.layout.addWidget(header) + self.layout.addStretch() - if self.gameEvent.is_successfull(self.debriefing): - title = QLabel("Operation Succesfull !") - title.setProperty("style", "title-success") - else: - title = QLabel("Operation failed !") - title.setProperty("style", "title-danger") + # Result + #if self.gameEvent.is_successfull(self.debriefing): + # title = QLabel("Operation end !") + # title.setProperty("style", "title-success") + #else: + # title = QLabel("Operation end !") + # title.setProperty("style", "title-danger") + title = QLabel("Casualty report") self.layout.addWidget(title) # Player lost units diff --git a/qt_ui/windows/QLiberationWindow.py b/qt_ui/windows/QLiberationWindow.py index 8f273505..50bf8d55 100644 --- a/qt_ui/windows/QLiberationWindow.py +++ b/qt_ui/windows/QLiberationWindow.py @@ -5,7 +5,7 @@ import webbrowser from PySide2.QtCore import Qt from PySide2.QtGui import QIcon from PySide2.QtWidgets import QWidget, QVBoxLayout, QMainWindow, QAction, QMessageBox, QDesktopWidget, \ - QSplitter + QSplitter, QFileDialog import qt_ui.uiconstants as CONST from game import Game @@ -73,10 +73,18 @@ class QLiberationWindow(QMainWindow): self.newGameAction.setIcon(QIcon(CONST.ICONS["New"])) self.newGameAction.triggered.connect(self.newGame) + self.openAction = QAction("Open", self) + self.openAction.setIcon(QIcon(CONST.ICONS["Open"])) + self.openAction.triggered.connect(self.openFile) + self.saveGameAction = QAction("Save", self) self.saveGameAction.setIcon(QIcon(CONST.ICONS["Save"])) self.saveGameAction.triggered.connect(self.saveGame) + self.saveAsAction = QAction("Save As", self) + self.saveAsAction.setIcon(QIcon(CONST.ICONS["Save"])) + self.saveAsAction.triggered.connect(self.saveGameAs) + self.showAboutDialogAction = QAction("About DCS Liberation", self) self.showAboutDialogAction.setIcon(QIcon.fromTheme("help-about")) self.showAboutDialogAction.triggered.connect(self.showAboutDialog) @@ -88,7 +96,7 @@ class QLiberationWindow(QMainWindow): def initToolbar(self): self.tool_bar = self.addToolBar("File") self.tool_bar.addAction(self.newGameAction) - #self.tool_bar.addAction(QIcon(CONST.ICONS["Open"]), "Open") + self.tool_bar.addAction(self.openAction) self.tool_bar.addAction(self.saveGameAction) def initMenuBar(self): @@ -96,7 +104,7 @@ class QLiberationWindow(QMainWindow): file_menu = self.menu.addMenu("File") file_menu.addAction(self.newGameAction) - #file_menu.addAction(QIcon(CONST.ICONS["Open"]), "Open") # TODO : implement + file_menu.addAction(QIcon(CONST.ICONS["Open"]), "Open") # TODO : implement file_menu.addAction(self.saveGameAction) file_menu.addSeparator() file_menu.addAction(self.showLiberationPrefDialogAction) @@ -163,11 +171,17 @@ class QLiberationWindow(QMainWindow): wizard.show() wizard.accepted.connect(lambda: self.onGameGenerated(wizard.generatedGame)) + def openFile(self): + file = str(QFileDialog.getOpenFileName(self, "Select game file to open")) + def saveGame(self): logging.info("Saving game") persistency.save_game(self.game) GameUpdateSignal.get_instance().updateGame(self.game) + def saveGameAs(self): + file = str(QFileDialog.getSaveFileName(self, "Save As", dir=persistency._dcs_saved_game_folder)) + def onGameGenerated(self, game: Game): logging.info("On Game generated") self.game = game diff --git a/qt_ui/windows/QWaitingForMissionResultWindow.py b/qt_ui/windows/QWaitingForMissionResultWindow.py index bd880262..251a142f 100644 --- a/qt_ui/windows/QWaitingForMissionResultWindow.py +++ b/qt_ui/windows/QWaitingForMissionResultWindow.py @@ -1,15 +1,18 @@ +import json import os from PySide2 import QtCore -from PySide2.QtCore import QObject, Signal -from PySide2.QtGui import QMovie, QIcon -from PySide2.QtWidgets import QLabel, QDialog, QVBoxLayout, QGroupBox, QGridLayout, QPushButton +from PySide2.QtCore import QObject, Signal, Qt +from PySide2.QtGui import QMovie, QIcon, QPixmap +from PySide2.QtWidgets import QLabel, QDialog, QGroupBox, QGridLayout, QPushButton, QFileDialog, QMessageBox, QTextEdit, \ + QHBoxLayout from game.game import Event, Game from qt_ui.windows.GameUpdateSignal import GameUpdateSignal from userdata.debriefing import wait_for_debriefing, Debriefing from userdata.persistency import base_path + class DebriefingFileWrittenSignal(QObject): instance = None @@ -38,40 +41,63 @@ class QWaitingForMissionResultWindow(QDialog): self.setWindowTitle("Waiting for mission completion.") self.setWindowFlag(QtCore.Qt.WindowCloseButtonHint, False) self.setWindowIcon(QIcon("./resources/icon.png")) + self.setMinimumHeight(570) self.initUi() DebriefingFileWrittenSignal.get_instance().debriefingReceived.connect(self.updateLayout) - wait_for_debriefing(lambda debriefing: self.on_debriefing_udpate(debriefing), self.game) + self.wait_thread = wait_for_debriefing(lambda debriefing: self.on_debriefing_udpate(debriefing), self.game) def initUi(self): self.layout = QGridLayout() + + header = QLabel(self) + header.setGeometry(0, 0, 655, 106) + pixmap = QPixmap("./resources/ui/conflict.png") + header.setPixmap(pixmap) + self.layout.addWidget(header, 0, 0) + self.gridLayout = QGridLayout() - self.gridLayout.addWidget(QLabel("You are clear for takeoff"), 1, 0) - self.gridLayout.addWidget(QLabel(""), 2, 0) - self.gridLayout.addWidget(QLabel("
Then once the mission is loaded in ME, in menu \"Flight\",\n" + \ + "click on FLY Mission to launch.
\n" + \ + "" + \ + "Click on File/Save. Then exit the mission editor, and go to Multiplayer.
" + \ + "Then host a server with the mission, and tell your friends to join !
" + \ + "(The step in the mission editor is important, and fix a game breaking bug.)" + \ + "Once you have played the mission, click on the \"Accept Results\" button.
" + + self.instructions_text = QTextEdit(TEXT) + self.instructions_text.setReadOnly(True) + self.gridLayout.addWidget(self.instructions_text, 1, 0) progress = QLabel("") progress.setAlignment(QtCore.Qt.AlignCenter) - progressBar = QMovie("./resources/ui/loader.gif") - progress.setMovie(progressBar) - self.gridLayout.addWidget(progress, 15, 0) - self.gridLayout.addWidget(QLabel(""), 16, 0) - self.gridLayout.addWidget(QLabel("Once you have played the mission, this window will dissapear."), 17, 0) - self.gridLayout.addWidget(QLabel("You will have to click on \"Accept Results\" to proceed"), 18, 0) + progress_bar = QMovie("./resources/ui/loader.gif") + progress.setMovie(progress_bar) - progressBar.start() - self.layout.addLayout(self.gridLayout,0,0) + self.actions = QGroupBox("Actions :") + self.actions_layout = QHBoxLayout() + self.actions.setLayout(self.actions_layout) + + manually_submit = QPushButton("Manually Submit [Advanced users]") + manually_submit.clicked.connect(self.submit_manually) + self.actions_layout.addWidget(manually_submit) + cancel = QPushButton("Abort mission") + cancel.clicked.connect(self.close) + self.actions_layout.addWidget(cancel) + self.gridLayout.addWidget(self.actions, 2, 0) + + progress_bar.start() + self.layout.addLayout(self.gridLayout, 1, 0) self.setLayout(self.layout) def updateLayout(self, debriefing): @@ -100,6 +126,7 @@ class QWaitingForMissionResultWindow(QDialog): if not debriefing.mission_ended: self.gridLayout.addWidget(QLabel("Mission is being played"), 1, 0) + self.gridLayout.addWidget(self.actions, 2, 0) else: #self.gridLayout.addWidget(QLabel("Mission is over !"), 1, 0) proceed = QPushButton("Accept results") @@ -111,7 +138,9 @@ class QWaitingForMissionResultWindow(QDialog): print("On Debriefing update") print(debriefing) DebriefingFileWrittenSignal.get_instance().sendDebriefing(debriefing) - wait_for_debriefing(lambda debriefing: self.on_debriefing_udpate(debriefing), self.game) + + if not debriefing.mission_ended: + self.wait_thread = wait_for_debriefing(lambda debriefing: self.on_debriefing_udpate(debriefing), self.game) def process_debriefing(self, debriefing: Debriefing): self.game.finish_event(event=self.gameEvent, debriefing=debriefing) @@ -122,3 +151,28 @@ class QWaitingForMissionResultWindow(QDialog): def debriefing_directory_location(self) -> str: return os.path.join(base_path(), "liberation_debriefings") + + def closeEvent(self, evt): + super(QWaitingForMissionResultWindow, self).closeEvent(evt) + if self.wait_thread is not None: + self.wait_thread.stop() + + def submit_manually(self): + file = str(QFileDialog.getOpenFileName(self, "Select game file to open", filter="json(*.json)")) + print(file) + try: + with open("state.json", "r") as json_file: + json_data = json.load(json_file) + json_data["mission_ended"] = True + debriefing = Debriefing(json_data, self.game) + self.on_debriefing_udpate(debriefing) + except: + msg = QMessageBox() + msg.setIcon(QMessageBox.Information) + msg.setText("Invalid file : " + file) + msg.setWindowTitle("Invalid file.") + msg.setStandardButtons(QMessageBox.Ok) + msg.setWindowFlags(Qt.WindowStaysOnTopHint) + msg.exec_() + return + diff --git a/qt_ui/windows/basemenu/QBaseMenu.py b/qt_ui/windows/basemenu/QBaseMenu.py deleted file mode 100644 index b033a2d0..00000000 --- a/qt_ui/windows/basemenu/QBaseMenu.py +++ /dev/null @@ -1,246 +0,0 @@ -import traceback - -from PySide2.QtCore import Qt -from PySide2.QtGui import QCloseEvent -from PySide2.QtWidgets import QHBoxLayout, QLabel, QWidget, QDialog, QVBoxLayout, QGridLayout, QPushButton, \ - QGroupBox, QSizePolicy, QSpacerItem -from dcs.unittype import UnitType - -from game.event import UnitsDeliveryEvent, ControlPointType -from qt_ui.widgets.QBudgetBox import QBudgetBox -from qt_ui.widgets.base.QAirportInformation import QAirportInformation -from qt_ui.windows.basemenu.base_defenses.QBaseInformation import QBaseInformation -from qt_ui.windows.mission.QPlannedFlightsView import QPlannedFlightsView -from qt_ui.windows.GameUpdateSignal import GameUpdateSignal -from theater import ControlPoint, CAP, Embarking, CAS, PinpointStrike, db -from game import Game - - -class QBaseMenu(QDialog): - - def __init__(self, parent, controlPoint: ControlPoint, game: Game): - super(QBaseMenu, self).__init__(parent) - - self.cp = controlPoint - self.game = game - self.is_carrier = self.cp.cptype in [ControlPointType.AIRCRAFT_CARRIER_GROUP, ControlPointType.LHA_GROUP] - - try: - self.airport = game.theater.terrain.airport_by_id(self.cp.id) - except: - self.airport = None - - if self.cp.captured: - self.deliveryEvent = None - for event in self.game.events: - print(event.__class__) - print(UnitsDeliveryEvent.__class__) - if event.__class__ == UnitsDeliveryEvent and event.from_cp == self.cp: - self.deliveryEvent = event - break - if not self.deliveryEvent: - print("Rebuild event") - self.deliveryEvent = self.game.units_delivery_event(self.cp) - - self.setWindowFlags(Qt.WindowStaysOnTopHint) - self.setMinimumSize(300, 200) - self.setModal(True) - self.initUi() - - def initUi(self): - - self.setWindowTitle(self.cp.name) - - self.topLayoutWidget = QWidget() - self.topLayout = QHBoxLayout() - - title = QLabel("" + self.cp.name + "") - title.setAlignment(Qt.AlignLeft | Qt.AlignTop) - title.setProperty("style", "base-title") - unitsPower = QLabel("{} / {} / Runway : {}".format(self.cp.base.total_planes, self.cp.base.total_armor, - "Available" if self.cp.has_runway() else "Unavailable")) - - self.topLayout.addWidget(title) - self.topLayout.addWidget(unitsPower) - self.topLayout.setAlignment(Qt.AlignTop) - self.topLayoutWidget.setProperty("style", "baseMenuHeader") - self.topLayoutWidget.setLayout(self.topLayout) - - if self.cp.captured: - units = { - CAP: db.find_unittype(CAP, self.game.player_name), - Embarking: db.find_unittype(Embarking, self.game.player_name), - CAS: db.find_unittype(CAS, self.game.player_name), - PinpointStrike: db.find_unittype(PinpointStrike, self.game.player_name), - } - else: - units = { - CAP: db.find_unittype(CAP, self.game.enemy_name), - Embarking: db.find_unittype(Embarking, self.game.enemy_name), - CAS: db.find_unittype(CAS, self.game.enemy_name), - PinpointStrike: db.find_unittype(PinpointStrike, self.game.enemy_name), - } - - self.mainLayout = QGridLayout() - self.leftLayout = QVBoxLayout() - self.unitLayout = QVBoxLayout() - self.bought_amount_labels = {} - self.existing_units_labels = {} - - row = 0 - - if self.cp.captured: - - self.recruitment = QGroupBox("Recruitment") - self.recruitmentLayout = QVBoxLayout() - self.budget = QBudgetBox() - self.budget.setBudget(self.game.budget, self.game.budget_reward_amount) - self.recruitmentLayout.addWidget(self.budget) - - for task_type in units.keys(): - - if task_type == PinpointStrike and self.is_carrier: - continue - - units_column = list(set(units[task_type])) - if len(units_column) == 0: continue - units_column.sort(key=lambda x: db.PRICES[x]) - - task_box = QGroupBox("{}".format(db.task_name(task_type))) - task_box_layout = QGridLayout() - task_box.setLayout(task_box_layout) - row = 0 - for unit_type in units_column: - if self.is_carrier and not unit_type in db.CARRIER_CAPABLE: - continue - row = self.add_purchase_row(unit_type, task_box_layout, row) - - stretch = QVBoxLayout() - stretch.addStretch() - task_box_layout.addLayout(stretch, row, 0) - - self.recruitmentLayout.addWidget(task_box) - self.recruitmentLayout.addStretch() - - self.recruitment.setLayout(self.recruitmentLayout) - self.leftLayout.addWidget(self.recruitment) - self.leftLayout.addStretch() - else: - intel = QGroupBox("Intel") - intelLayout = QVBoxLayout() - - for task_type in units.keys(): - units_column = list(set(units[task_type])) - - if sum([self.cp.base.total_units_of_type(u) for u in units_column]) > 0: - - group = QGroupBox(db.task_name(task_type)) - groupLayout = QGridLayout() - group.setLayout(groupLayout) - - row = 0 - for unit_type in units_column: - existing_units = self.cp.base.total_units_of_type(unit_type) - if existing_units == 0: - continue - groupLayout.addWidget(QLabel("" + db.unit_type_name(unit_type) + ""), row, 0) - groupLayout.addWidget(QLabel(str(existing_units)), row, 1) - row += 1 - - intelLayout.addWidget(group) - intelLayout.addStretch() - intel.setLayout(intelLayout) - self.leftLayout.addWidget(intel) - - self.mainLayout.addWidget(self.topLayoutWidget, 0, 0) - self.mainLayout.addLayout(self.leftLayout, 1, 0) - self.mainLayout.addWidget(QBaseInformation(self.cp, self.airport), 1, 1) - - self.rightLayout = QVBoxLayout() - try: - self.rightLayout.addWidget(QPlannedFlightsView(self.game.planners[self.cp.id])) - except Exception: - traceback.print_exc() - - if self.airport: - self.rightLayout.addWidget(QAirportInformation(self.cp, self.airport)) - self.mainLayout.addLayout(self.rightLayout, 1, 2) - - self.setLayout(self.mainLayout) - - def add_purchase_row(self, unit_type, layout, row): - - existing_units = self.cp.base.total_units_of_type(unit_type) - scheduled_units = self.deliveryEvent.units.get(unit_type, 0) - - unitName = QLabel("" + db.unit_type_name(unit_type) + "") - unitName.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) - - existing_units = QLabel(str(existing_units)) - existing_units.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) - - amount_bought = QLabel("[{}]".format(str(scheduled_units))) - amount_bought.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) - - self.existing_units_labels[unit_type] = existing_units - self.bought_amount_labels[unit_type] = amount_bought - - price = QLabel("{}m".format(db.PRICES[unit_type])) - price.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) - - buy = QPushButton("+") - buy.setProperty("style", "btn-success") - buy.setMinimumSize(24, 24) - buy.clicked.connect(lambda: self.buy(unit_type)) - buy.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) - - sell = QPushButton("-") - sell.setProperty("style", "btn-danger") - sell.setMinimumSize(24, 24) - sell.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) - sell.clicked.connect(lambda: self.sell(unit_type)) - - layout.addWidget(unitName, row, 0) - layout.addItem(QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Minimum), row, 1) - layout.addWidget(existing_units, row, 2) - layout.addWidget(amount_bought, row, 3) - layout.addWidget(price, row, 4) - layout.addItem(QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Minimum), row, 5) - layout.addWidget(buy, row, 6) - layout.addWidget(sell, row, 7) - - return row + 1 - - def _update_count_label(self, unit_type: UnitType): - self.bought_amount_labels[unit_type].setText("[{}]".format( - unit_type in self.deliveryEvent.units and "{}".format(self.deliveryEvent.units[unit_type]) or "0" - )) - - self.existing_units_labels[unit_type].setText("{}".format( - self.cp.base.total_units_of_type(unit_type) - )) - - def buy(self, unit_type): - price = db.PRICES[unit_type] - if self.game.budget >= price: - self.deliveryEvent.deliver({unit_type: 1}) - self.game.budget -= price - self.budget.setBudget(self.game.budget, self.game.budget_reward_amount) - self._update_count_label(unit_type) - - def sell(self, unit_type): - if self.deliveryEvent.units.get(unit_type, 0) > 0: - price = db.PRICES[unit_type] - self.game.budget += price - self.deliveryEvent.units[unit_type] = self.deliveryEvent.units[unit_type] - 1 - if self.deliveryEvent.units[unit_type] == 0: - del self.deliveryEvent.units[unit_type] - elif self.cp.base.total_units_of_type(unit_type) > 0: - price = db.PRICES[unit_type] - self.game.budget += price - self.cp.base.commit_losses({unit_type: 1}) - - self._update_count_label(unit_type) - - def closeEvent(self, closeEvent:QCloseEvent): - GameUpdateSignal.get_instance().updateGame(self.game) \ No newline at end of file diff --git a/qt_ui/windows/basemenu/QBaseMenu2.py b/qt_ui/windows/basemenu/QBaseMenu2.py index 1c4ffcd6..065b3631 100644 --- a/qt_ui/windows/basemenu/QBaseMenu2.py +++ b/qt_ui/windows/basemenu/QBaseMenu2.py @@ -1,5 +1,5 @@ from PySide2.QtCore import Qt -from PySide2.QtGui import QCloseEvent +from PySide2.QtGui import QCloseEvent, QPixmap from PySide2.QtWidgets import QHBoxLayout, QLabel, QWidget, QDialog, QGridLayout from game import Game @@ -35,6 +35,8 @@ class QBaseMenu2(QDialog): self.setWindowFlags(Qt.WindowStaysOnTopHint) self.setMinimumSize(300, 200) + self.setMinimumWidth(680) + self.setMaximumWidth(680) self.setModal(True) self.initUi() @@ -46,6 +48,11 @@ class QBaseMenu2(QDialog): self.topLayoutWidget = QWidget() self.topLayout = QHBoxLayout() + header = QLabel(self) + header.setGeometry(0, 0, 655, 106) + pixmap = QPixmap("./resources/ui/airbase.png") + header.setPixmap(pixmap) + title = QLabel("" + self.cp.name + "") title.setAlignment(Qt.AlignLeft | Qt.AlignTop) title.setProperty("style", "base-title") @@ -59,10 +66,20 @@ class QBaseMenu2(QDialog): self.topLayoutWidget.setLayout(self.topLayout) self.mainLayout = QGridLayout() - self.mainLayout.addWidget(self.topLayoutWidget, 0, 0) - self.mainLayout.addWidget(self.qbase_menu_tab, 1, 0) + self.mainLayout.addWidget(header, 0, 0) + self.mainLayout.addWidget(self.topLayoutWidget, 1, 0) + self.mainLayout.addWidget(self.qbase_menu_tab, 2, 0) self.setLayout(self.mainLayout) def closeEvent(self, closeEvent:QCloseEvent): - GameUpdateSignal.get_instance().updateGame(self.game) \ No newline at end of file + GameUpdateSignal.get_instance().updateGame(self.game) + + + def get_base_image(self): + if self.cp.cptype == ControlPointType.AIRCRAFT_CARRIER_GROUP: + return "./resources/ui/carrier.png" + elif self.cp.cptype == ControlPointType.LHA_GROUP: + return "./resources/ui/lha.png" + else: + return "./resources/ui/airbase.png" \ No newline at end of file diff --git a/qt_ui/windows/basemenu/QRecruitBehaviour.py b/qt_ui/windows/basemenu/QRecruitBehaviour.py index 97c1d56e..86844e65 100644 --- a/qt_ui/windows/basemenu/QRecruitBehaviour.py +++ b/qt_ui/windows/basemenu/QRecruitBehaviour.py @@ -1,5 +1,5 @@ from PySide2.QtWidgets import QLabel, QPushButton, \ - QSizePolicy, QSpacerItem + QSizePolicy, QSpacerItem, QGroupBox, QHBoxLayout from dcs.unittype import UnitType from theater import db @@ -19,55 +19,75 @@ class QRecruitBehaviour: def add_purchase_row(self, unit_type, layout, row): + exist = QGroupBox() + exist.setProperty("style", "buy-box") + exist.setMaximumHeight(36) + exist.setMinimumHeight(36) + existLayout = QHBoxLayout() + exist.setLayout(existLayout) + existing_units = self.cp.base.total_units_of_type(unit_type) scheduled_units = self.deliveryEvent.units.get(unit_type, 0) - unitName = QLabel("" + db.unit_type_name(unit_type) + "") + unitName = QLabel("" + db.unit_type_name_2(unit_type) + "") unitName.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) existing_units = QLabel(str(existing_units)) existing_units.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) - amount_bought = QLabel("{}".format(str(scheduled_units))) + amount_bought = QLabel("{}".format(str(scheduled_units))) amount_bought.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) self.existing_units_labels[unit_type] = existing_units self.bought_amount_labels[unit_type] = amount_bought - price = QLabel("$ {} m".format(db.PRICES[unit_type])) + price = QLabel("$ {:02d} m".format(db.PRICES[unit_type])) price.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) + buysell = QGroupBox() + buysell.setProperty("style", "buy-box") + buysell.setMaximumHeight(36) + buysell.setMinimumHeight(36) + buysellayout = QHBoxLayout() + buysell.setLayout(buysellayout) + buy = QPushButton("+") - buy.setProperty("style", "btn-success") - buy.setMinimumSize(24, 24) + buy.setProperty("style", "btn-buy") + buy.setMinimumSize(16, 16) + buy.setMaximumSize(16, 16) buy.clicked.connect(lambda: self.buy(unit_type)) buy.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) sell = QPushButton("-") - sell.setProperty("style", "btn-danger") - sell.setMinimumSize(24, 24) + sell.setProperty("style", "btn-sell") + sell.setMinimumSize(16, 16) + sell.setMaximumSize(16, 16) sell.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) sell.clicked.connect(lambda: self.sell(unit_type)) - layout.addWidget(unitName, row, 0) - layout.addItem(QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Minimum), row, 1) - layout.addWidget(existing_units, row, 2) - layout.addItem(QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Minimum), row, 3) - layout.addWidget(price, row, 4) - layout.addWidget(sell, row, 5) - layout.addWidget(amount_bought, row, 6) - layout.addWidget(buy, row, 7) + existLayout.addWidget(unitName) + existLayout.addItem(QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Minimum)) + existLayout.addWidget(existing_units) + existLayout.addItem(QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Minimum)) + existLayout.addWidget(price) + + buysellayout.addWidget(sell) + buysellayout.addWidget(amount_bought) + buysellayout.addWidget(buy) + + layout.addWidget(exist, row, 1) + layout.addWidget(buysell, row, 2) return row + 1 def _update_count_label(self, unit_type: UnitType): - self.bought_amount_labels[unit_type].setText("{}".format( + self.bought_amount_labels[unit_type].setText("{}".format( unit_type in self.deliveryEvent.units and "{}".format(self.deliveryEvent.units[unit_type]) or "0" )) - self.existing_units_labels[unit_type].setText("{}".format( + self.existing_units_labels[unit_type].setText("{}".format( self.cp.base.total_units_of_type(unit_type) )) diff --git a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py index c9245e84..2d4621d6 100644 --- a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py @@ -1,4 +1,5 @@ -from PySide2.QtWidgets import QVBoxLayout, QGridLayout, QGroupBox +from PySide2.QtCore import Qt +from PySide2.QtWidgets import QVBoxLayout, QGridLayout, QGroupBox, QScrollArea, QFrame, QWidget from game.event import UnitsDeliveryEvent from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour @@ -6,10 +7,10 @@ from theater import ControlPoint, CAP, CAS, db from game import Game -class QAircraftRecruitmentMenu(QGroupBox, QRecruitBehaviour): +class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): def __init__(self, cp:ControlPoint, game:Game): - QGroupBox.__init__(self, "Recruitment") + QFrame.__init__(self) self.cp = cp self.game = game @@ -25,13 +26,14 @@ class QAircraftRecruitmentMenu(QGroupBox, QRecruitBehaviour): self.init_ui() def init_ui(self): - layout = QVBoxLayout() + main_layout = QVBoxLayout() units = { CAP: db.find_unittype(CAP, self.game.player_name), CAS: db.find_unittype(CAS, self.game.player_name), } + scroll_content = QWidget() task_box_layout = QGridLayout() row = 0 @@ -49,6 +51,11 @@ class QAircraftRecruitmentMenu(QGroupBox, QRecruitBehaviour): stretch.addStretch() task_box_layout.addLayout(stretch, row, 0) - layout.addLayout(task_box_layout) - layout.addStretch() - self.setLayout(layout) + scroll_content.setLayout(task_box_layout) + scroll = QScrollArea() + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) + scroll.setWidgetResizable(True) + scroll.setWidget(scroll_content) + main_layout.addWidget(scroll) + self.setLayout(main_layout) diff --git a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py index 24e834d4..e260d64c 100644 --- a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py @@ -1,4 +1,5 @@ -from PySide2.QtWidgets import QVBoxLayout, QGridLayout, QGroupBox +from PySide2.QtCore import Qt +from PySide2.QtWidgets import QVBoxLayout, QGridLayout, QGroupBox, QFrame, QWidget, QScrollArea from game import Game from game.event import UnitsDeliveryEvent @@ -6,10 +7,10 @@ from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour from theater import ControlPoint, PinpointStrike, db -class QArmorRecruitmentMenu(QGroupBox, QRecruitBehaviour): +class QArmorRecruitmentMenu(QFrame, QRecruitBehaviour): def __init__(self, cp:ControlPoint, game:Game): - QGroupBox.__init__(self, "Recruitment") + QFrame.__init__(self) self.cp = cp self.game = game @@ -25,13 +26,15 @@ class QArmorRecruitmentMenu(QGroupBox, QRecruitBehaviour): self.init_ui() def init_ui(self): - layout = QVBoxLayout() + main_layout = QVBoxLayout() units = { PinpointStrike: db.find_unittype(PinpointStrike, self.game.player_name), } + scroll_content = QWidget() task_box_layout = QGridLayout() + scroll_content.setLayout(task_box_layout) row = 0 for task_type in units.keys(): @@ -44,6 +47,11 @@ class QArmorRecruitmentMenu(QGroupBox, QRecruitBehaviour): stretch.addStretch() task_box_layout.addLayout(stretch, row, 0) - layout.addLayout(task_box_layout) - layout.addStretch() - self.setLayout(layout) + scroll_content.setLayout(task_box_layout) + scroll = QScrollArea() + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) + scroll.setWidgetResizable(True) + scroll.setWidget(scroll_content) + main_layout.addWidget(scroll) + self.setLayout(main_layout) \ No newline at end of file diff --git a/resources/stylesheets/style-dcs.css b/resources/stylesheets/style-dcs.css index ba01ba58..fb41fa21 100644 --- a/resources/stylesheets/style-dcs.css +++ b/resources/stylesheets/style-dcs.css @@ -44,6 +44,12 @@ QMenu::item:selected { background: #435466; } +QScrollBar:horizontal { + background: #435466; +} +QScrollBar:vertical { + background: #435466; +} QLabel{ font-weight:normal; @@ -73,7 +79,6 @@ QTopPanel * { font-weight: bold; } - /*QPushButton*/ QPushButton { background: qlineargradient(x1:0, y1:0, x2:1, y2:1,stop:0 #A4B3B9, stop:1 #85989D); @@ -94,7 +99,7 @@ QPushButton[style="btn-primary"]{ background: qlineargradient(x1:0, y1:0, x2:1, y2:1,stop:0 #A4B3B9, stop:1 #85989D); border: 1px solid #97A9A9; color:#fff; - padding: 6px 20px; + padding: 6px; border-radius:2px; cursor: pointer; font-weight:bold; @@ -110,7 +115,6 @@ QPushButton[style="btn-success"] , QPushButton[style="start-button"]{ background-color:#82A466; color: white; cursor:pointer; - padding: 6px 20px; border-radius:2px; font-weight:bold; text-transform:uppercase; @@ -124,6 +128,44 @@ QPushButton[style="btn-success"]:hover , QPushButton[style="start-button"]:hover background:#5C863F; } + + +/* Buy button */ +QPushButton[style="btn-buy"]{ + background-color:#82A466; + color: white; + cursor:pointer; + border-radius:2px; + font-weight:bold; + text-transform:uppercase; + margin: 0px; + padding: 2px; +} + +QPushButton[style="btn-buy"]:hover{ + cursor:pointer; + background:#5C863F; +} + +/* Sell button */ +QPushButton[style="btn-sell"]{ + background-color:#9E3232; + color: white; + cursor:pointer; + border-radius:2px; + font-weight:bold; + text-transform:uppercase; + margin: 0px; + padding: 2px; +} + +QPushButton[style="btn-sell"]:hover{ + cursor:pointer; + background:#D84545; +} + + + QPushButton[style="btn-danger"]{ background-color:#9E3232; color: white; @@ -239,10 +281,15 @@ QGroupBox { margin:5px; } +QGroupBox[style="buy-box"]{ + padding: 0px; + margin: 0px; +} + QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; /* position at the top left */ - padding: 3px; + padding: 5px; color: #B7C0C6; font-weight: 800; } @@ -275,18 +322,18 @@ QDialog{ } - QListView { -border: none; + border: 1px solid #14202B; + background-color: #14202B; } /*QTabWidget*/ QTabWidget::pane { /* The tab widget frame */ - border-top: 2px solid #1D2731; + border: 1px solid #1D2731; } QTabWidget::tab-bar { - + border: 1px solid #14202B; } QTabBar::tab { @@ -294,6 +341,7 @@ QTabBar::tab { background: #202C3A; border-right: 1px solid #14202B; border-left: 1px solid #14202B; + border-top: 1px solid #14202B; min-width: 8ex; padding: 6px 10px; } @@ -401,23 +449,26 @@ QHeaderView::section { QHeaderView::section:horizontal { - border: none; + /*border: none;*/ text-align:left; background: #4B5B74; - } QHeaderView::section:vertical { - border: none; + /*border: none;*/ text-align:left; background: #4B5B74; } + QTableWidget { - gridline-color: #1D2731; + gridline-color: red; background: #4B5B74; } +QTableView QTableCornerButton::section { + background: #4B5B74; +} /*helper modifiers*/ *[style="no-border"] { diff --git a/resources/stylesheets/style.css b/resources/stylesheets/style.css index 1d387adb..7a6f74cf 100644 --- a/resources/stylesheets/style.css +++ b/resources/stylesheets/style.css @@ -42,7 +42,7 @@ QPushButton[style="start-button"]{ background-color:#699245; color: white; cursor:pointer; - padding: 15px 15px 15px 15px; + padding: 5px 5px 5px 5px; border-radius:5px; } @@ -53,6 +53,40 @@ QPushButton[style="start-button"]:hover{ cursor: pointer; } +/* Buy button */ +QPushButton[style="btn-buy"]{ + background-color:#82A466; + color: white; + cursor:pointer; + border-radius:2px; + font-weight:bold; + text-transform:uppercase; + margin: 0px; + padding: 2px; +} + +QPushButton[style="btn-buy"]:hover{ + cursor:pointer; + background:#5C863F; +} + +/* Sell button */ +QPushButton[style="btn-sell"]{ + background-color:#9E3232; + color: white; + cursor:pointer; + border-radius:2px; + font-weight:bold; + text-transform:uppercase; + margin: 0px; + padding: 2px; +} + +QPushButton[style="btn-sell"]:hover{ + cursor:pointer; + background:#D84545; +} + QPushButton[style="btn-danger"]{ background-color:#9E3232; color: white; diff --git a/resources/ui/airbase.png b/resources/ui/airbase.png new file mode 100644 index 00000000..9fe36945 Binary files /dev/null and b/resources/ui/airbase.png differ diff --git a/resources/ui/carrier.png b/resources/ui/carrier.png new file mode 100644 index 00000000..4ae09dd6 Binary files /dev/null and b/resources/ui/carrier.png differ diff --git a/resources/ui/conflict.png b/resources/ui/conflict.png new file mode 100644 index 00000000..ee233637 Binary files /dev/null and b/resources/ui/conflict.png differ diff --git a/resources/ui/debriefing.png b/resources/ui/debriefing.png new file mode 100644 index 00000000..ceda551f Binary files /dev/null and b/resources/ui/debriefing.png differ diff --git a/resources/ui/lha.png b/resources/ui/lha.png new file mode 100644 index 00000000..7f2a68e9 Binary files /dev/null and b/resources/ui/lha.png differ diff --git a/resources/ui/loader.gif b/resources/ui/loader.gif index 47adbf03..1a29732d 100644 Binary files a/resources/ui/loader.gif and b/resources/ui/loader.gif differ diff --git a/userdata/debriefing.py b/userdata/debriefing.py index be30930e..5886270e 100644 --- a/userdata/debriefing.py +++ b/userdata/debriefing.py @@ -1,17 +1,11 @@ import json import logging import os -import re import threading import time import typing -from dcs.lua import parse -from dcs.mission import Mission -from dcs.unit import UnitType - from game import db -from .persistency import base_path DEBRIEFING_LOG_EXTENSION = "log" @@ -158,9 +152,6 @@ class Debriefing: else: self.enemy_dead_buildings_dict[a.type] = 1 - for destroyed_unit in self.destroyed_units: - game.add_destroyed_units(destroyed_unit) - logging.info("--------------------------------") logging.info("Debriefing pre process results :") logging.info("--------------------------------") @@ -172,20 +163,39 @@ class Debriefing: logging.info(self.enemy_dead_buildings_dict) -def _poll_new_debriefing_log(callback: typing.Callable, game): - if os.path.isfile("state.json"): - last_modified = os.path.getmtime("state.json") - else: - last_modified = 0 - while True: - if os.path.isfile("state.json") and os.path.getmtime("state.json") > last_modified: - with open("state.json", "r") as json_file: - json_data = json.load(json_file) #Debriefing.parse(os.path.join(debriefing_directory_location(), file)) - debriefing = Debriefing(json_data, game) - callback(debriefing) - break - time.sleep(5) +class PollDebriefingFileThread(threading.Thread): + """Thread class with a stop() method. The thread itself has to check + regularly for the stopped() condition.""" -def wait_for_debriefing(callback: typing.Callable, game): - threading.Thread(target=_poll_new_debriefing_log, args=[callback, game]).start() + def __init__(self, callback: typing.Callable, game): + super(PollDebriefingFileThread, self).__init__() + self._stop_event = threading.Event() + self.callback = callback + self.game = game + + def stop(self): + self._stop_event.set() + + def stopped(self): + return self._stop_event.is_set() + + def run(self): + if os.path.isfile("state.json"): + last_modified = os.path.getmtime("state.json") + else: + last_modified = 0 + while not self.stopped(): + if os.path.isfile("state.json") and os.path.getmtime("state.json") > last_modified: + with open("state.json", "r") as json_file: + json_data = json.load(json_file) + debriefing = Debriefing(json_data, self.game) + self.callback(debriefing) + break + time.sleep(5) + + +def wait_for_debriefing(callback: typing.Callable, game)->PollDebriefingFileThread: + thread = PollDebriefingFileThread(callback, game) + thread.start() + return thread diff --git a/userdata/persistency.py b/userdata/persistency.py index 27af4677..61c0ee9f 100644 --- a/userdata/persistency.py +++ b/userdata/persistency.py @@ -4,11 +4,12 @@ import pickle import shutil _dcs_saved_game_folder = None # type: str - +_file_abs_path = None def setup(user_folder: str): global _dcs_saved_game_folder _dcs_saved_game_folder = user_folder + _file_abs_path = os.path.join(base_path(), "liberation_save") def base_path() -> str: