Added widget for toggling quickplay properties

This commit is contained in:
Aruki 2019-04-07 00:00:33 -07:00
parent 7de85a5a2d
commit 42d079ff49
12 changed files with 344 additions and 51 deletions

View File

@ -70,17 +70,17 @@ bool CEditorApplication::CloseAllEditors()
bool CEditorApplication::CloseProject()
{
if (mpActiveProject && CloseAllEditors())
{
// Close any active quickplay sessions
NDolphinIntegration::KillQuickplay();
if (mpActiveProject && !CloseAllEditors())
return false;
// Emit before actually deleting the project to allow editor references to clean up
CGameProject *pOldProj = mpActiveProject;
mpActiveProject = nullptr;
emit ActiveProjectChanged(nullptr);
delete pOldProj;
}
// Close any active quickplay sessions
NDolphinIntegration::KillQuickplay();
// Emit before actually deleting the project to allow editor references to clean up
CGameProject *pOldProj = mpActiveProject;
mpActiveProject = nullptr;
emit ActiveProjectChanged(nullptr);
delete pOldProj;
return true;
}
@ -255,6 +255,22 @@ bool CEditorApplication::CookPackageList(QList<CPackage*> PackageList)
else return true;
}
bool CEditorApplication::HasAnyDirtyPackages()
{
if (!mpActiveProject)
return false;
for (uint32 PkgIdx = 0; PkgIdx < mpActiveProject->NumPackages(); PkgIdx++)
{
CPackage *pPackage = mpActiveProject->PackageByIndex(PkgIdx);
if (pPackage->NeedsRecook())
return true;
}
return false;
}
bool CEditorApplication::RebuildResourceDatabase()
{
// Make sure all editors are closed

View File

@ -43,6 +43,7 @@ public:
bool CookPackage(CPackage *pPackage);
bool CookAllDirtyPackages();
bool CookPackageList(QList<CPackage*> PackageList);
bool HasAnyDirtyPackages();
bool RebuildResourceDatabase();

View File

@ -0,0 +1,98 @@
#include "CQuickplayPropertyEditor.h"
#include "ui_CQuickplayPropertyEditor.h"
#include "UICommon.h"
#include <QFileInfo>
/** Validator class for Dolphin line edit */
class CDolphinValidator : public QValidator
{
public:
CDolphinValidator(QObject* pParent = 0) : QValidator(pParent) {}
virtual QValidator::State validate(QString& Input, int& Pos) const override
{
return PathValid(Input) ? QValidator::Acceptable : QValidator::Invalid;
}
static bool PathValid(const QString& kPath)
{
QFileInfo FileInfo(kPath);
return FileInfo.exists() && FileInfo.suffix() == "exe";
}
};
/** CQuickplayPropertyEditor functions */
CQuickplayPropertyEditor::CQuickplayPropertyEditor(SQuickplayParameters& Parameters, QWidget* pParent /*= 0*/)
: QMenu(pParent)
, mpUI(new Ui::CQuickplayPropertyEditor)
, mParameters(Parameters)
{
mpUI->setupUi(this);
setMinimumWidth(300);
NDolphinIntegration::LoadQuickplayParameters(Parameters);
mpUI->DolphinPathLineEdit->setText( NDolphinIntegration::GetDolphinPath() );
mpUI->DolphinPathLineEdit->setValidator( new CDolphinValidator(this) );
mpUI->BootToAreaCheckBox->setChecked( Parameters.Features.HasFlag(EQuickplayFeature::JumpToArea) );
mpUI->SpawnAtCameraLocationCheckBox->setChecked( Parameters.Features.HasFlag(EQuickplayFeature::SetSpawnPosition) );
connect(mpUI->DolphinPathLineEdit, SIGNAL(textChanged(QString)),
this, SLOT(OnDolphinPathChanged(QString)));
connect(mpUI->BootToAreaCheckBox, SIGNAL(toggled(bool)),
this, SLOT(OnBootToAreaToggled(bool)));
connect(mpUI->SpawnAtCameraLocationCheckBox, SIGNAL(toggled(bool)),
this, SLOT(OnSpawnAtCameraLocationToggled(bool)));
}
CQuickplayPropertyEditor::~CQuickplayPropertyEditor()
{
delete mpUI;
}
void CQuickplayPropertyEditor::BrowseForDolphin()
{
QString Path = UICommon::OpenFileDialog(this, "Open Dolphin", "Dolphin.exe");
if (!Path.isEmpty())
{
mpUI->DolphinPathLineEdit->setText(Path);
}
}
void CQuickplayPropertyEditor::OnDolphinPathChanged(const QString& kNewPath)
{
if (CDolphinValidator::PathValid(kNewPath))
{
NDolphinIntegration::SetDolphinPath(parentWidget(), kNewPath);
}
}
void CQuickplayPropertyEditor::OnBootToAreaToggled(bool Enabled)
{
if (Enabled)
{
mParameters.Features.SetFlag(EQuickplayFeature::JumpToArea);
}
else
{
mParameters.Features.ClearFlag(EQuickplayFeature::JumpToArea);
}
NDolphinIntegration::SaveQuickplayParameters(mParameters);
}
void CQuickplayPropertyEditor::OnSpawnAtCameraLocationToggled(bool Enabled)
{
if (Enabled)
{
mParameters.Features.SetFlag(EQuickplayFeature::SetSpawnPosition);
}
else
{
mParameters.Features.ClearFlag(EQuickplayFeature::SetSpawnPosition);
}
NDolphinIntegration::SaveQuickplayParameters(mParameters);
}

View File

@ -0,0 +1,32 @@
#ifndef CQUICKPLAYPROPERTYEDITOR_H
#define CQUICKPLAYPROPERTYEDITOR_H
#include <QMenu>
#include "NDolphinIntegration.h"
namespace Ui {
class CQuickplayPropertyEditor;
}
/** Property editor widget for quickplay.
* @todo may want this to use a CPropertyView eventually.
*/
class CQuickplayPropertyEditor : public QMenu
{
Q_OBJECT
Ui::CQuickplayPropertyEditor* mpUI;
SQuickplayParameters& mParameters;
public:
CQuickplayPropertyEditor(SQuickplayParameters& Parameters, QWidget* pParent = 0);
~CQuickplayPropertyEditor();
public slots:
void BrowseForDolphin();
void OnDolphinPathChanged(const QString& kNewPath);
void OnBootToAreaToggled(bool Enabled);
void OnSpawnAtCameraLocationToggled(bool Enabled);
};
#endif // CQUICKPLAYPROPERTYEDITOR_H

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CQuickplayPropertyEditor</class>
<widget class="QMenu" name="CQuickplayPropertyEditor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>345</width>
<height>94</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="DolphinPathLabel">
<property name="text">
<string>Dolphin Path</string>
</property>
</widget>
</item>
<item>
<widget class="CSoftValidatorLineEdit" name="DolphinPathLineEdit"/>
</item>
<item>
<widget class="QToolButton" name="DolphinBrowseButton">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="BootToAreaCheckBox">
<property name="text">
<string>Boot to Current Area</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="SpawnAtCameraLocationCheckBox">
<property name="text">
<string>Spawn at Camera Location</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>CSoftValidatorLineEdit</class>
<extends>QLineEdit</extends>
<header>Editor/Widgets/CSoftValidatorLineEdit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -519,7 +519,7 @@ void CSceneViewport::OnPlayFromHere()
if (mpMenuNode)
{
pOwnerWorldEd->LaunchQuickplayFromLocation(mMenuPoint);
pOwnerWorldEd->LaunchQuickplayFromLocation(mMenuPoint, true);
}
else
{

View File

@ -207,7 +207,8 @@ HEADERS += \
Undo/CSaveStoreCommand.h \
CollisionEditor/CCollisionEditor.h \
CollisionEditor/CCollisionEditorViewport.h \
NDolphinIntegration.h
NDolphinIntegration.h \
CQuickplayPropertyEditor.h
# Source Files
SOURCES += \
@ -286,7 +287,8 @@ SOURCES += \
ScanEditor/CScanEditor.cpp \
CollisionEditor/CCollisionEditor.cpp \
CollisionEditor/CCollisionEditorViewport.cpp \
NDolphinIntegration.cpp
NDolphinIntegration.cpp \
CQuickplayPropertyEditor.cpp
# UI Files
FORMS += \
@ -315,7 +317,8 @@ FORMS += \
StringEditor/CStringEditor.ui \
CTweakEditor.ui \
ScanEditor/CScanEditor.ui \
CollisionEditor/CCollisionEditor.ui
CollisionEditor/CCollisionEditor.ui \
CQuickplayPropertyEditor.ui
# Codegen
CODEGEN_DIR = $$EXTERNALS_DIR/CodeGen

View File

@ -11,6 +11,8 @@ namespace NDolphinIntegration
/** Constants */
const char* const gkDolphinPathSetting = "Quickplay/DolphinPath";
const char* const gkFeaturesSetting = "Quickplay/Features";
const char* const gkRelFileName = "EditorQuickplay.rel";
const char* const gkParameterFile = "dbgconfig";
const char* const gkDolPath = "sys/main.dol";
@ -20,23 +22,27 @@ const char* const gkDolBackupPath = "sys/main.original.dol";
QString gDolphinPath;
/** The current Dolphin quickplay process */
QProcess gDolphinProcess;
QProcess* gpDolphinProcess = nullptr;
/** The project that the current active quickplay session is running for */
CGameProject* gpQuickplayProject = nullptr;
/** Quickplay relay implementation to detect when the active quickplay session closes */
void CQuickplayRelay::TrackProcess(QProcess* pProcess)
void CQuickplayRelay::QuickplayStarted()
{
disconnect(this);
connect(pProcess, SIGNAL(finished(int)), this, SLOT(OnQuickplayFinished(int)));
debugf("Quickplay session started.");
connect(gpDolphinProcess, SIGNAL(finished(int)), this, SLOT(QuickplayFinished(int)));
}
void CQuickplayRelay::OnQuickplayFinished(int ReturnCode)
void CQuickplayRelay::QuickplayFinished(int ReturnCode)
{
debugf("Quickplay session finished.");
disconnect(this);
disconnect(gpDolphinProcess, 0, this, 0);
CleanupQuickplayFiles(gpQuickplayProject);
gpDolphinProcess->waitForFinished();
gpDolphinProcess->deleteLater();
gpDolphinProcess = nullptr;
gpQuickplayProject = nullptr;
}
@ -62,7 +68,7 @@ EQuickplayLaunchResult LaunchQuickplay(QWidget* pParentWidget,
}
// Check if quickplay is already running
if (gDolphinProcess.state() != QProcess::NotRunning)
if (gpDolphinProcess && gpDolphinProcess->state() != QProcess::NotRunning)
{
if (UICommon::YesNoQuestion(pParentWidget, "Quickplay",
"Quickplay is already running. Close the existing session and relaunch?"))
@ -80,8 +86,7 @@ EQuickplayLaunchResult LaunchQuickplay(QWidget* pParentWidget,
if (gDolphinPath.isEmpty())
{
// If the user configured Dolphin on a previous run, it should be stashed in settings
QSettings Settings;
QString Path = Settings.value(gkDolphinPathSetting).toString();
QString Path = GetDolphinPath();
if (Path.isEmpty())
{
@ -99,6 +104,16 @@ EQuickplayLaunchResult LaunchQuickplay(QWidget* pParentWidget,
}
}
// Check if the user has any dirty packages
if (gpEdApp->HasAnyDirtyPackages())
{
if (UICommon::YesNoQuestion(pParentWidget, "Uncooked Changes",
"You have uncooked changes. Cook before starting quickplay?"))
{
gpEdApp->CookAllDirtyPackages();
}
}
// All good. Perform initialization tasks. Start by creating the patched dol.
TString DiscSys = pProject->DiscDir(false) / "sys";
std::vector<uint8> DolData;
@ -139,7 +154,7 @@ EQuickplayLaunchResult LaunchQuickplay(QWidget* pParentWidget,
memcpy(&DolData[PatchOffset], &PatchData[0], PatchData.size());
// These constants are hardcoded for the moment.
// We write the patch to text section 6, which should be at address 0x80002600.
// We write the patch to text section 6, which must be at address 0x80002600.
// We hook over the call to LCEnable, which is at offset 0x1D64.
CMemoryOutStream Mem(DolData.data(), DolData.size(), EEndian::BigEndian);
Mem.GoTo(0x18);
@ -167,18 +182,20 @@ EQuickplayLaunchResult LaunchQuickplay(QWidget* pParentWidget,
kParms.Write(FSTParmPath);
// We're good to go - launch the quickplay process
gDolphinProcess.start(gDolphinPath, QStringList() << TO_QSTRING(DolPath));
gDolphinProcess.waitForStarted();
gpDolphinProcess = new QProcess(pParentWidget);
gpDolphinProcess->start(gDolphinPath, QStringList() << TO_QSTRING(DolPath));
gpDolphinProcess->waitForStarted();
if (gDolphinProcess.state() != QProcess::Running)
if (gpDolphinProcess->state() != QProcess::Running)
{
warnf("Quickplay launch failed! Process did not start correctly.");
delete gpDolphinProcess;
gpDolphinProcess = nullptr;
return EQuickplayLaunchResult::Failure;
}
gQuickplayRelay.TrackProcess(&gDolphinProcess);
gpQuickplayProject = pProject;
debugf("Quickplay session started.");
gQuickplayRelay.QuickplayStarted();
return EQuickplayLaunchResult::Success;
}
@ -196,10 +213,12 @@ bool IsQuickplaySupported(CGameProject* pProject)
/** Kill the current quickplay process, if it exists. */
void KillQuickplay()
{
debugf("Killing quickplay.");
gDolphinProcess.close();
CleanupQuickplayFiles(gpQuickplayProject);
gpQuickplayProject = nullptr;
if (gpDolphinProcess)
{
debugf("Stopping active quickplay session.");
gpDolphinProcess->close();
// CQuickplayRelay handles remaining destruction & cleanup
}
}
/** Clean up any quickplay related file data from the project disc files. */
@ -250,4 +269,38 @@ bool SetDolphinPath(QWidget* pParentWidget, const QString& kDolphinPath, bool bS
return true;
}
/** Retrieves the user path to Dolphin. */
QString GetDolphinPath()
{
// Check if we have a path to Dolphin
QString Path = gDolphinPath;
if (Path.isEmpty())
{
// If the user configured Dolphin on a previous run, it should be stashed in settings
QSettings Settings;
Path = Settings.value(gkDolphinPathSetting).toString();
if (!Path.isEmpty())
{
gDolphinPath = Path;
}
}
return Path;
}
/** Saves/retrieves the given quickplay settings to/from QSettings. */
void SaveQuickplayParameters(const SQuickplayParameters& kParms)
{
QSettings Settings;
Settings.setValue(gkFeaturesSetting, kParms.Features.ToInt32());
}
void LoadQuickplayParameters(SQuickplayParameters& Parms)
{
QSettings Settings;
Parms.Features = Settings.value(gkFeaturesSetting, (uint32) EQuickplayFeature::DefaultFeatures).toInt();
}
}

View File

@ -13,9 +13,6 @@
// are mirrored in PWEQuickplayPatch and are used by the game.
// If you modify one, make sure you modify the other.
namespace NDolphinIntegration
{
/** Return value for LaunchQuickplay */
enum class EQuickplayLaunchResult
{
@ -33,6 +30,9 @@ enum class EQuickplayFeature
JumpToArea = 0x00000001,
/** Spawn the player in the location specified by SpawnTransform */
SetSpawnPosition = 0x00000002,
/** Flags enabled by default */
DefaultFeatures = JumpToArea | SetSpawnPosition
};
DECLARE_FLAGS_ENUMCLASS(EQuickplayFeature, FQuickplayFeatures)
@ -71,17 +71,19 @@ struct SQuickplayParameters
}
};
/** Minimal relay class that detects when the active quickplay session is closed */
namespace NDolphinIntegration
{
/** Minimal relay class for internal use that detects when the active quickplay session is closed */
class CQuickplayRelay : public QObject
{
Q_OBJECT
public:
CQuickplayRelay() {}
void TrackProcess(QProcess* pProcess);
public slots:
void OnQuickplayFinished(int ReturnCode);
void QuickplayStarted();
void QuickplayFinished(int ReturnCode);
};
/** Attempt to launch quickplay based on the current editor state. */
@ -103,6 +105,13 @@ bool SetDolphinPath(QWidget* pParentWidget,
const QString& kDolphinPath,
bool bSilent = false);
/** Retrieves the user path to Dolphin. */
QString GetDolphinPath();
/** Saves/retrieves the given quickplay settings to/from QSettings. */
void SaveQuickplayParameters(const SQuickplayParameters& kParms);
void LoadQuickplayParameters(SQuickplayParameters& Parms);
}
#endif // CQUICKPLAYCONTROLLER_H

View File

@ -12,6 +12,7 @@
#include "Editor/CExportGameDialog.h"
#include "Editor/CNodeCopyMimeData.h"
#include "Editor/CProjectSettingsDialog.h"
#include "Editor/CQuickplayPropertyEditor.h"
#include "Editor/CSelectionIterator.h"
#include "Editor/UICommon.h"
#include "Editor/PropertyEdit/CPropertyView.h"
@ -122,6 +123,11 @@ CWorldEditor::CWorldEditor(QWidget *parent)
// Quickplay buttons
QToolButton* pQuickplayButton = new QToolButton(this);
pQuickplayButton->setIcon( QIcon(":/icons/Play_32px.png") );
pQuickplayButton->setPopupMode( QToolButton::MenuButtonPopup );
pQuickplayButton->setMenu( new CQuickplayPropertyEditor(mQuickplayParms, this) );
pQuickplayButton->setToolTip( "Quickplay" );
ui->MainToolBar->addSeparator();
mpQuickplayAction = ui->MainToolBar->addWidget(pQuickplayButton);
mpQuickplayAction->setVisible(false);
mpQuickplayAction->setEnabled(false);
@ -950,23 +956,29 @@ void CWorldEditor::UpdateNewLinkLine()
void CWorldEditor::LaunchQuickplay()
{
CVector3f CameraPosition = Viewport()->Camera().Position();
LaunchQuickplayFromLocation(CameraPosition);
LaunchQuickplayFromLocation(CameraPosition, false);
}
void CWorldEditor::LaunchQuickplayFromLocation(CVector3f Location)
void CWorldEditor::LaunchQuickplayFromLocation(CVector3f Location, bool ForceAsSpawnPosition)
{
// This function should not be called if a level is not open in a project.
ASSERT( gpEdApp->ActiveProject() != nullptr );
ASSERT( mpWorld && mpArea );
// Fill in parameters and start running
mQuickplayParms.BootWorldAssetID = mpWorld->ID().ToLong();
mQuickplayParms.BootAreaAssetID = mpArea->ID().ToLong();
mQuickplayParms.SpawnTransform = Viewport()->Camera().GetCameraTransform();
mQuickplayParms.SpawnTransform.SetTranslation(Location);
mQuickplayParms.Features.SetFlag(NDolphinIntegration::EQuickplayFeature::JumpToArea);
mQuickplayParms.Features.SetFlag(NDolphinIntegration::EQuickplayFeature::SetSpawnPosition);
NDolphinIntegration::LaunchQuickplay(this, gpEdApp->ActiveProject(), mQuickplayParms);
SQuickplayParameters Parameters = mQuickplayParms;
Parameters.BootWorldAssetID = mpWorld->ID().ToLong();
Parameters.BootAreaAssetID = mpArea->ID().ToLong();
Parameters.SpawnTransform = Viewport()->Camera().GetCameraTransform();
Parameters.SpawnTransform.SetTranslation(Location);
if (ForceAsSpawnPosition)
{
Parameters.Features.SetFlag(EQuickplayFeature::JumpToArea);
Parameters.Features.SetFlag(EQuickplayFeature::SetSpawnPosition);
}
NDolphinIntegration::LaunchQuickplay(this, gpEdApp->ActiveProject(), Parameters);
}
// ************ PROTECTED ************

View File

@ -67,7 +67,7 @@ class CWorldEditor : public INodeEditor
// Quickplay
QAction* mpQuickplayAction;
NDolphinIntegration::SQuickplayParameters mQuickplayParms;
SQuickplayParameters mQuickplayParms;
// Sidebars
QVBoxLayout* mpRightSidebarLayout;
@ -136,7 +136,7 @@ public slots:
void UpdateNewLinkLine();
void LaunchQuickplay();
void LaunchQuickplayFromLocation(CVector3f Location);
void LaunchQuickplayFromLocation(CVector3f Location, bool ForceAsSpawnPosition);
protected:
QAction* AddEditModeButton(QIcon Icon, QString ToolTip, EWorldEditorMode Mode);

View File

@ -1,5 +1,6 @@
#include "CEditorApplication.h"
#include "CUIRelay.h"
#include "NDolphinIntegration.h"
#include "UICommon.h"
#include <Common/Log.h>
@ -75,6 +76,7 @@ public:
/** Clean up any resources at the end of application execution */
~CMain()
{
NDolphinIntegration::KillQuickplay();
NGameList::Shutdown();
}