Quickplay support
This commit is contained in:
parent
3507be8e42
commit
7de85a5a2d
|
@ -1 +1 @@
|
||||||
Subproject commit 685830ad4aa398baee88df2f010ef94f5915c03d
|
Subproject commit b16f7e26a1120bffb8e474625cceefc3c48cef99
|
Binary file not shown.
Binary file not shown.
|
@ -200,6 +200,14 @@ void CCamera::LoadMatrices() const
|
||||||
CGraphics::UpdateMVPBlock();
|
CGraphics::UpdateMVPBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CTransform4f CCamera::GetCameraTransform() const
|
||||||
|
{
|
||||||
|
CTransform4f Out = CTransform4f::skIdentity;
|
||||||
|
Out.SetRotationFromAxes(mRightVector, mDirection, mUpVector);
|
||||||
|
Out.SetTranslation(mPosition);
|
||||||
|
return Out;
|
||||||
|
}
|
||||||
|
|
||||||
// ************ PRIVATE ************
|
// ************ PRIVATE ************
|
||||||
void CCamera::ValidatePitch()
|
void CCamera::ValidatePitch()
|
||||||
{
|
{
|
||||||
|
|
|
@ -61,6 +61,7 @@ public:
|
||||||
void ProcessMouseInput(FKeyInputs KeyFlags, FMouseInputs MouseFlags, float XMovement, float YMovement);
|
void ProcessMouseInput(FKeyInputs KeyFlags, FMouseInputs MouseFlags, float XMovement, float YMovement);
|
||||||
CRay CastRay(CVector2f DeviceCoords) const;
|
CRay CastRay(CVector2f DeviceCoords) const;
|
||||||
void LoadMatrices() const;
|
void LoadMatrices() const;
|
||||||
|
CTransform4f GetCameraTransform() const;
|
||||||
|
|
||||||
void SetMoveMode(ECameraMoveMode Mode);
|
void SetMoveMode(ECameraMoveMode Mode);
|
||||||
void SetOrbit(const CVector3f& rkOrbitTarget, float Distance);
|
void SetOrbit(const CVector3f& rkOrbitTarget, float Distance);
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "CBasicViewport.h"
|
#include "CBasicViewport.h"
|
||||||
#include "CProgressDialog.h"
|
#include "CProgressDialog.h"
|
||||||
#include "CProjectSettingsDialog.h"
|
#include "CProjectSettingsDialog.h"
|
||||||
|
#include "NDolphinIntegration.h"
|
||||||
|
|
||||||
#include "Editor/CharacterEditor/CCharacterEditor.h"
|
#include "Editor/CharacterEditor/CCharacterEditor.h"
|
||||||
#include "Editor/CollisionEditor/CCollisionEditor.h"
|
#include "Editor/CollisionEditor/CCollisionEditor.h"
|
||||||
|
@ -71,6 +72,9 @@ bool CEditorApplication::CloseProject()
|
||||||
{
|
{
|
||||||
if (mpActiveProject && CloseAllEditors())
|
if (mpActiveProject && CloseAllEditors())
|
||||||
{
|
{
|
||||||
|
// Close any active quickplay sessions
|
||||||
|
NDolphinIntegration::KillQuickplay();
|
||||||
|
|
||||||
// Emit before actually deleting the project to allow editor references to clean up
|
// Emit before actually deleting the project to allow editor references to clean up
|
||||||
CGameProject *pOldProj = mpActiveProject;
|
CGameProject *pOldProj = mpActiveProject;
|
||||||
mpActiveProject = nullptr;
|
mpActiveProject = nullptr;
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "CEditorApplication.h"
|
#include "CEditorApplication.h"
|
||||||
#include "CExportGameDialog.h"
|
#include "CExportGameDialog.h"
|
||||||
#include "CProgressDialog.h"
|
#include "CProgressDialog.h"
|
||||||
|
#include "NDolphinIntegration.h"
|
||||||
#include "UICommon.h"
|
#include "UICommon.h"
|
||||||
#include "Editor/ResourceBrowser/CResourceBrowser.h"
|
#include "Editor/ResourceBrowser/CResourceBrowser.h"
|
||||||
#include <Common/Macros.h>
|
#include <Common/Macros.h>
|
||||||
|
@ -184,6 +185,9 @@ void CProjectSettingsDialog::BuildISO()
|
||||||
|
|
||||||
if (gpEdApp->CookAllDirtyPackages())
|
if (gpEdApp->CookAllDirtyPackages())
|
||||||
{
|
{
|
||||||
|
// Make sure there will be no leftover quickplay files in the built ISO
|
||||||
|
NDolphinIntegration::CleanupQuickplayFiles(pProj);
|
||||||
|
|
||||||
CProgressDialog Dialog("Building ISO", false, true, this);
|
CProgressDialog Dialog("Building ISO", false, true, this);
|
||||||
Dialog.DisallowCanceling();
|
Dialog.DisallowCanceling();
|
||||||
bool Success;
|
bool Success;
|
||||||
|
|
|
@ -204,11 +204,17 @@ void CSceneViewport::CreateContextMenu()
|
||||||
mpUnhideAllAction = new QAction("Unhide all", this);
|
mpUnhideAllAction = new QAction("Unhide all", this);
|
||||||
connect(mpUnhideAllAction, SIGNAL(triggered()), this, SLOT(OnUnhideAll()));
|
connect(mpUnhideAllAction, SIGNAL(triggered()), this, SLOT(OnUnhideAll()));
|
||||||
|
|
||||||
|
mpPlayFromHereSeparator = new QAction(this);
|
||||||
|
mpPlayFromHereSeparator->setSeparator(true);
|
||||||
|
|
||||||
|
mpPlayFromHereAction = new QAction("Play from here");
|
||||||
|
connect(mpPlayFromHereAction, SIGNAL(triggered()), this, SLOT(OnPlayFromHere()));
|
||||||
|
|
||||||
QList<QAction*> Actions;
|
QList<QAction*> Actions;
|
||||||
Actions << mpToggleSelectAction
|
Actions << mpToggleSelectAction
|
||||||
<< mpHideSelectionSeparator << mpHideSelectionAction << mpHideUnselectedAction
|
<< mpHideSelectionSeparator << mpHideSelectionAction << mpHideUnselectedAction
|
||||||
<< mpHideHoverSeparator << mpHideHoverNodeAction << mpHideHoverTypeAction << mpHideHoverLayerAction
|
<< mpHideHoverSeparator << mpHideHoverNodeAction << mpHideHoverTypeAction << mpHideHoverLayerAction
|
||||||
<< mpUnhideSeparator << mpUnhideAllAction;
|
<< mpUnhideSeparator << mpUnhideAllAction << mpPlayFromHereSeparator << mpPlayFromHereAction;
|
||||||
|
|
||||||
mpContextMenu->addActions(Actions);
|
mpContextMenu->addActions(Actions);
|
||||||
|
|
||||||
|
@ -227,7 +233,6 @@ void CSceneViewport::CreateContextMenu()
|
||||||
QList<QAction*> SelectConnectedActions;
|
QList<QAction*> SelectConnectedActions;
|
||||||
SelectConnectedActions << mpSelectConnectedOutgoingAction << mpSelectConnectedIncomingAction << mpSelectConnectedAllAction;
|
SelectConnectedActions << mpSelectConnectedOutgoingAction << mpSelectConnectedIncomingAction << mpSelectConnectedAllAction;
|
||||||
mpSelectConnectedMenu->addActions(SelectConnectedActions);
|
mpSelectConnectedMenu->addActions(SelectConnectedActions);
|
||||||
|
|
||||||
mpContextMenu->insertMenu(mpHideSelectionSeparator, mpSelectConnectedMenu);
|
mpContextMenu->insertMenu(mpHideSelectionSeparator, mpSelectConnectedMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,6 +348,9 @@ void CSceneViewport::ContextMenu(QContextMenuEvent *pEvent)
|
||||||
bool HasSelection = mpEditor->HasSelection();
|
bool HasSelection = mpEditor->HasSelection();
|
||||||
bool IsScriptNode = (mpHoverNode && mpHoverNode->NodeType() == ENodeType::Script);
|
bool IsScriptNode = (mpHoverNode && mpHoverNode->NodeType() == ENodeType::Script);
|
||||||
|
|
||||||
|
CWorldEditor* pOwnerWorldEd = qobject_cast<CWorldEditor*>(mpEditor);
|
||||||
|
bool QuickplayEnabled = (pOwnerWorldEd && pOwnerWorldEd->IsQuickplayEnabled());
|
||||||
|
|
||||||
mpToggleSelectAction->setVisible(HasHoverNode);
|
mpToggleSelectAction->setVisible(HasHoverNode);
|
||||||
mpSelectConnectedMenu->menuAction()->setVisible(IsScriptNode);
|
mpSelectConnectedMenu->menuAction()->setVisible(IsScriptNode);
|
||||||
mpHideSelectionSeparator->setVisible(HasHoverNode);
|
mpHideSelectionSeparator->setVisible(HasHoverNode);
|
||||||
|
@ -353,6 +361,8 @@ void CSceneViewport::ContextMenu(QContextMenuEvent *pEvent)
|
||||||
mpHideHoverTypeAction->setVisible(IsScriptNode);
|
mpHideHoverTypeAction->setVisible(IsScriptNode);
|
||||||
mpHideHoverLayerAction->setVisible(IsScriptNode);
|
mpHideHoverLayerAction->setVisible(IsScriptNode);
|
||||||
mpUnhideSeparator->setVisible(HasHoverNode);
|
mpUnhideSeparator->setVisible(HasHoverNode);
|
||||||
|
mpPlayFromHereSeparator->setVisible(QuickplayEnabled);
|
||||||
|
mpPlayFromHereAction->setVisible(QuickplayEnabled);
|
||||||
|
|
||||||
if (HasHoverNode)
|
if (HasHoverNode)
|
||||||
{
|
{
|
||||||
|
@ -379,6 +389,7 @@ void CSceneViewport::ContextMenu(QContextMenuEvent *pEvent)
|
||||||
|
|
||||||
// Show menu
|
// Show menu
|
||||||
mpMenuNode = mpHoverNode;
|
mpMenuNode = mpHoverNode;
|
||||||
|
mMenuPoint = mHoverPoint;
|
||||||
mpContextMenu->exec(pEvent->pos());
|
mpContextMenu->exec(pEvent->pos());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -501,6 +512,21 @@ void CSceneViewport::OnUnhideAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CSceneViewport::OnPlayFromHere()
|
||||||
|
{
|
||||||
|
CWorldEditor* pOwnerWorldEd = qobject_cast<CWorldEditor*>(mpEditor);
|
||||||
|
ASSERT( pOwnerWorldEd != nullptr );
|
||||||
|
|
||||||
|
if (mpMenuNode)
|
||||||
|
{
|
||||||
|
pOwnerWorldEd->LaunchQuickplayFromLocation(mMenuPoint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pOwnerWorldEd->LaunchQuickplay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CSceneViewport::OnContextMenuClose()
|
void CSceneViewport::OnContextMenuClose()
|
||||||
{
|
{
|
||||||
mpContextMenu = nullptr;
|
mpContextMenu = nullptr;
|
||||||
|
|
|
@ -34,7 +34,10 @@ class CSceneViewport : public CBasicViewport
|
||||||
QAction *mpHideHoverLayerAction;
|
QAction *mpHideHoverLayerAction;
|
||||||
QAction *mpUnhideSeparator;
|
QAction *mpUnhideSeparator;
|
||||||
QAction *mpUnhideAllAction;
|
QAction *mpUnhideAllAction;
|
||||||
|
QAction *mpPlayFromHereSeparator;
|
||||||
|
QAction *mpPlayFromHereAction;
|
||||||
CSceneNode *mpMenuNode;
|
CSceneNode *mpMenuNode;
|
||||||
|
CVector3f mMenuPoint;
|
||||||
|
|
||||||
QMenu *mpSelectConnectedMenu;
|
QMenu *mpSelectConnectedMenu;
|
||||||
QAction *mpSelectConnectedOutgoingAction;
|
QAction *mpSelectConnectedOutgoingAction;
|
||||||
|
@ -97,6 +100,7 @@ protected slots:
|
||||||
void OnHideType();
|
void OnHideType();
|
||||||
void OnHideLayer();
|
void OnHideLayer();
|
||||||
void OnUnhideAll();
|
void OnUnhideAll();
|
||||||
|
void OnPlayFromHere();
|
||||||
void OnContextMenuClose();
|
void OnContextMenuClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -206,7 +206,8 @@ HEADERS += \
|
||||||
Undo/ICreateDeleteResourceCommand.h \
|
Undo/ICreateDeleteResourceCommand.h \
|
||||||
Undo/CSaveStoreCommand.h \
|
Undo/CSaveStoreCommand.h \
|
||||||
CollisionEditor/CCollisionEditor.h \
|
CollisionEditor/CCollisionEditor.h \
|
||||||
CollisionEditor/CCollisionEditorViewport.h
|
CollisionEditor/CCollisionEditorViewport.h \
|
||||||
|
NDolphinIntegration.h
|
||||||
|
|
||||||
# Source Files
|
# Source Files
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
|
@ -284,7 +285,8 @@ SOURCES += \
|
||||||
CTweakEditor.cpp \
|
CTweakEditor.cpp \
|
||||||
ScanEditor/CScanEditor.cpp \
|
ScanEditor/CScanEditor.cpp \
|
||||||
CollisionEditor/CCollisionEditor.cpp \
|
CollisionEditor/CCollisionEditor.cpp \
|
||||||
CollisionEditor/CCollisionEditorViewport.cpp
|
CollisionEditor/CCollisionEditorViewport.cpp \
|
||||||
|
NDolphinIntegration.cpp
|
||||||
|
|
||||||
# UI Files
|
# UI Files
|
||||||
FORMS += \
|
FORMS += \
|
||||||
|
|
|
@ -0,0 +1,253 @@
|
||||||
|
#include "NDolphinIntegration.h"
|
||||||
|
#include "Editor/UICommon.h"
|
||||||
|
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QRegExp>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QSettings>
|
||||||
|
|
||||||
|
namespace NDolphinIntegration
|
||||||
|
{
|
||||||
|
|
||||||
|
/** Constants */
|
||||||
|
const char* const gkDolphinPathSetting = "Quickplay/DolphinPath";
|
||||||
|
const char* const gkRelFileName = "EditorQuickplay.rel";
|
||||||
|
const char* const gkParameterFile = "dbgconfig";
|
||||||
|
const char* const gkDolPath = "sys/main.dol";
|
||||||
|
const char* const gkDolBackupPath = "sys/main.original.dol";
|
||||||
|
|
||||||
|
/** The user's path to the Dolphin exe */
|
||||||
|
QString gDolphinPath;
|
||||||
|
|
||||||
|
/** The current Dolphin quickplay process */
|
||||||
|
QProcess gDolphinProcess;
|
||||||
|
|
||||||
|
/** 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)
|
||||||
|
{
|
||||||
|
disconnect(this);
|
||||||
|
connect(pProcess, SIGNAL(finished(int)), this, SLOT(OnQuickplayFinished(int)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CQuickplayRelay::OnQuickplayFinished(int ReturnCode)
|
||||||
|
{
|
||||||
|
debugf("Quickplay session finished.");
|
||||||
|
disconnect(this);
|
||||||
|
CleanupQuickplayFiles(gpQuickplayProject);
|
||||||
|
gpQuickplayProject = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
CQuickplayRelay gQuickplayRelay;
|
||||||
|
|
||||||
|
/** Attempt to launch quickplay based on the current editor state. */
|
||||||
|
EQuickplayLaunchResult LaunchQuickplay(QWidget* pParentWidget,
|
||||||
|
CGameProject* pProject,
|
||||||
|
const SQuickplayParameters& kParms)
|
||||||
|
{
|
||||||
|
debugf("Launching quickplay...");
|
||||||
|
|
||||||
|
// Check if quickplay is supported for this project
|
||||||
|
TString QuickplayDir = "../resources/quickplay" / ::GetGameShortName(pProject->Game());
|
||||||
|
TString BuildString = "v" + TString::FromFloat(pProject->BuildVersion());
|
||||||
|
TString RelFile = QuickplayDir / BuildString + ".rel";
|
||||||
|
TString PatchFile = QuickplayDir / BuildString + ".bin";
|
||||||
|
|
||||||
|
if (!FileUtil::Exists(RelFile) || !FileUtil::Exists(PatchFile))
|
||||||
|
{
|
||||||
|
warnf("Quickplay launch failed! Quickplay is not supported for this project.");
|
||||||
|
return EQuickplayLaunchResult::UnsupportedForProject;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if quickplay is already running
|
||||||
|
if (gDolphinProcess.state() != QProcess::NotRunning)
|
||||||
|
{
|
||||||
|
if (UICommon::YesNoQuestion(pParentWidget, "Quickplay",
|
||||||
|
"Quickplay is already running. Close the existing session and relaunch?"))
|
||||||
|
{
|
||||||
|
KillQuickplay();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
warnf("Quickplay launch failed! User is already running quickplay.");
|
||||||
|
return EQuickplayLaunchResult::AlreadyRunning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have a path to Dolphin
|
||||||
|
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();
|
||||||
|
|
||||||
|
if (Path.isEmpty())
|
||||||
|
{
|
||||||
|
// Allow the user to select the Dolphin exe.
|
||||||
|
Path = UICommon::OpenFileDialog(pParentWidget, "Open Dolphin", "Dolphin.exe");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bGotDolphin = (!Path.isEmpty() && SetDolphinPath(pParentWidget, Path, true));
|
||||||
|
|
||||||
|
if (!bGotDolphin)
|
||||||
|
{
|
||||||
|
// We don't have Dolphin
|
||||||
|
warnf("Quickplay launch failed! Dolphin is not configured.");
|
||||||
|
return EQuickplayLaunchResult::DolphinNotSet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All good. Perform initialization tasks. Start by creating the patched dol.
|
||||||
|
TString DiscSys = pProject->DiscDir(false) / "sys";
|
||||||
|
std::vector<uint8> DolData;
|
||||||
|
std::vector<uint8> PatchData;
|
||||||
|
|
||||||
|
bool bLoadedDol = FileUtil::LoadFileToBuffer(DiscSys / "main.dol", DolData);
|
||||||
|
bool bLoadedPatch = FileUtil::LoadFileToBuffer(PatchFile, PatchData);
|
||||||
|
|
||||||
|
if (!bLoadedDol || !bLoadedPatch)
|
||||||
|
{
|
||||||
|
warnf("Quickplay launch failed! Failed to load %s into memory.",
|
||||||
|
bLoadedDol ? "patch data" : "game DOL");
|
||||||
|
|
||||||
|
return EQuickplayLaunchResult::Failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back up the original dol.
|
||||||
|
// Note that Dolphin requires the dol to be located at sys/main.dol, which
|
||||||
|
// is why this is needed as a workaround.
|
||||||
|
TString DolPath = DiscSys / "main.dol";
|
||||||
|
TString DolBackupPath = DiscSys / "main.original.dol";
|
||||||
|
|
||||||
|
// Note we want to make sure there is no existing backup before copying.
|
||||||
|
// There may be a case where some quickplay files were left over from an old
|
||||||
|
// session that didn't terminate correctly. In this case, main.dol will be
|
||||||
|
// the quickplay dol, and this operation would overwrite the original dol.
|
||||||
|
if (!FileUtil::Exists(DolBackupPath))
|
||||||
|
{
|
||||||
|
FileUtil::CopyFile(DolPath, DolBackupPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the patch data to the end of the dol
|
||||||
|
uint32 AlignedDolSize = ALIGN(DolData.size(), 32);
|
||||||
|
uint32 AlignedPatchSize = ALIGN(PatchData.size(), 32);
|
||||||
|
uint32 PatchOffset = AlignedDolSize;
|
||||||
|
uint32 PatchSize = AlignedPatchSize;
|
||||||
|
DolData.resize(PatchOffset + PatchSize);
|
||||||
|
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 hook over the call to LCEnable, which is at offset 0x1D64.
|
||||||
|
CMemoryOutStream Mem(DolData.data(), DolData.size(), EEndian::BigEndian);
|
||||||
|
Mem.GoTo(0x18);
|
||||||
|
Mem.WriteLong(PatchOffset);
|
||||||
|
Mem.GoTo(0x60);
|
||||||
|
Mem.WriteLong(0x80002600);
|
||||||
|
Mem.GoTo(0xA8);
|
||||||
|
Mem.WriteLong(PatchSize);
|
||||||
|
Mem.GoTo(0x1D64);
|
||||||
|
Mem.WriteLong(0x4BFFD80D); // this patches in a call to the quickplay bootstrap during game boot process
|
||||||
|
|
||||||
|
if (!FileUtil::SaveBufferToFile(DolPath, DolData))
|
||||||
|
{
|
||||||
|
warnf("Quickplay launch failed! Failed to write patched DOL.");
|
||||||
|
return EQuickplayLaunchResult::Failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write other disc files that are needed by quickplay
|
||||||
|
TString FSTRoot = pProject->DiscFilesystemRoot(false);
|
||||||
|
TString FSTRelPath = FSTRoot / gkRelFileName;
|
||||||
|
TString FSTParmPath = FSTRoot / gkParameterFile;
|
||||||
|
FileUtil::DeleteFile(FSTRelPath);
|
||||||
|
FileUtil::DeleteFile(FSTParmPath);
|
||||||
|
FileUtil::CopyFile(RelFile, FSTRelPath);
|
||||||
|
kParms.Write(FSTParmPath);
|
||||||
|
|
||||||
|
// We're good to go - launch the quickplay process
|
||||||
|
gDolphinProcess.start(gDolphinPath, QStringList() << TO_QSTRING(DolPath));
|
||||||
|
gDolphinProcess.waitForStarted();
|
||||||
|
|
||||||
|
if (gDolphinProcess.state() != QProcess::Running)
|
||||||
|
{
|
||||||
|
warnf("Quickplay launch failed! Process did not start correctly.");
|
||||||
|
return EQuickplayLaunchResult::Failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
gQuickplayRelay.TrackProcess(&gDolphinProcess);
|
||||||
|
gpQuickplayProject = pProject;
|
||||||
|
debugf("Quickplay session started.");
|
||||||
|
return EQuickplayLaunchResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return whether quickplay is supported for the given project */
|
||||||
|
bool IsQuickplaySupported(CGameProject* pProject)
|
||||||
|
{
|
||||||
|
// Quickplay is supported if there is a quickplay module & patch in the resources folder
|
||||||
|
TString QuickplayDir = "../resources/quickplay" / ::GetGameShortName(pProject->Game());
|
||||||
|
TString BuildString = "v" + TString::FromFloat(pProject->BuildVersion());
|
||||||
|
TString RelFile = QuickplayDir / BuildString + ".rel";
|
||||||
|
TString PatchFile = QuickplayDir / BuildString + ".bin";
|
||||||
|
return FileUtil::Exists(RelFile) && FileUtil::Exists(PatchFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Kill the current quickplay process, if it exists. */
|
||||||
|
void KillQuickplay()
|
||||||
|
{
|
||||||
|
debugf("Killing quickplay.");
|
||||||
|
gDolphinProcess.close();
|
||||||
|
CleanupQuickplayFiles(gpQuickplayProject);
|
||||||
|
gpQuickplayProject = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clean up any quickplay related file data from the project disc files. */
|
||||||
|
void CleanupQuickplayFiles(CGameProject* pProject)
|
||||||
|
{
|
||||||
|
if( pProject )
|
||||||
|
{
|
||||||
|
TString DiscSys = pProject->DiscDir(false) / "sys";
|
||||||
|
TString DolPath = DiscSys / "main.dol";
|
||||||
|
TString BackupDolPath = DiscSys / "main.original.dol";
|
||||||
|
|
||||||
|
if (FileUtil::Exists(BackupDolPath))
|
||||||
|
{
|
||||||
|
FileUtil::DeleteFile(DolPath);
|
||||||
|
FileUtil::MoveFile(BackupDolPath, DolPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
TString FSTRoot = pProject->DiscFilesystemRoot(false);
|
||||||
|
FileUtil::DeleteFile(FSTRoot / gkParameterFile);
|
||||||
|
FileUtil::DeleteFile(FSTRoot / gkRelFileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the user path to Dolphin */
|
||||||
|
bool SetDolphinPath(QWidget* pParentWidget, const QString& kDolphinPath, bool bSilent /*= false*/)
|
||||||
|
{
|
||||||
|
// Validate if this is a Dolphin build
|
||||||
|
//@todo Is there a way to check if this exe is Dolphin specifically?
|
||||||
|
//@todo Validate the build version to make sure the build supports quickplay? Necessary?
|
||||||
|
QFileInfo DolphinFile(kDolphinPath);
|
||||||
|
|
||||||
|
if (!DolphinFile.exists() || DolphinFile.suffix() != "exe")
|
||||||
|
{
|
||||||
|
if (!bSilent)
|
||||||
|
{
|
||||||
|
UICommon::ErrorMsg(pParentWidget, "The selected file is not a Dolphin exe!");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build is legit, stash it
|
||||||
|
QSettings Settings;
|
||||||
|
Settings.setValue(gkDolphinPathSetting, kDolphinPath);
|
||||||
|
|
||||||
|
gDolphinPath = kDolphinPath;
|
||||||
|
debugf("Setting Dolphin path to %s.", *TO_TSTRING(kDolphinPath));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
#ifndef NDOLPHININTEGRATION_H
|
||||||
|
#define NDOLPHININTEGRATION_H
|
||||||
|
|
||||||
|
#include <Common/Common.h>
|
||||||
|
#include <Common/FileIO/IOutputStream.h>
|
||||||
|
#include <Common/Math/CTransform4f.h>
|
||||||
|
#include <Core/GameProject/CGameProject.h>
|
||||||
|
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
// IMPORTANT NOTE: Most values, enums, and structs declared in this file
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
Success = 0,
|
||||||
|
AlreadyRunning = -1,
|
||||||
|
DolphinNotSet = -2,
|
||||||
|
UnsupportedForProject = -3,
|
||||||
|
Failure = -10
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Flags allowing for quickplay features to be toggled on/off */
|
||||||
|
enum class EQuickplayFeature
|
||||||
|
{
|
||||||
|
/** On boot, automatically load the area specified by WorldID and AreaID */
|
||||||
|
JumpToArea = 0x00000001,
|
||||||
|
/** Spawn the player in the location specified by SpawnTransform */
|
||||||
|
SetSpawnPosition = 0x00000002,
|
||||||
|
};
|
||||||
|
DECLARE_FLAGS_ENUMCLASS(EQuickplayFeature, FQuickplayFeatures)
|
||||||
|
|
||||||
|
/** Full parameter set for quickplay that gets passed to the game. */
|
||||||
|
struct SQuickplayParameters
|
||||||
|
{
|
||||||
|
/** Magic/Version */
|
||||||
|
static const uint32 kParmsMagic = 0x00BADB01;
|
||||||
|
static const uint32 kParmsVersion = 1;
|
||||||
|
|
||||||
|
/** Flags indicating which features are enabled. */
|
||||||
|
FQuickplayFeatures Features;
|
||||||
|
/** Asset ID of the world/area to load on boot (if JumpToArea is set). */
|
||||||
|
uint32 BootWorldAssetID;
|
||||||
|
uint32 BootAreaAssetID;
|
||||||
|
/** Location to spawn the player at when the game initially starts up. */
|
||||||
|
CTransform4f SpawnTransform;
|
||||||
|
|
||||||
|
/** Serialize to disk */
|
||||||
|
void Write(const TString& kPath) const
|
||||||
|
{
|
||||||
|
CFileOutStream Stream(kPath, EEndian::BigEndian);
|
||||||
|
ASSERT( Stream.IsValid() );
|
||||||
|
|
||||||
|
// Magic/Version
|
||||||
|
Stream.WriteLong(kParmsMagic);
|
||||||
|
Stream.WriteLong(kParmsVersion);
|
||||||
|
|
||||||
|
// Parameters
|
||||||
|
Stream.WriteLong( Features.ToInt32() );
|
||||||
|
Stream.WriteLong( BootWorldAssetID );
|
||||||
|
Stream.WriteLong( BootAreaAssetID );
|
||||||
|
SpawnTransform.Write( Stream );
|
||||||
|
|
||||||
|
Stream.Close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Minimal relay class 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Attempt to launch quickplay based on the current editor state. */
|
||||||
|
EQuickplayLaunchResult LaunchQuickplay(QWidget* pParentWidget,
|
||||||
|
CGameProject* pProject,
|
||||||
|
const SQuickplayParameters& kParms);
|
||||||
|
|
||||||
|
/** Return whether quickplay is supported for the given project */
|
||||||
|
bool IsQuickplaySupported(CGameProject* pProject);
|
||||||
|
|
||||||
|
/** Kill the current quickplay process, if it exists. */
|
||||||
|
void KillQuickplay();
|
||||||
|
|
||||||
|
/** Clean up any quickplay related file data from the project disc files. */
|
||||||
|
void CleanupQuickplayFiles(CGameProject* pProject);
|
||||||
|
|
||||||
|
/** Set the user path to Dolphin. Returns true if succeeded. */
|
||||||
|
bool SetDolphinPath(QWidget* pParentWidget,
|
||||||
|
const QString& kDolphinPath,
|
||||||
|
bool bSilent = false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CQUICKPLAYCONTROLLER_H
|
|
@ -31,6 +31,7 @@
|
||||||
#include <QFontMetrics>
|
#include <QFontMetrics>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
#include <QToolButton>
|
||||||
|
|
||||||
CWorldEditor::CWorldEditor(QWidget *parent)
|
CWorldEditor::CWorldEditor(QWidget *parent)
|
||||||
: INodeEditor(parent)
|
: INodeEditor(parent)
|
||||||
|
@ -118,6 +119,15 @@ CWorldEditor::CWorldEditor(QWidget *parent)
|
||||||
|
|
||||||
mpCollisionDialog = new CCollisionRenderSettingsDialog(this, this);
|
mpCollisionDialog = new CCollisionRenderSettingsDialog(this, this);
|
||||||
|
|
||||||
|
// Quickplay buttons
|
||||||
|
QToolButton* pQuickplayButton = new QToolButton(this);
|
||||||
|
pQuickplayButton->setIcon( QIcon(":/icons/Play_32px.png") );
|
||||||
|
mpQuickplayAction = ui->MainToolBar->addWidget(pQuickplayButton);
|
||||||
|
mpQuickplayAction->setVisible(false);
|
||||||
|
mpQuickplayAction->setEnabled(false);
|
||||||
|
|
||||||
|
connect(pQuickplayButton, SIGNAL(pressed()), this, SLOT(LaunchQuickplay()));
|
||||||
|
|
||||||
// "Open Recent" menu
|
// "Open Recent" menu
|
||||||
mpOpenRecentMenu = new QMenu(this);
|
mpOpenRecentMenu = new QMenu(this);
|
||||||
ui->ActionOpenRecent->setMenu(mpOpenRecentMenu);
|
ui->ActionOpenRecent->setMenu(mpOpenRecentMenu);
|
||||||
|
@ -221,6 +231,7 @@ bool CWorldEditor::CloseWorld()
|
||||||
UndoStack().clear();
|
UndoStack().clear();
|
||||||
mpCollisionDialog->close();
|
mpCollisionDialog->close();
|
||||||
mpLinkDialog->close();
|
mpLinkDialog->close();
|
||||||
|
mpQuickplayAction->setEnabled(false);
|
||||||
|
|
||||||
mpArea = nullptr;
|
mpArea = nullptr;
|
||||||
mpWorld = nullptr;
|
mpWorld = nullptr;
|
||||||
|
@ -289,6 +300,7 @@ bool CWorldEditor::SetArea(CWorld *pWorld, int AreaIndex)
|
||||||
ui->ActionSave->setEnabled(true);
|
ui->ActionSave->setEnabled(true);
|
||||||
ui->ActionSaveAndRepack->setEnabled(true);
|
ui->ActionSaveAndRepack->setEnabled(true);
|
||||||
ui->ActionEditLayers->setEnabled(true);
|
ui->ActionEditLayers->setEnabled(true);
|
||||||
|
mpQuickplayAction->setEnabled(true);
|
||||||
|
|
||||||
// Emit signals
|
// Emit signals
|
||||||
emit MapChanged(mpWorld, mpArea);
|
emit MapChanged(mpWorld, mpArea);
|
||||||
|
@ -313,6 +325,11 @@ bool CWorldEditor::HasAnyScriptNodesSelected() const
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CWorldEditor::IsQuickplayEnabled() const
|
||||||
|
{
|
||||||
|
return mpQuickplayAction->isVisible() && mpQuickplayAction->isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
CSceneViewport* CWorldEditor::Viewport() const
|
CSceneViewport* CWorldEditor::Viewport() const
|
||||||
{
|
{
|
||||||
return ui->MainViewport;
|
return ui->MainViewport;
|
||||||
|
@ -509,6 +526,7 @@ void CWorldEditor::OnActiveProjectChanged(CGameProject *pProj)
|
||||||
ui->ActionProjectSettings->setEnabled( pProj != nullptr );
|
ui->ActionProjectSettings->setEnabled( pProj != nullptr );
|
||||||
ui->ActionCloseProject->setEnabled( pProj != nullptr );
|
ui->ActionCloseProject->setEnabled( pProj != nullptr );
|
||||||
mpPoiMapAction->setVisible( pProj != nullptr && pProj->Game() >= EGame::EchoesDemo && pProj->Game() <= EGame::Corruption );
|
mpPoiMapAction->setVisible( pProj != nullptr && pProj->Game() >= EGame::EchoesDemo && pProj->Game() <= EGame::Corruption );
|
||||||
|
mpQuickplayAction->setVisible( pProj != nullptr && NDolphinIntegration::IsQuickplaySupported(pProj) );
|
||||||
ResetCamera();
|
ResetCamera();
|
||||||
UpdateWindowTitle();
|
UpdateWindowTitle();
|
||||||
|
|
||||||
|
@ -929,6 +947,28 @@ void CWorldEditor::UpdateNewLinkLine()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CWorldEditor::LaunchQuickplay()
|
||||||
|
{
|
||||||
|
CVector3f CameraPosition = Viewport()->Camera().Position();
|
||||||
|
LaunchQuickplayFromLocation(CameraPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CWorldEditor::LaunchQuickplayFromLocation(CVector3f Location)
|
||||||
|
{
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
// ************ PROTECTED ************
|
// ************ PROTECTED ************
|
||||||
QAction* CWorldEditor::AddEditModeButton(QIcon Icon, QString ToolTip, EWorldEditorMode Mode)
|
QAction* CWorldEditor::AddEditModeButton(QIcon Icon, QString ToolTip, EWorldEditorMode Mode)
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "CScriptEditSidebar.h"
|
#include "CScriptEditSidebar.h"
|
||||||
#include "CTweakEditor.h"
|
#include "CTweakEditor.h"
|
||||||
#include "CWorldInfoSidebar.h"
|
#include "CWorldInfoSidebar.h"
|
||||||
|
#include "NDolphinIntegration.h"
|
||||||
#include "Editor/INodeEditor.h"
|
#include "Editor/INodeEditor.h"
|
||||||
#include "Editor/CGeneratePropertyNamesDialog.h"
|
#include "Editor/CGeneratePropertyNamesDialog.h"
|
||||||
#include "Editor/CGizmo.h"
|
#include "Editor/CGizmo.h"
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
#include <QToolButton>
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class CWorldEditor;
|
class CWorldEditor;
|
||||||
|
@ -63,6 +65,10 @@ class CWorldEditor : public INodeEditor
|
||||||
CScriptObject* mpNewLinkSender;
|
CScriptObject* mpNewLinkSender;
|
||||||
CScriptObject* mpNewLinkReceiver;
|
CScriptObject* mpNewLinkReceiver;
|
||||||
|
|
||||||
|
// Quickplay
|
||||||
|
QAction* mpQuickplayAction;
|
||||||
|
NDolphinIntegration::SQuickplayParameters mQuickplayParms;
|
||||||
|
|
||||||
// Sidebars
|
// Sidebars
|
||||||
QVBoxLayout* mpRightSidebarLayout;
|
QVBoxLayout* mpRightSidebarLayout;
|
||||||
CWorldEditorSidebar* mpCurSidebar;
|
CWorldEditorSidebar* mpCurSidebar;
|
||||||
|
@ -81,6 +87,7 @@ public:
|
||||||
bool SetArea(CWorld *pWorld, int AreaIndex);
|
bool SetArea(CWorld *pWorld, int AreaIndex);
|
||||||
void ResetCamera();
|
void ResetCamera();
|
||||||
bool HasAnyScriptNodesSelected() const;
|
bool HasAnyScriptNodesSelected() const;
|
||||||
|
bool IsQuickplayEnabled() const;
|
||||||
|
|
||||||
inline CWorld* ActiveWorld() const { return mpWorld; }
|
inline CWorld* ActiveWorld() const { return mpWorld; }
|
||||||
inline CGameArea* ActiveArea() const { return mpArea; }
|
inline CGameArea* ActiveArea() const { return mpArea; }
|
||||||
|
@ -128,6 +135,9 @@ public slots:
|
||||||
void UpdateCursor();
|
void UpdateCursor();
|
||||||
void UpdateNewLinkLine();
|
void UpdateNewLinkLine();
|
||||||
|
|
||||||
|
void LaunchQuickplay();
|
||||||
|
void LaunchQuickplayFromLocation(CVector3f Location);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QAction* AddEditModeButton(QIcon Icon, QString ToolTip, EWorldEditorMode Mode);
|
QAction* AddEditModeButton(QIcon Icon, QString ToolTip, EWorldEditorMode Mode);
|
||||||
void SetSidebar(CWorldEditorSidebar *pSidebar);
|
void SetSidebar(CWorldEditorSidebar *pSidebar);
|
||||||
|
|
Loading…
Reference in New Issue