Quickplay support

This commit is contained in:
Aruki 2019-04-06 15:53:05 -07:00
parent 3507be8e42
commit 7de85a5a2d
14 changed files with 465 additions and 5 deletions

2
externals/LibCommon vendored

@ -1 +1 @@
Subproject commit 685830ad4aa398baee88df2f010ef94f5915c03d Subproject commit b16f7e26a1120bffb8e474625cceefc3c48cef99

Binary file not shown.

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
}; };

View File

@ -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 += \

View File

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

View File

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

View File

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

View File

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