Skip to content

Commit 1c751fd

Browse files
committed
Main menu bar changes
- Moved the level list into it's own menu bar instead of being part of Tools > - The level list is now based on mp_levels.xtbl and dlc02_mp_levels.xtbl (except for terr01 and dlc01 which are hardcoded). This is a step towards supporting custom map vpps. We've mostly figured out how to make these. Just trying to figure out some lingering issues like the level the custom map was based on not loading. We can't create them completely from scratch yet so instead they're based on existing maps. - The level list is now alphabetically sorted - Moved the themes menu under View >
1 parent aeb788f commit 1c751fd

File tree

2 files changed

+170
-101
lines changed

2 files changed

+170
-101
lines changed

Nanoforge/gui/MainGui.cpp

Lines changed: 161 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
#include "rfg/xtbl/XtblManager.h"
2727
#include "util/TaskScheduler.h"
2828
#include "common/filesystem/Path.h"
29+
#include "rfg/PackfileVFS.h"
30+
#include <tinyxml2/tinyxml2.h>
31+
#include <RfgTools++/formats/packfiles/Packfile3.h>
32+
#include "common/Defer.h"
2933

3034
CVar CVar_ShowFPS("Show FPS", ConfigType::Bool,
3135
"If enabled an FPS meter is shown on the main menu bar.",
@@ -39,63 +43,6 @@ CVar CVar_UI_Theme("UI Theme", ConfigType::String,
3943
false //Show in settings
4044
);
4145

42-
//Used in MainGui::DrawMainMenuBar()
43-
std::vector<const char*> TerritoryList =
44-
{
45-
"terr01",
46-
"dlc01",
47-
"mp_cornered",
48-
"mp_crashsite",
49-
"mp_crescent",
50-
"mp_crevice",
51-
"mp_deadzone",
52-
"mp_downfall",
53-
"mp_excavation",
54-
"mp_fallfactor",
55-
"mp_framework",
56-
"mp_garrison",
57-
"mp_gauntlet",
58-
"mp_overpass",
59-
"mp_pcx_assembly",
60-
"mp_pcx_crossover",
61-
"mp_pinnacle",
62-
"mp_quarantine",
63-
"mp_radial",
64-
"mp_rift",
65-
"mp_sandpit",
66-
"mp_settlement",
67-
"mp_warlords",
68-
"mp_wasteland",
69-
"mp_wreckage",
70-
"mpdlc_broadside",
71-
"mpdlc_division",
72-
"mpdlc_islands",
73-
"mpdlc_landbridge",
74-
"mpdlc_minibase",
75-
"mpdlc_overhang",
76-
"mpdlc_puncture",
77-
"mpdlc_ruins",
78-
"wc1",
79-
"wc2",
80-
"wc3",
81-
"wc4",
82-
"wc5",
83-
"wc6",
84-
"wc7",
85-
"wc8",
86-
"wc9",
87-
"wc10",
88-
"wcdlc1",
89-
"wcdlc2",
90-
"wcdlc3",
91-
"wcdlc4",
92-
"wcdlc5",
93-
"wcdlc6",
94-
"wcdlc7",
95-
"wcdlc8",
96-
"wcdlc9"
97-
};
98-
9946
//Titles for outliner and inspector windows. Used when drawing the windows, but also needed when setting their default docking positions
10047
const char* OutlinerIdentifier = ICON_FA_LIST " Outliner";
10148
const char* InspectorIdentifier = ICON_FA_WRENCH " Inspector";
@@ -148,6 +95,12 @@ void MainGui::Update()
14895
ImGui::GetIO().FontGlobalScale = lastFrameScale;
14996
}
15097

98+
//Load level definitions from xtbls when possible
99+
if (!levelDefinitionsLoaded_ && State.PackfileVFS->Ready())
100+
{
101+
LoadLevelDefinitions();
102+
}
103+
151104
//Draw always visible UI elements
152105
DrawMainMenuBar();
153106
DrawDockspace();
@@ -500,6 +453,30 @@ void MainGui::DrawMainMenuBar()
500453
}
501454
ImGui::EndMenu();
502455
}
456+
if (ImGui::BeginMenu("Theme"))
457+
{
458+
459+
if (ImGui::MenuItem("Dark"))
460+
{
461+
gui::SetThemePreset(Dark);
462+
CVar_UI_Theme.Get<string>() = "dark";
463+
Config::Get()->Save();
464+
}
465+
if (ImGui::MenuItem("Orange"))
466+
{
467+
gui::SetThemePreset(Orange);
468+
CVar_UI_Theme.Get<string>() = "orange";
469+
Config::Get()->Save();
470+
}
471+
if (ImGui::MenuItem("Blue"))
472+
{
473+
gui::SetThemePreset(Blue);
474+
CVar_UI_Theme.Get<string>() = "blue";
475+
Config::Get()->Save();
476+
}
477+
ImGui::EndMenu();
478+
}
479+
ImGui::Separator();
503480
#ifdef DEVELOPMENT_BUILD
504481
if (ImGui::MenuItem("ImGui Demo", nullptr, &imguiDemoWindowOpen_))
505482
{
@@ -524,32 +501,6 @@ void MainGui::DrawMainMenuBar()
524501
//Draw tools menu
525502
if (ImGui::BeginMenu("Tools"))
526503
{
527-
bool canOpenTerritory = State.CurrentProject && State.CurrentProject->Loaded();
528-
if (ImGui::BeginMenu("Open territory", canOpenTerritory))
529-
{
530-
for (const char* territory : TerritoryList)
531-
{
532-
string territoryShortname = string(territory);
533-
string territoryFilename = territoryShortname;
534-
if (territoryFilename == "terr01") territoryFilename = "zonescript_terr01";
535-
else if (territoryFilename == "dlc01") territoryFilename = "zonescript_dlc01";
536-
territoryFilename += ".vpp_pc";
537-
538-
bool overrideOpenCheck = (ImGui::IsKeyDown(ImGuiKey_RightCtrl) || ImGui::IsKeyDown(ImGuiKey_LeftCtrl)) && (ImGui::IsKeyDown(ImGuiKey_RightShift) || ImGui::IsKeyDown(ImGuiKey_LeftShift));
539-
bool alreadyOpen = false;
540-
for (Handle<IDocument> doc : State.Documents)
541-
{
542-
if (doc->Title == territoryShortname)
543-
alreadyOpen = true;
544-
}
545-
546-
if (ImGui::MenuItem(territory, "", nullptr, overrideOpenCheck || !alreadyOpen))
547-
{
548-
State.CreateDocument(territoryShortname, CreateHandle<TerritoryDocument>(&State, territoryFilename, territoryShortname));
549-
}
550-
}
551-
ImGui::EndMenu();
552-
}
553504
if (ImGui::BeginMenu("Localization"))
554505
{
555506
if (ImGui::MenuItem("View localized strings"))
@@ -582,29 +533,35 @@ void MainGui::DrawMainMenuBar()
582533
ImGui::EndMenu();
583534
}
584535

585-
//Draw themes menu
586-
if (ImGui::BeginMenu("Theme"))
536+
bool canOpenTerritory = State.CurrentProject && State.CurrentProject->Loaded();
537+
if (canOpenTerritory)
587538
{
588-
589-
if (ImGui::MenuItem("Dark"))
590-
{
591-
gui::SetThemePreset(Dark);
592-
CVar_UI_Theme.Get<string>() = "dark";
593-
Config::Get()->Save();
594-
}
595-
if (ImGui::MenuItem("Orange"))
539+
if (ImGui::BeginMenu("Maps", canOpenTerritory))
596540
{
597-
gui::SetThemePreset(Orange);
598-
CVar_UI_Theme.Get<string>() = "orange";
599-
Config::Get()->Save();
541+
for (LevelDefinition& definition : levelDefinitions_)
542+
{
543+
bool overrideOpenCheck = (ImGui::IsKeyDown(ImGuiKey_RightCtrl) || ImGui::IsKeyDown(ImGuiKey_LeftCtrl)) && (ImGui::IsKeyDown(ImGuiKey_RightShift) || ImGui::IsKeyDown(ImGuiKey_LeftShift));
544+
bool alreadyOpen = false;
545+
for (Handle<IDocument> doc : State.Documents)
546+
{
547+
if (doc->Title == definition.Name)
548+
alreadyOpen = true;
549+
}
550+
551+
if (ImGui::MenuItem(definition.Name.c_str(), (definition.Filename + ".vpp_pc").c_str(), nullptr, overrideOpenCheck || !alreadyOpen))
552+
{
553+
State.CreateDocument(definition.Name, CreateHandle<TerritoryDocument>(&State, definition.Filename + ".vpp_pc", definition.Name));
554+
}
555+
}
556+
ImGui::EndMenu();
600557
}
601-
if (ImGui::MenuItem("Blue"))
558+
}
559+
else
560+
{
561+
if (ImGui::BeginMenu("Open a project to edit maps", false))
602562
{
603-
gui::SetThemePreset(Blue);
604-
CVar_UI_Theme.Get<string>() = "blue";
605-
Config::Get()->Save();
563+
ImGui::EndMenu();
606564
}
607-
ImGui::EndMenu();
608565
}
609566

610567
bool showFPS = CVar_ShowFPS.Get<bool>();
@@ -963,6 +920,109 @@ void MainGui::DrawProjectSaveLoadDialogs()
963920
State.FontManager->FontMedium.Pop();
964921
}
965922

923+
bool LoadLevelDefinitionsFromXml(tinyxml2::XMLDocument* doc, std::vector<LevelDefinition>& definitions)
924+
{
925+
tinyxml2::XMLElement* root = doc->FirstChildElement();
926+
tinyxml2::XMLElement* table = root->FirstChildElement("Table");
927+
tinyxml2::XMLElement* current = table->FirstChildElement("mp_level_list");
928+
while (current)
929+
{
930+
LevelDefinition definition;
931+
tinyxml2::XMLElement* nameXml = current->FirstChildElement("Name");
932+
tinyxml2::XMLElement* filenameXml = current->FirstChildElement("Filename");
933+
if (!nameXml)
934+
{
935+
LOG_ERROR("Level definition is missing <Name>. Skipping definition...");
936+
return false;
937+
}
938+
if (!filenameXml)
939+
{
940+
LOG_ERROR("Level definition is missing <Filename>. Skipping definition...");
941+
return false;
942+
}
943+
944+
definition.Name = nameXml->GetText();
945+
definition.Filename = filenameXml->GetText();
946+
definitions.push_back(definition);
947+
current = current->NextSiblingElement("mp_level_list");
948+
}
949+
950+
return true;
951+
}
952+
953+
void MainGui::LoadLevelDefinitions()
954+
{
955+
//Wait for packfiles to be parsed
956+
if (!State.PackfileVFS->Ready() || levelDefinitionsLoaded_)
957+
return;
958+
959+
//Add hardcoded definitions
960+
levelDefinitions_.push_back({ "Terr01", "zonescript_terr01" });
961+
levelDefinitions_.push_back({ "Dlc01", "zonescript_dlc01" });
962+
963+
//Get misc.vpp_pc
964+
Handle<Packfile3> misc = State.PackfileVFS->GetPackfile("misc.vpp_pc");
965+
if (!misc)
966+
{
967+
LOG_ERROR("Failed to get misc.vpp_pc for level definition loading! Cannot load level list.");
968+
levelDefinitionsLoaded_ = true; //Set true on failure so it doesn't spam the error log
969+
return;
970+
}
971+
972+
//Extract xtbls from misc
973+
auto mpLevels = misc->ExtractSingleFile("mp_levels.xtbl");
974+
auto dlcMpLevels = misc->ExtractSingleFile("dlc02_mp_levels.xtbl");
975+
if (!mpLevels)
976+
{
977+
LOG_ERROR("Failed to extract mp_levels.xtbl from misc.vpp_pc. Can't load level list.");
978+
levelDefinitionsLoaded_ = true;
979+
return;
980+
}
981+
if (!dlcMpLevels)
982+
{
983+
LOG_ERROR("Failed to extract dlc02_mp_levels.xtbl from misc.vpp_pc. Can't load level list.");
984+
levelDefinitionsLoaded_ = true;
985+
return;
986+
}
987+
988+
//Parse xtbls
989+
tinyxml2::XMLDocument* mpLevelsXml = new tinyxml2::XMLDocument();
990+
tinyxml2::XMLDocument* dlcMpLevelsXml = new tinyxml2::XMLDocument();
991+
defer(delete mpLevelsXml);
992+
defer(delete dlcMpLevelsXml);
993+
if (mpLevelsXml->Parse((const char*)mpLevels.value().data(), mpLevels.value().size()) != tinyxml2::XML_SUCCESS)
994+
{
995+
LOG_ERROR("Failed to parse mp_levels.xtbl. Can't load level list.");
996+
levelDefinitionsLoaded_ = true;
997+
return;
998+
}
999+
if (dlcMpLevelsXml->Parse((const char*)dlcMpLevels.value().data(), dlcMpLevels.value().size()) != tinyxml2::XML_SUCCESS)
1000+
{
1001+
LOG_ERROR("Failed to parse dlc02_mp_levels.xtbl. Can't load level list.");
1002+
levelDefinitionsLoaded_ = true;
1003+
return;
1004+
}
1005+
1006+
//Read definitions from xtbls
1007+
if (!LoadLevelDefinitionsFromXml(mpLevelsXml, levelDefinitions_))
1008+
{
1009+
LOG_ERROR("Failed to load level definitions from mp_levels.xtbl. Can't load level list.");
1010+
levelDefinitionsLoaded_ = true;
1011+
return;
1012+
}
1013+
if (!LoadLevelDefinitionsFromXml(dlcMpLevelsXml, levelDefinitions_))
1014+
{
1015+
LOG_ERROR("Failed to load level definitions from dlc02_mp_levels.xtbl. Can't load level list.");
1016+
levelDefinitionsLoaded_ = true;
1017+
return;
1018+
}
1019+
1020+
//Sort alphabetically
1021+
std::ranges::sort(levelDefinitions_, [](LevelDefinition& a, LevelDefinition& b) -> bool { return a.Name < b.Name; });
1022+
1023+
levelDefinitionsLoaded_ = true;
1024+
}
1025+
9661026
void MainGui::SaveAll()
9671027
{
9681028
if (!State.CurrentProject || !State.CurrentProject->Loaded())

Nanoforge/gui/MainGui.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ using ImGuiID = unsigned int;
2020

2121
#define ToolbarEnabled false //Currently disabled since there's no use for it yet. It has undo/redo buttons but support for that hasn't been added yet
2222

23+
struct LevelDefinition
24+
{
25+
string Name;
26+
string Filename;
27+
};
28+
2329
//Todo: Split the gui out into multiple files and/or classes. Will be a mess if it's all in one file
2430
class MainGui
2531
{
@@ -46,9 +52,12 @@ class MainGui
4652
MenuItem* GetMenu(std::string_view text);
4753
void DrawOutlinerAndInspector();
4854
void DrawProjectSaveLoadDialogs();
55+
void LoadLevelDefinitions();
4956

5057
std::vector<Handle<IGuiPanel>> panels_ = {};
5158
std::vector<MenuItem> menuItems_ = {}; //Main menu bar items
59+
std::vector<LevelDefinition> levelDefinitions_ = {};
60+
bool levelDefinitionsLoaded_ = false;
5261

5362
ImGuiID dockspaceId = 0;
5463
ImGuiID dockspaceCentralNodeId = 0;

0 commit comments

Comments
 (0)