diff --git a/src/Editor/WorldEditor/CWorldInfoSidebar.cpp b/src/Editor/WorldEditor/CWorldInfoSidebar.cpp index 9d5dab43..9aeda2ef 100644 --- a/src/Editor/WorldEditor/CWorldInfoSidebar.cpp +++ b/src/Editor/WorldEditor/CWorldInfoSidebar.cpp @@ -102,14 +102,17 @@ void CWorldInfoSidebar::OnWorldTreeClicked(QModelIndex Index) QModelIndex RealIndex = mProxyModel.mapToSource(Index); // Fill in world info - mpUI->WorldInfoWidget->setHidden(false); - CWorld *pWorld = mModel.WorldForIndex(RealIndex); - mpUI->WorldNameLabel->setText( TO_QSTRING(pWorld->InGameName()) ); - mpUI->WorldSelector->SetResource(pWorld); - mpUI->WorldNameSelector->SetResource(pWorld->NameString()); - mpUI->DarkWorldNameSelector->SetResource(pWorld->DarkNameString()); - mpUI->SkySelector->SetResource(pWorld->DefaultSkybox()); + mpUI->WorldInfoWidget->setHidden( pWorld == nullptr ); + + if (pWorld) + { + mpUI->WorldNameLabel->setText( TO_QSTRING(pWorld->InGameName()) ); + mpUI->WorldSelector->SetResource(pWorld); + mpUI->WorldNameSelector->SetResource(pWorld->NameString()); + mpUI->DarkWorldNameSelector->SetResource(pWorld->DarkNameString()); + mpUI->SkySelector->SetResource(pWorld->DefaultSkybox()); + } // Fill in area info bool IsArea = !mModel.IndexIsWorld(RealIndex); @@ -117,7 +120,7 @@ void CWorldInfoSidebar::OnWorldTreeClicked(QModelIndex Index) if (IsArea) { - int AreaIndex = mModel.AreaIndexForIndex(RealIndex); + int AreaIndex = Editor()->CurrentGame() == eReturns ? 0 : mModel.AreaIndexForIndex(RealIndex); mpUI->AreaNameLabel->setText( TO_QSTRING(pWorld->AreaInGameName(AreaIndex)) ); mpUI->AreaSelector->SetResource( pWorld->AreaResourceID(AreaIndex) ); mpUI->AreaNameLineEdit->setText( TO_QSTRING(pWorld->AreaInternalName(AreaIndex)) ); @@ -140,9 +143,20 @@ void CWorldInfoSidebar::OnWorldTreeDoubleClicked(QModelIndex Index) if (!mModel.IndexIsWorld(RealIndex)) { - CWorld *pWorld = mModel.WorldForIndex(RealIndex); + TResPtr pWorld = mModel.WorldForIndex(RealIndex); int AreaIndex = mModel.AreaIndexForIndex(RealIndex); - gpEdApp->WorldEditor()->SetArea(pWorld, AreaIndex); + + // Validate area actually exists... DKCR has worlds that contain areas that don't exist + CAssetID AreaAssetID = pWorld->AreaResourceID(AreaIndex); + + if (gpResourceStore->IsResourceRegistered(AreaAssetID)) + { + gpEdApp->WorldEditor()->SetArea(pWorld, AreaIndex); + } + else + { + UICommon::ErrorMsg(Editor(), "The MREA asset associated with this area doesn't exist!"); + } } } diff --git a/src/Editor/WorldEditor/CWorldTreeModel.cpp b/src/Editor/WorldEditor/CWorldTreeModel.cpp index 9419ecb3..63f83203 100644 --- a/src/Editor/WorldEditor/CWorldTreeModel.cpp +++ b/src/Editor/WorldEditor/CWorldTreeModel.cpp @@ -3,6 +3,7 @@ #include "CWorldEditor.h" #include "UICommon.h" #include +#include #include CWorldTreeModel::CWorldTreeModel(CWorldEditor *pEditor) @@ -14,7 +15,7 @@ CWorldTreeModel::CWorldTreeModel(CWorldEditor *pEditor) int CWorldTreeModel::rowCount(const QModelIndex& rkParent) const { if (!rkParent.isValid()) return mWorldList.size(); - else if (IndexIsWorld(rkParent)) return WorldForIndex(rkParent)->NumAreas(); + else if (IndexIsWorld(rkParent)) return mWorldList[rkParent.row()].Areas.size(); else return 0; } @@ -49,54 +50,46 @@ QVariant CWorldTreeModel::data(const QModelIndex& rkIndex, int Role) const { if (Role == Qt::DisplayRole || Role == Qt::ToolTipRole) { - CWorld *pWorld = WorldForIndex(rkIndex); + const SWorldInfo& rkInfo = WorldInfoForIndex(rkIndex); // World if (IndexIsWorld(rkIndex)) { - QString WorldName = TO_QSTRING( pWorld->Name() ); - CStringTable *pWorldNameString = pWorld->NameString(); - // For Corruption worlds, we swap the columns around. This is because Corruption's in-game world names // are often missing, confusing, or just straight-up inaccurate, which makes the internal name a better // means of telling worlds apart. - u32 InternalNameCol = (pWorld->Game() == eCorruption ? 0 : 1); + // For DKCR worlds, we only display the world name in the first column. + u32 InternalNameCol = (gpEdApp->ActiveProject()->Game() >= eCorruption ? 0 : 1); + // Internal name if (rkIndex.column() == InternalNameCol) - return WorldName; + return rkInfo.WorldName; + // In-Game name else { - if (pWorldNameString) - return TO_QSTRING( pWorldNameString->String("ENGL", 0) ); + if (rkInfo.pWorld) + return TO_QSTRING( rkInfo.pWorld->InGameName() ); else - return WorldName; + return ""; } } // Area else { - u32 AreaIndex = AreaIndexForIndex(rkIndex); - ASSERT(AreaIndex >= 0 && AreaIndex < pWorld->NumAreas()); + CWorld *pWorld = WorldForIndex(rkIndex); + int AreaIndex = AreaIndexForIndex(rkIndex); + ASSERT(pWorld); - CAssetID AreaAssetID = pWorld->AreaResourceID(AreaIndex); - CResourceEntry *pAreaEntry = pWorld->Entry()->ResourceStore()->FindEntry(AreaAssetID); - ASSERT(pAreaEntry); - - QString AreaAssetName = TO_QSTRING( pAreaEntry->Name() ); - CStringTable *pAreaNameString = pWorld->AreaName(AreaIndex); + TString AreaInternalName = pWorld->AreaInternalName(AreaIndex); + TString AreaInGameName = (gpEdApp->ActiveProject()->Game() == eReturns ? pWorld->InGameName() : pWorld->AreaInGameName( AreaIndexForIndex(rkIndex) )); + // Return name if (rkIndex.column() == 1) - return AreaAssetName; - + return TO_QSTRING(AreaInternalName); else - { - if (pAreaNameString) - return TO_QSTRING( pAreaNameString->String("ENGL", 0) ); - else - return "!!" + AreaAssetName; - } + return TO_QSTRING(AreaInGameName); } } @@ -115,9 +108,6 @@ QVariant CWorldTreeModel::data(const QModelIndex& rkIndex, int Role) const else if (Role == Qt::FontRole) { - CWorld *pWorld = WorldForIndex(rkIndex); - ASSERT(pWorld); - QFont Font; int PointSize = Font.pointSize() + 2; @@ -125,16 +115,25 @@ QVariant CWorldTreeModel::data(const QModelIndex& rkIndex, int Role) const { PointSize += 1; - CWorld *pWorld = WorldForIndex(rkIndex); - if (gpEdApp->WorldEditor()->ActiveWorld() == pWorld) - Font.setBold(true); + const SWorldInfo& rkInfo = WorldInfoForIndex(rkIndex); + CWorld *pActiveWorld = gpEdApp->WorldEditor()->ActiveWorld(); + + if (pActiveWorld) + { + EGame Game = gpEdApp->ActiveProject()->Game(); + + bool IsActiveWorld = (Game <= eCorruption && rkInfo.pWorld == pActiveWorld) || + (Game == eReturns && rkInfo.Areas.contains(pActiveWorld->Entry())); + + if (IsActiveWorld) + Font.setBold(true); + } } else { CResourceEntry *pEntry = AreaEntryForIndex(rkIndex); - ASSERT(pEntry); - if (pEntry->IsLoaded()) + if (pEntry && pEntry->IsLoaded()) { if (gpEdApp->WorldEditor()->ActiveArea() == pEntry->Resource()) Font.setBold(true); @@ -164,26 +163,52 @@ QVariant CWorldTreeModel::headerData(int Section, Qt::Orientation Orientation, i bool CWorldTreeModel::IndexIsWorld(const QModelIndex& rkIndex) const { - return AreaIndexForIndex(rkIndex) == 0xFFFF; -} - -CWorld* CWorldTreeModel::WorldForIndex(const QModelIndex& rkIndex) const -{ - int WorldIndex = (rkIndex.internalId() >> 16) & 0xFFFF; - return mWorldList[WorldIndex].pWorld; + int AreaIndex = (int) rkIndex.internalId() & 0xFFFF; + return (AreaIndex == 0xFFFF); } int CWorldTreeModel::AreaIndexForIndex(const QModelIndex& rkIndex) const { - int InternalID = (int) rkIndex.internalId(); - return (InternalID & 0xFFFF); + if (gpEdApp->ActiveProject()->Game() == eReturns) + return 0; + + else + { + int InternalID = (int) rkIndex.internalId(); + return (InternalID & 0xFFFF); + } +} + +CWorld* CWorldTreeModel::WorldForIndex(const QModelIndex& rkIndex) const +{ + ASSERT(rkIndex.isValid()); + const SWorldInfo& rkInfo = WorldInfoForIndex(rkIndex); + + if (gpEdApp->ActiveProject()->Game() == eReturns && !IndexIsWorld(rkIndex)) + { + int AreaIndex = (int) rkIndex.internalId() & 0xFFFF; + CResourceEntry *pEntry = rkInfo.Areas[AreaIndex]; + return pEntry ? (CWorld*) pEntry->Load() : nullptr; + } + else + return rkInfo.pWorld; } CResourceEntry* CWorldTreeModel::AreaEntryForIndex(const QModelIndex& rkIndex) const { ASSERT(rkIndex.isValid() && !IndexIsWorld(rkIndex)); - const SWorldInfo& rkInfo = mWorldList[rkIndex.parent().row()]; - return rkInfo.Areas[rkIndex.row()]; + CWorld *pWorld = WorldForIndex(rkIndex); + int AreaIndex = AreaIndexForIndex(rkIndex); + + CAssetID AreaID; + if (pWorld) AreaID = pWorld->AreaResourceID(AreaIndex); + return gpResourceStore->FindEntry(AreaID); +} + +const CWorldTreeModel::SWorldInfo& CWorldTreeModel::WorldInfoForIndex(const QModelIndex& rkIndex) const +{ + int WorldIndex = ((int) rkIndex.internalId() >> 16) & 0xFFFF; + return mWorldList[WorldIndex]; } // ************ SLOTS ************ @@ -194,43 +219,125 @@ void CWorldTreeModel::OnProjectChanged(CGameProject *pProj) if (pProj) { - std::list WorldIDs; - pProj->GetWorldList(WorldIDs); - QList QWorldIDs = QList::fromStdList(WorldIDs); - - foreach (const CAssetID& rkID, QWorldIDs) + if (pProj->Game() != eReturns) { - CResourceEntry *pEntry = pProj->ResourceStore()->FindEntry(rkID); + // Metroid Prime series; fetch all world assets + std::list WorldIDs; + pProj->GetWorldList(WorldIDs); + QList QWorldIDs = QList::fromStdList(WorldIDs); - if (pEntry) + foreach (const CAssetID& rkID, QWorldIDs) { - TResPtr pWorld = pEntry->Load(); + CResourceEntry *pEntry = pProj->ResourceStore()->FindEntry(rkID); - if (pWorld) + if (pEntry) { - SWorldInfo Info; - Info.pWorld = pWorld; + TResPtr pWorld = pEntry->Load(); - for (u32 iArea = 0; iArea < pWorld->NumAreas(); iArea++) + if (pWorld) { - CAssetID AreaID = pWorld->AreaResourceID(iArea); - CResourceEntry *pAreaEntry = pWorld->Entry()->ResourceStore()->FindEntry(AreaID); - ASSERT(pAreaEntry); - Info.Areas << pAreaEntry; - } + SWorldInfo Info; + Info.WorldName = TO_QSTRING( pWorld->Name() ); + Info.pWorld = pWorld; - mWorldList << Info; + // Add areas + for (u32 iArea = 0; iArea < pWorld->NumAreas(); iArea++) + { + CAssetID AreaID = pWorld->AreaResourceID(iArea); + CResourceEntry *pAreaEntry = pWorld->Entry()->ResourceStore()->FindEntry(AreaID); + ASSERT(pAreaEntry); + Info.Areas << pAreaEntry; + } + + mWorldList << Info; + } } } + + // Sort in alphabetical order for MP3 + if (pProj->Game() >= eCorruption) + { + qSort(mWorldList.begin(), mWorldList.end(), [](const SWorldInfo& rkA, const SWorldInfo& rkB) -> bool { + return (rkA.WorldName.toUpper() < rkB.WorldName.toUpper()); + }); + } } - // Sort in alphabetical order for MP3 - if (pProj->Game() == eCorruption) + // DKCR - Get worlds from areas.lst + else { - qSort(mWorldList.begin(), mWorldList.end(), [](const SWorldInfo& rkLeft, const SWorldInfo& rkRight) -> bool { - return (rkLeft.pWorld->Name().ToUpper() < rkRight.pWorld->Name().ToUpper()); + TString AreaListPath = pProj->DiscDir(false) + "areas.lst"; + + // I really need a good text stream class at some point + FILE* pAreaList = fopen(*AreaListPath, "r"); + SWorldInfo *pInfo = nullptr; + std::set UsedWorlds; + + while (!feof(pAreaList)) + { + char LineBuffer[256]; + memset(LineBuffer, 0, 256); + fgets(LineBuffer, 256, pAreaList); + TString Line(LineBuffer); + + CAssetID WorldID; + TString WorldName; + u32 IDSplit = Line.IndexOf(' '); + + if (IDSplit != -1) + { + // Get world ID + TString IDString = (IDSplit == -1 ? "" : Line.SubString(2, IDSplit - 2)); + WorldID = CAssetID::FromString(IDString); + + // Get world name + TString WorldPath = (IDSplit == -1 ? "" : Line.SubString(IDSplit + 1, Line.Size() - IDSplit - 1)); + u32 UnderscoreIdx = WorldPath.IndexOf('_'); + u32 WorldDirEnd = WorldPath.IndexOf("\\/", UnderscoreIdx); + + if (UnderscoreIdx != -1 && WorldDirEnd != -1) + WorldName = WorldPath.SubString(UnderscoreIdx + 1, WorldDirEnd - UnderscoreIdx - 1); + } + + if (WorldID.IsValid() && !WorldName.IsEmpty()) + { + CResourceEntry *pEntry = gpResourceStore->FindEntry(WorldID); + + if (pEntry) + { + QString WorldNameQ = TO_QSTRING(WorldName); + + if (!pInfo || pInfo->WorldName != WorldNameQ) + { + mWorldList << SWorldInfo(); + pInfo = &mWorldList.back(); + pInfo->WorldName = WorldNameQ; + } + + pInfo->Areas << pEntry; + UsedWorlds.insert(pEntry->ID()); + } + } + } + fclose(pAreaList); + + // Add remaining worlds to FrontEnd world + mWorldList.prepend( SWorldInfo() ); + pInfo = &mWorldList.front(); + pInfo->WorldName = "FrontEnd"; + + for (TResourceIterator It; It; ++It) + { + if (UsedWorlds.find(It->ID()) == UsedWorlds.end()) + pInfo->Areas << *It; + } + + // Sort FrontEnd world + qSort( pInfo->Areas.begin(), pInfo->Areas.end(), [](CResourceEntry *pA, CResourceEntry *pB) -> bool { + return pA->UppercaseName() < pB->UppercaseName(); }); } + } endResetModel(); diff --git a/src/Editor/WorldEditor/CWorldTreeModel.h b/src/Editor/WorldEditor/CWorldTreeModel.h index 85801334..f5cddcf8 100644 --- a/src/Editor/WorldEditor/CWorldTreeModel.h +++ b/src/Editor/WorldEditor/CWorldTreeModel.h @@ -6,12 +6,19 @@ #include class CWorldEditor; +struct STreeArea +{ + CAssetID WorldID; + int AreaIndex; +}; + class CWorldTreeModel : public QAbstractItemModel { Q_OBJECT struct SWorldInfo { + QString WorldName; TResPtr pWorld; QList Areas; }; @@ -28,10 +35,13 @@ public: QVariant headerData(int Section, Qt::Orientation Orientation, int Role) const; bool IndexIsWorld(const QModelIndex& rkIndex) const; - CWorld* WorldForIndex(const QModelIndex& rkIndex) const; int AreaIndexForIndex(const QModelIndex& rkIndex) const; + CWorld* WorldForIndex(const QModelIndex& rkIndex) const; CResourceEntry* AreaEntryForIndex(const QModelIndex& rkIndex) const; +protected: + const SWorldInfo& WorldInfoForIndex(const QModelIndex& rkIndex) const; + public slots: void OnProjectChanged(CGameProject *pProj); void OnMapChanged();