Multiple WIP changes on UI / Submit manually debriefing.

This commit is contained in:
Khopa 2020-07-26 12:59:16 +02:00
parent cfa4f7da2e
commit f897cf745f
26 changed files with 399 additions and 369 deletions

View File

@ -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

View File

@ -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:

View File

@ -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():

View File

@ -125,7 +125,11 @@ class Operation:
# Generate destroyed units
for d in self.game.get_destroyed_units():
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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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("<b>Operation Succesfull !</b>")
title.setProperty("style", "title-success")
else:
title = QLabel("<b>Operation failed !</b>")
title.setProperty("style", "title-danger")
# Result
#if self.gameEvent.is_successfull(self.debriefing):
# title = QLabel("<b>Operation end !</b>")
# title.setProperty("style", "title-success")
#else:
# title = QLabel("<b>Operation end !</b>")
# title.setProperty("style", "title-danger")
title = QLabel("<b>Casualty report</b>")
self.layout.addWidget(title)
# Player lost units

View File

@ -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

View File

@ -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("<b>You are clear for takeoff</b>"), 1, 0)
self.gridLayout.addWidget(QLabel(""), 2, 0)
self.gridLayout.addWidget(QLabel("<h2>For Singleplayer :</h2>"), 3, 0)
self.gridLayout.addWidget(QLabel("In DCS, open the Mission Editor, and load the file : "), 4, 0)
self.gridLayout.addWidget(QLabel("<i>liberation_nextturn</i>"), 5, 0)
self.gridLayout.addWidget(QLabel("Then once the mission is loaded in ME, in menu \"Flight\", click on FLY Mission to launch"), 6, 0)
self.gridLayout.addWidget(QLabel(""), 7, 0)
self.gridLayout.addWidget(QLabel("<h2>For Multiplayer :</h2>"), 8, 0)
self.gridLayout.addWidget(QLabel("In DCS, open the Mission Editor, and load the file : "), 9, 0)
self.gridLayout.addWidget(QLabel("<i>liberation_nextturn</i>"), 10, 0)
self.gridLayout.addWidget(QLabel("Click on File/Save. Then exit the mission editor, and go to Multiplayer."), 11, 0)
self.gridLayout.addWidget(QLabel("Then host a server with the mission, and tell your friends to join !"), 12, 0)
self.gridLayout.addWidget(QLabel("(The step in the mission editor is important, and fix a game breaking bug.)"), 13, 0)
self.gridLayout.addWidget(QLabel(""), 14, 0)
TEXT = "" + \
"<b>You are clear for takeoff</b>" + \
"" + \
"<h2>For Singleplayer :</h2>\n" + \
"In DCS, open the Mission Editor, and load the file : \n" + \
"<i>liberation_nextturn</i>\n" + \
"<p>Then once the mission is loaded in ME, in menu \"Flight\",\n" + \
"click on FLY Mission to launch.</p>\n" + \
"" + \
"<h2>For Multiplayer :</h2>" + \
"In DCS, open the Mission Editor, and load the file : " + \
"<i>liberation_nextturn</i>" + \
"<p>Click on File/Save. Then exit the mission editor, and go to Multiplayer.</p>" + \
"<p>Then host a server with the mission, and tell your friends to join !</p>" + \
"<i>(The step in the mission editor is important, and fix a game breaking bug.)</i>" + \
"<h2>Finishing</h2>" + \
"<p>Once you have played the mission, click on the \"Accept Results\" button.</p>"
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("<b>Mission is being played</b>"), 1, 0)
self.gridLayout.addWidget(self.actions, 2, 0)
else:
#self.gridLayout.addWidget(QLabel("<b>Mission is over !</b>"), 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

View File

@ -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("<b>" + self.cp.name + "</b>")
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("<b>" + db.unit_type_name(unit_type) + "</b>"), 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("<b>" + db.unit_type_name(unit_type) + "</b>")
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)

View File

@ -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("<b>" + self.cp.name + "</b>")
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)
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"

View File

@ -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("<b>" + db.unit_type_name(unit_type) + "</b>")
unitName = QLabel("<b>" + db.unit_type_name_2(unit_type) + "</b>")
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("<b>{}</b>".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("$ <b>{}</b> m".format(db.PRICES[unit_type]))
price = QLabel("<b>$ {:02d}</b> 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("<b>{}</b>".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("<b>{}</b>".format(
self.cp.base.total_units_of_type(unit_type)
))

View File

@ -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)

View File

@ -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)

View File

@ -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"] {

View File

@ -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;

BIN
resources/ui/airbase.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

BIN
resources/ui/carrier.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

BIN
resources/ui/conflict.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

BIN
resources/ui/debriefing.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

BIN
resources/ui/lha.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -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):
class PollDebriefingFileThread(threading.Thread):
"""Thread class with a stop() method. The thread itself has to check
regularly for the stopped() condition."""
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 True:
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.parse(os.path.join(debriefing_directory_location(), file))
debriefing = Debriefing(json_data, game)
callback(debriefing)
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):
threading.Thread(target=_poll_new_debriefing_log, args=[callback, game]).start()
def wait_for_debriefing(callback: typing.Callable, game)->PollDebriefingFileThread:
thread = PollDebriefingFileThread(callback, game)
thread.start()
return thread

View File

@ -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: