Added project overview dialog with placeholder UI to allow loading worlds through a game project

This commit is contained in:
parax0 2016-07-08 01:10:07 -06:00
parent 12bd4eff90
commit 08dcfe5e5a
13 changed files with 404 additions and 74 deletions

View File

@ -460,7 +460,7 @@ void CGameExporter::ExportWorlds()
SResourceInstance *pInst = FindResourceInstance(AreaID);
ASSERT(pInst != nullptr);
SetResourcePath(AreaID, AreaDir, HasInternalName ? InternalAreaName : GameAreaName);
SetResourcePath(AreaID, AreaDir, GameAreaName);
ExportResource(*pInst);
}
@ -488,7 +488,7 @@ void CGameExporter::ExportCookedResources()
{
SCOPED_TIMER(SaveResourceDatabase);
#if EXPORT_COOKED
mStore.SaveResourceDatabase(this->mExportDir.ToUTF8() + "ResourceDatabase.rdb");
mStore.SaveResourceDatabase(mpProject->ResourceDBPath(false).ToUTF8());
#endif
mpProject->Save();
}

View File

@ -3,17 +3,27 @@
#include <tinyxml2.h>
using namespace tinyxml2;
CGameProject *CGameProject::mspActiveProject = nullptr;
void CGameProject::Load()
CGameProject::~CGameProject()
{
TString ProjPath = ProjectPath().ToUTF8();
if (IsActive())
{
mspActiveProject = nullptr;
gpResourceStore->SetActiveProject(nullptr);
}
}
bool CGameProject::Load(const TWideString& rkPath)
{
TString ProjPath = rkPath.ToUTF8();
XMLDocument Doc;
Doc.LoadFile(*ProjPath);
if (Doc.Error())
{
Log::Error("Unable to open game project at " + ProjPath);
return;
return false;
}
XMLElement *pRoot = Doc.FirstChildElement("GameProject");
@ -29,7 +39,7 @@ void CGameProject::Load()
{
TString MissingElem = pProjName ? (pGame ? (pResDB ? "Packages" : "ResourceDB") : "Game") : "Name";
Log::Error("Unable to load game project at " + ProjPath + "; " + MissingElem + " element is missing");
return;
return false;
}
mProjectName = pProjName->GetText();
@ -41,7 +51,6 @@ void CGameProject::Load()
while (pPkgElem)
{
pPkgElem = pPkgElem->NextSiblingElement("Package");
TString Path = pPkgElem->Attribute("Path");
if (Path.IsEmpty())
@ -53,7 +62,13 @@ void CGameProject::Load()
pPackage->Load();
mPackages.push_back(pPackage);
}
pPkgElem = pPkgElem->NextSiblingElement("Package");
}
// All loaded!
mProjectRoot = rkPath.GetFileDirectory();
return true;
}
void CGameProject::Save()
@ -101,3 +116,33 @@ void CGameProject::Save()
if (Result != XML_SUCCESS)
Log::Error("Failed to save game project at: " + ProjPath);
}
void CGameProject::SetActive()
{
if (mspActiveProject != this)
{
mspActiveProject = this;
gpResourceStore->SetActiveProject(this);
}
}
void CGameProject::GetWorldList(std::list<CUniqueID>& rOut) const
{
for (u32 iPkg = 0; iPkg < mPackages.size(); iPkg++)
{
CPackage *pPkg = mPackages[iPkg];
for (u32 iCol = 0; iCol < pPkg->NumCollections(); iCol++)
{
CResourceCollection *pCol = pPkg->CollectionByIndex(iCol);
for (u32 iRes = 0; iRes < pCol->NumResources(); iRes++)
{
const SNamedResource& rkRes = pCol->ResourceByIndex(iRes);
if (rkRes.Type == "MLVL" && !rkRes.Name.EndsWith("NODEPEND"))
rOut.push_back(rkRes.ID);
}
}
}
}

View File

@ -24,7 +24,15 @@ class CGameProject
eVer_Max,
eVer_Current = eVer_Max - 1
};
static CGameProject *mspActiveProject;
public:
CGameProject()
: mGame(eUnknownVersion)
, mProjectName("Unnamed Project")
{}
CGameProject(const TWideString& rkProjRootDir)
: mGame(eUnknownVersion)
, mProjectName("Unnamed Project")
@ -32,8 +40,12 @@ public:
, mResourceDBPath(L"ResourceDB.rdb")
{}
void Load();
~CGameProject();
bool Load(const TWideString& rkPath);
void Save();
void SetActive();
void GetWorldList(std::list<CUniqueID>& rOut) const;
// Directory Handling
inline TWideString ProjectRoot() const { return mProjectRoot; }
@ -53,6 +65,9 @@ public:
inline void AddPackage(CPackage *pPackage) { mPackages.push_back(pPackage); }
inline EGame Game() const { return mGame; }
inline bool IsActive() const { return mspActiveProject == this; }
static inline CGameProject* ActiveProject() { return mspActiveProject; }
};
extern CGameProject *gpProject;

View File

@ -137,6 +137,7 @@ CResource* CResourceEntry::Load(IInputStream& rInput)
default: mpResource = new CResource(this); break;
}
mpStore->TrackLoadedResource(this);
return mpResource;
}

View File

@ -49,50 +49,21 @@ void CResourceStore::LoadResourceDatabase(const TString& rkPath)
while (pRes)
{
XMLElement *pChild = pRes->FirstChildElement();
XMLElement *pID = pRes->FirstChildElement("ID");
XMLElement *pType = pRes->FirstChildElement("Type");
XMLElement *pDir = pRes->FirstChildElement("FileDir");
XMLElement *pName = pRes->FirstChildElement("FileName");
bool HasID = false, HasType = false, HasDir = false, HasName = false;
CUniqueID ID;
EResType Type;
TWideString FileDir;
TWideString FileName;
while (pChild)
if (pID && pType && pDir && pName)
{
TString NodeName = pChild->Name();
if (NodeName == "ID")
{
ID = CUniqueID::FromString(pChild->GetText());
HasID = true;
}
else if (NodeName == "Type")
{
Type = CResource::ResTypeForExtension(pChild->GetText());
HasType = true;
ASSERT(Type != eInvalidResType);
}
else if (NodeName == "FileDir")
{
FileDir = pChild->GetText();
HasDir = true;
}
else if (NodeName == "FileName")
{
FileName = pChild->GetText();
HasName = true;
}
pChild = pChild->NextSiblingElement();
}
if (HasID && HasType && HasDir && HasName)
CUniqueID ID = CUniqueID::FromString(pID->GetText());
EResType Type = CResource::ResTypeForExtension(pType->GetText());
TWideString FileDir = pDir->GetText();
TWideString FileName = pName->GetText();
RegisterResource(ID, Type, FileDir, FileName);
}
else
Log::Error("Error reading " + rkPath + ": Resource entry " + TString::FromInt32(ResIndex, 0, 10) + " is missing one or more components");
Log::Error("Error reading " + rkPath + ": Resource entry " + TString::FromInt32(ResIndex, 0, 10) + " is missing one or more required components");
ResIndex++;
pRes = pRes->NextSiblingElement("Resource");
@ -292,12 +263,6 @@ CResource* CResourceStore::LoadResource(const CUniqueID& rkID, const CFourCC& rk
EResType Type = CResource::ResTypeForExtension(rkType);
CResourceEntry *pEntry = RegisterTransientResource(Type, rkID);
CResource *pRes = pEntry->Load(MemStream);
if (pRes)
{
mLoadedResources[rkID] = pEntry;
}
return pRes;
}
@ -322,8 +287,7 @@ CResource* CResourceStore::LoadResource(const CUniqueID& rkID, const CFourCC& rk
CFileInStream File(Path.ToStdString(), IOUtil::eBigEndian);
CResource *pRes = pEntry->Load(File);
if (pRes) mLoadedResources[rkID] = pEntry;
else DeleteResourceEntry(pEntry);
if (!pRes) DeleteResourceEntry(pEntry);
return pRes;
}
@ -371,15 +335,20 @@ CResource* CResourceStore::LoadResource(const TString& rkPath)
CResourceEntry *pEntry = RegisterTransientResource(Type, ID, Dir, Name);
CResource *pRes = pEntry->Load(File);
if (pRes) mLoadedResources[ID] = pEntry;
else DeleteResourceEntry(pEntry);
if (!pRes) DeleteResourceEntry(pEntry);
mTransientLoadDir = OldTransientDir;
return pRes;
}
void CResourceStore::TrackLoadedResource(CResourceEntry *pEntry)
{
ASSERT(pEntry->IsLoaded());
ASSERT(mLoadedResources.find(pEntry->ID()) == mLoadedResources.end());
mLoadedResources[pEntry->ID()] = pEntry;
}
CFourCC CResourceStore::ResourceTypeByID(const CUniqueID& rkID, const TStringList& rkPossibleTypes) const
{
if (!rkID.IsValid()) return eInvalidResType;

View File

@ -54,6 +54,7 @@ public:
CResource* LoadResource(const CUniqueID& rkID, const CFourCC& rkType);
CResource* LoadResource(const TString& rkPath);
void TrackLoadedResource(CResourceEntry *pEntry);
CFourCC ResourceTypeByID(const CUniqueID& rkID, const TStringList& rkPossibleTypes) const;
void DestroyUnreferencedResources();
bool DeleteResourceEntry(CResourceEntry *pEntry);

View File

@ -94,14 +94,7 @@ void CVirtualDirectory::AddChild(const TWideString &rkPath, CResourceEntry *pEnt
if (rkPath.IsEmpty())
{
if (pEntry)
{
#if !PUBLIC_RELEASE
for (u32 iRes = 0; iRes < mResources.size(); iRes++)
ASSERT(mResources[iRes] != pEntry);
#endif
mResources.push_back(pEntry);
}
}
else

View File

@ -0,0 +1,166 @@
#include "CProjectOverviewDialog.h"
#include "ui_CProjectOverviewDialog.h"
#include "UICommon.h"
#include <Common/AssertMacro.h>
#include <Core/GameProject/CGameExporter.h>
#include <QFileDialog>
#include <QMessageBox>
CProjectOverviewDialog::CProjectOverviewDialog(QWidget *pParent)
: QDialog(pParent)
, mpUI(new Ui::CProjectOverviewDialog)
, mpProject(nullptr)
{
mpUI->setupUi(this);
mpWorldEditor = new CWorldEditor();
connect(mpUI->OpenProjectButton, SIGNAL(clicked()), this, SLOT(OpenProject()));
connect(mpUI->ExportGameButton, SIGNAL(clicked()), this, SLOT(ExportGame()));
connect(mpUI->LoadWorldButton, SIGNAL(clicked()), this, SLOT(LoadWorld()));
connect(mpUI->LaunchEditorButton, SIGNAL(clicked()), this, SLOT(LaunchEditor()));
}
CProjectOverviewDialog::~CProjectOverviewDialog()
{
delete mpUI;
delete mpWorldEditor;
}
void CProjectOverviewDialog::OpenProject()
{
// Open project file
QString ProjPath = QFileDialog::getOpenFileName(this, "Open Project", "", "Game Project (*.prj)");
if (ProjPath.isEmpty()) return;
// Load project
TWideString Path = TO_TWIDESTRING(ProjPath);
CGameProject *pNewProj = new CGameProject(Path.GetFileDirectory());
if (pNewProj->Load(Path))
{
if (mpProject) delete mpProject;
mpProject = pNewProj;
mpProject->SetActive();
SetupWorldsList();
}
else
{
Log::Error("Failed to load project");
delete pNewProj;
}
}
void CProjectOverviewDialog::ExportGame()
{
// TEMP - hardcoded names for convenience. will remove later!
#define USE_HARDCODED_GAME_ROOT 0
#define USE_HARDCODED_EXPORT_DIR 1
#if USE_HARDCODED_GAME_ROOT
QString GameRoot = "E:/Unpacked/Metroid Prime";
#else
QString GameRoot = QFileDialog::getExistingDirectory(this, "Select game root directory");
if (GameRoot.isEmpty()) return;
#endif
#if USE_HARDCODED_EXPORT_DIR
QString ExportDir = "E:/Unpacked/ExportTest";
#else
QString ExportDir = QFileDialog::getExistingDirectory(this, "Select output export directory");
if (ExportDir.isEmpty()) return;
#endif
// Verify valid game root by checking if opening.bnr exists
TString OpeningBNR = TO_TSTRING(GameRoot) + "/opening.bnr";
if (!FileUtil::Exists(OpeningBNR.ToUTF16()))
{
QMessageBox::warning(this, "Error", "Error; this is not a valid game root directory!");
return;
}
CGameExporter Exporter(TO_TSTRING(GameRoot), TO_TSTRING(ExportDir));
Exporter.Export();
}
void CProjectOverviewDialog::SetupWorldsList()
{
ASSERT(mpProject != nullptr && mpProject->IsActive());
std::list<CUniqueID> WorldIDs;
mpProject->GetWorldList(WorldIDs);
mWorldEntries.clear();
mpUI->WorldsList->clear();
for (auto It = WorldIDs.begin(); It != WorldIDs.end(); It++)
{
CUniqueID ID = *It;
CResourceEntry *pEntry = gpResourceStore->FindEntry(ID);
if (!pEntry)
{
Log::Error("Couldn't find entry for world: " + ID.ToString());
continue;
}
mWorldEntries << pEntry;
mpUI->WorldsList->addItem(TO_QSTRING(pEntry->Name()));
}
mpUI->AreasGroupBox->setEnabled(false);
mpUI->LoadWorldButton->setEnabled(!mWorldEntries.isEmpty());
}
void CProjectOverviewDialog::LoadWorld()
{
// Find world
u32 WorldIdx = mpUI->WorldsList->currentRow();
CResourceEntry *pWorldEntry = mWorldEntries[WorldIdx];
mpWorld = pWorldEntry->Load();
mAreaEntries.clear();
mpUI->AreaComboBox->clear();
if (mpWorld)
{
for (u32 iArea = 0; iArea < mpWorld->NumAreas(); iArea++)
{
CResourceEntry *pAreaEntry = gpResourceStore->FindEntry( mpWorld->AreaResourceID(iArea) );
if (pAreaEntry)
{
mAreaEntries << pAreaEntry;
mpUI->AreaComboBox->addItem(TO_QSTRING(pAreaEntry->Name()));
}
}
}
mpUI->AreasGroupBox->setEnabled(true);
mpUI->AreaComboBox->setEnabled(!mAreaEntries.isEmpty());
mpUI->LaunchEditorButton->setEnabled(!mAreaEntries.isEmpty());
gpResourceStore->DestroyUnreferencedResources();
}
void CProjectOverviewDialog::LaunchEditor()
{
CGameArea *pOldArea = mpWorldEditor->ActiveArea();
(void) pOldArea;
// Load area
u32 AreaIdx = mpUI->AreaComboBox->currentIndex();
CResourceEntry *pAreaEntry = mAreaEntries[AreaIdx];
CGameArea *pArea = (CGameArea*) pAreaEntry->Load();
if (pArea)
{
pArea->SetWorldIndex(AreaIdx);
mpWorld->SetAreaLayerInfo(pArea);
mpWorldEditor->SetArea(mpWorld, pArea);
mpWorldEditor->showMaximized();
}
else
Log::Error("Failed to load area");
gpResourceStore->DestroyUnreferencedResources();
}

View File

@ -0,0 +1,37 @@
#ifndef CPROJECTOVERVIEWDIALOG_H
#define CPROJECTOVERVIEWDIALOG_H
#include "Editor/WorldEditor/CWorldEditor.h"
#include <Core/GameProject/CGameProject.h>
#include <Core/Resource/CWorld.h>
#include <QDialog>
namespace Ui {
class CProjectOverviewDialog;
}
class CProjectOverviewDialog : public QDialog
{
Q_OBJECT
Ui::CProjectOverviewDialog *mpUI;
CWorldEditor *mpWorldEditor;
CGameProject *mpProject;
QVector<CResourceEntry*> mWorldEntries;
QVector<CResourceEntry*> mAreaEntries;
TResPtr<CWorld> mpWorld;
public:
explicit CProjectOverviewDialog(QWidget *pParent = 0);
~CProjectOverviewDialog();
public slots:
void OpenProject();
void ExportGame();
void LoadWorld();
void LaunchEditor();
void SetupWorldsList();
};
#endif // CPROJECTOVERVIEWDIALOG_H

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CProjectOverviewDialog</class>
<widget class="QDialog" name="CProjectOverviewDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>268</width>
<height>367</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="OpenProjectButton">
<property name="text">
<string>Open Project</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="ExportGameButton">
<property name="text">
<string>Export Game</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="WorldsGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>2</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Worlds</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QListWidget" name="WorldsList"/>
</item>
<item>
<widget class="QPushButton" name="LoadWorldButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Load</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="AreasGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>3</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Areas</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QComboBox" name="AreaComboBox">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="LaunchEditorButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Launch World Editor</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -163,7 +163,8 @@ HEADERS += \
CharacterEditor/CCharacterEditorViewport.h \
CGridRenderable.h \
CharacterEditor/CSkeletonHierarchyModel.h \
CLineRenderable.h
CLineRenderable.h \
CProjectOverviewDialog.h
# Source Files
SOURCES += \
@ -223,7 +224,8 @@ SOURCES += \
CAboutDialog.cpp \
CharacterEditor/CCharacterEditor.cpp \
CharacterEditor/CCharacterEditorViewport.cpp \
CharacterEditor/CSkeletonHierarchyModel.cpp
CharacterEditor/CSkeletonHierarchyModel.cpp \
CProjectOverviewDialog.cpp
# UI Files
FORMS += \
@ -244,4 +246,5 @@ FORMS += \
WorldEditor/CSelectInstanceDialog.ui \
WorldEditor/CRepackInfoDialog.ui \
CAboutDialog.ui \
CharacterEditor/CCharacterEditor.ui
CharacterEditor/CCharacterEditor.ui \
CProjectOverviewDialog.ui

View File

@ -21,7 +21,7 @@ QMap<QString,QString> FilterMap = {
{ "DCLN", "Collision Mesh (*.DCLN)" },
{ "DGRP", "Dependency Group (*.DGRP)" },
{ "DPSC", "Decal (*.DPSC)" },
{ "DSP", "Music Track (*.DSP)" },
{ "DSP" , "Music Track (*.DSP)" },
{ "DUMB", "Binary Data Dump (*.DUMB)" },
{ "ELSC", "Electric Particle (*.ELSC)" },
{ "EVNT", "Animation Event Data (*.EVNT)" },
@ -36,14 +36,14 @@ QMap<QString,QString> FilterMap = {
{ "MREA", "Area (*.MREA)" },
{ "NTWK", "Tweaks (*.NTWK)" },
{ "PATH", "AI Navigation Mesh (*.PATH)" },
{ "PAK", "Pack File (*.pak)" },
{ "PAK" , "Package (*.pak)" },
{ "PART", "Particle (*.PART)" },
{ "SAVW", "World Save Data (*.SAVW)" },
{ "SCAN", "Scannable Object Info (*.SCAN)" },
{ "STRG", "String Table (*.STRG)" },
{ "STRM", "Audio Stream (*.STRM)" },
{ "SWHC", "Swoosh Particle (*.SWHC)" },
{ "THP", "Video (*.thp)" },
{ "THP" , "Video (*.thp)" },
{ "TXTR", "Texture (*.TXTR)" },
{ "WPSC", "Projectile (*.WPSC)" }
};

View File

@ -1,4 +1,5 @@
#include "CStartWindow.h"
#include "CProjectOverviewDialog.h"
#include <Common/Log.h>
#include <Core/Resource/Factory/CTemplateLoader.h>
@ -53,8 +54,8 @@ int main(int argc, char *argv[])
qApp->setPalette(DarkPalette);
// Execute application
CStartWindow StartWindow;
StartWindow.show();
CProjectOverviewDialog Dialog;
Dialog.show();
return App.exec();
}