diff --git a/src/Core/GameProject/CGameExporter.cpp b/src/Core/GameProject/CGameExporter.cpp index df6b6464..b070ed46 100644 --- a/src/Core/GameProject/CGameExporter.cpp +++ b/src/Core/GameProject/CGameExporter.cpp @@ -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(); } diff --git a/src/Core/GameProject/CGameProject.cpp b/src/Core/GameProject/CGameProject.cpp index bfbf92e5..0a49804a 100644 --- a/src/Core/GameProject/CGameProject.cpp +++ b/src/Core/GameProject/CGameProject.cpp @@ -3,17 +3,27 @@ #include 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& 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); + } + } + } +} diff --git a/src/Core/GameProject/CGameProject.h b/src/Core/GameProject/CGameProject.h index 77466058..712d47cf 100644 --- a/src/Core/GameProject/CGameProject.h +++ b/src/Core/GameProject/CGameProject.h @@ -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& 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; diff --git a/src/Core/GameProject/CResourceEntry.cpp b/src/Core/GameProject/CResourceEntry.cpp index 594e6c37..43b1e16b 100644 --- a/src/Core/GameProject/CResourceEntry.cpp +++ b/src/Core/GameProject/CResourceEntry.cpp @@ -137,6 +137,7 @@ CResource* CResourceEntry::Load(IInputStream& rInput) default: mpResource = new CResource(this); break; } + mpStore->TrackLoadedResource(this); return mpResource; } diff --git a/src/Core/GameProject/CResourceStore.cpp b/src/Core/GameProject/CResourceStore.cpp index 785415ad..bc635ab4 100644 --- a/src/Core/GameProject/CResourceStore.cpp +++ b/src/Core/GameProject/CResourceStore.cpp @@ -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; diff --git a/src/Core/GameProject/CResourceStore.h b/src/Core/GameProject/CResourceStore.h index 0411eaa0..65f20e1e 100644 --- a/src/Core/GameProject/CResourceStore.h +++ b/src/Core/GameProject/CResourceStore.h @@ -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); diff --git a/src/Core/GameProject/CVirtualDirectory.cpp b/src/Core/GameProject/CVirtualDirectory.cpp index d330d904..f98f76b3 100644 --- a/src/Core/GameProject/CVirtualDirectory.cpp +++ b/src/Core/GameProject/CVirtualDirectory.cpp @@ -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 diff --git a/src/Editor/CProjectOverviewDialog.cpp b/src/Editor/CProjectOverviewDialog.cpp new file mode 100644 index 00000000..23dac610 --- /dev/null +++ b/src/Editor/CProjectOverviewDialog.cpp @@ -0,0 +1,166 @@ +#include "CProjectOverviewDialog.h" +#include "ui_CProjectOverviewDialog.h" +#include "UICommon.h" +#include +#include +#include +#include + +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 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(); +} diff --git a/src/Editor/CProjectOverviewDialog.h b/src/Editor/CProjectOverviewDialog.h new file mode 100644 index 00000000..5c48ff3c --- /dev/null +++ b/src/Editor/CProjectOverviewDialog.h @@ -0,0 +1,37 @@ +#ifndef CPROJECTOVERVIEWDIALOG_H +#define CPROJECTOVERVIEWDIALOG_H + +#include "Editor/WorldEditor/CWorldEditor.h" +#include +#include +#include + +namespace Ui { +class CProjectOverviewDialog; +} + +class CProjectOverviewDialog : public QDialog +{ + Q_OBJECT + Ui::CProjectOverviewDialog *mpUI; + CWorldEditor *mpWorldEditor; + CGameProject *mpProject; + + QVector mWorldEntries; + QVector mAreaEntries; + TResPtr mpWorld; + +public: + explicit CProjectOverviewDialog(QWidget *pParent = 0); + ~CProjectOverviewDialog(); + +public slots: + void OpenProject(); + void ExportGame(); + void LoadWorld(); + void LaunchEditor(); + + void SetupWorldsList(); +}; + +#endif // CPROJECTOVERVIEWDIALOG_H diff --git a/src/Editor/CProjectOverviewDialog.ui b/src/Editor/CProjectOverviewDialog.ui new file mode 100644 index 00000000..9f4f6d82 --- /dev/null +++ b/src/Editor/CProjectOverviewDialog.ui @@ -0,0 +1,99 @@ + + + CProjectOverviewDialog + + + + 0 + 0 + 268 + 367 + + + + Dialog + + + + + + + + Open Project + + + + + + + Export Game + + + + + + + + + + 2 + 0 + + + + Worlds + + + + + + + + + false + + + Load + + + + + + + + + + + 3 + 0 + + + + Areas + + + + + + false + + + + + + + false + + + Launch World Editor + + + + + + + + + + + diff --git a/src/Editor/Editor.pro b/src/Editor/Editor.pro index e79a1921..5d91916c 100644 --- a/src/Editor/Editor.pro +++ b/src/Editor/Editor.pro @@ -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 diff --git a/src/Editor/UICommon.cpp b/src/Editor/UICommon.cpp index b21c24fb..8c6f8a03 100644 --- a/src/Editor/UICommon.cpp +++ b/src/Editor/UICommon.cpp @@ -21,7 +21,7 @@ QMap 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 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)" } }; diff --git a/src/Editor/main.cpp b/src/Editor/main.cpp index 14c1d475..99b0782d 100644 --- a/src/Editor/main.cpp +++ b/src/Editor/main.cpp @@ -1,4 +1,5 @@ #include "CStartWindow.h" +#include "CProjectOverviewDialog.h" #include #include @@ -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(); }