mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2024-11-25 02:42:18 -05:00
Trackers MQ Cleanup (#4009)
* Clean up grayed out items in file select. Tie personal notes saving to `OnExitGame()` to account for save scum resets. Hide text input box (but not window) for personal notes when a save isn't loaded to prevent saving over a save's notes while in file select. * Allow check tracker window to be visible and show the "Waiting for file load..." text in file select (ease of positioning). * Fix key label text placement. * Persist `areasSpoiled`, change trigger to `OnSceneTransition`. Add `IsAreaSpoiled` for tie-in to Item Tracker. Add `SetAreaSpoiled` to unify bitmagic. * Add proper spoiling evaluation based on specific entrance IDs, or first check collection. Add light yellow color for vanilla dungeon abbreviations to match the purple for MQ. * Initialize `areasSpoiled` to 0, so 0 gets saved on file create. * All new conditional statements around `GameInteractor::IsSaveLoaded()` were inverted. Fixed that. * Change some c-style casts to `static_cast`. * Few more cleanups, plus clarifying comment.
This commit is contained in:
parent
0b8cc71d0d
commit
19be6e9b99
@ -103,6 +103,23 @@ std::map<SceneID, RandomizerCheckArea> DungeonRCAreasBySceneID = {
|
||||
{SCENE_INSIDE_GANONS_CASTLE, RCAREA_GANONS_CASTLE},
|
||||
};
|
||||
|
||||
// Dungeon entrances with obvious visual differences between MQ and vanilla qualifying as spoiling on sight
|
||||
std::vector<uint32_t> spoilingEntrances = {
|
||||
0x0000, // ENTR_DEKU_TREE_0
|
||||
0x0467, // ENTR_DODONGOS_CAVERN_1
|
||||
0x0028, // ENTR_JABU_JABU_0
|
||||
0x0407, // ENTR_JABU_JABU_1
|
||||
0x0169, // ENTR_FOREST_TEMPLE_0
|
||||
0x0165, // ENTR_FIRE_TEMPLE_0
|
||||
0x0175, // ENTR_FIRE_TEMPLE_1
|
||||
0x0423, // ENTR_WATER_TEMPLE_1
|
||||
0x0082, // ENTR_SPIRIT_TEMPLE_0
|
||||
0x02B2, // ENTR_SHADOW_TEMPLE_1
|
||||
0x0088, // ENTR_ICE_CAVERN_0
|
||||
0x0008, // ENTR_GERUDO_TRAINING_GROUNDS_0
|
||||
0x0467 // ENTR_INSIDE_GANONS_CASTLE_0
|
||||
};
|
||||
|
||||
std::map<RandomizerCheckArea, std::vector<RandomizerCheckObject>> checksByArea;
|
||||
bool areasFullyChecked[RCAREA_INVALID];
|
||||
u32 areasSpoiled = 0;
|
||||
@ -263,6 +280,10 @@ void SetCheckCollected(RandomizerCheck rc) {
|
||||
}
|
||||
SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true);
|
||||
|
||||
if (!IsAreaSpoiled(rcObj.rcArea)) {
|
||||
SetAreaSpoiled(rcObj.rcArea);
|
||||
}
|
||||
|
||||
doAreaScroll = true;
|
||||
UpdateOrdering(rcObj.rcArea);
|
||||
UpdateInventoryChecks();
|
||||
@ -467,9 +488,14 @@ void CheckTrackerLoadGame(int32_t fileNum) {
|
||||
areaChecksGotten[realRcObj.rcArea]++;
|
||||
}
|
||||
}
|
||||
|
||||
if (areaChecksGotten[realRcObj.rcArea] != 0 || RandomizerCheckObjects::AreaIsOverworld(realRcObj.rcArea)) {
|
||||
areasSpoiled |= (1 << realRcObj.rcArea);
|
||||
}
|
||||
for (int i = RCAREA_KOKIRI_FOREST; i < RCAREA_INVALID; i++) {
|
||||
if (!IsAreaSpoiled(static_cast<RandomizerCheckArea>(i)) && (RandomizerCheckObjects::AreaIsOverworld(static_cast<RandomizerCheckArea>(i)) || !IS_RANDO ||
|
||||
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_NONE ||
|
||||
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_SELECTION ||
|
||||
(OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_SET_NUMBER &&
|
||||
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_COUNT) == 12))) {
|
||||
SetAreaSpoiled(static_cast<RandomizerCheckArea>(i));
|
||||
}
|
||||
}
|
||||
if (OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_LINKS_POCKET) != RO_LINKS_POCKET_NOTHING && IS_RANDO) {
|
||||
@ -539,6 +565,9 @@ void CheckTrackerTransition(uint32_t sceneNum) {
|
||||
SetShopSeen(sceneNum, false);
|
||||
break;
|
||||
}
|
||||
if (!IsAreaSpoiled(currentArea) && (RandomizerCheckObjects::AreaIsOverworld(currentArea) || std::find(spoilingEntrances.begin(), spoilingEntrances.end(), gPlayState->nextEntranceIndex) != spoilingEntrances.end())) {
|
||||
SetAreaSpoiled(currentArea);
|
||||
}
|
||||
}
|
||||
|
||||
void CheckTrackerFrame() {
|
||||
@ -756,6 +785,7 @@ void CheckTrackerFlagSet(int16_t flagType, int32_t flag) {
|
||||
|
||||
void InitTrackerData(bool isDebug) {
|
||||
TrySetAreas();
|
||||
areasSpoiled = 0;
|
||||
for (auto& [rc, rco] : RandomizerCheckObjects::GetAllRCObjects()) {
|
||||
if (rc != RC_UNKNOWN_CHECK && rc != RC_MAX) {
|
||||
DefaultCheckData(rc);
|
||||
@ -783,6 +813,7 @@ void SaveTrackerData(SaveContext* saveContext, int sectionID, bool gameSave) {
|
||||
SaveManager::Instance->SaveData("hintItem", saveContext->checkTrackerData[i].hintItem);
|
||||
});
|
||||
});
|
||||
SaveManager::Instance->SaveData("areasSpoiled", areasSpoiled);
|
||||
}
|
||||
|
||||
void SaveFile(SaveContext* saveContext, int sectionID, bool fullSave) {
|
||||
@ -798,6 +829,7 @@ void LoadFile() {
|
||||
SaveManager::Instance->LoadData("hintItem", gSaveContext.checkTrackerData[i].hintItem);
|
||||
});
|
||||
});
|
||||
SaveManager::Instance->LoadData("areasSpoiled", areasSpoiled);
|
||||
}
|
||||
|
||||
void Teardown() {
|
||||
@ -809,6 +841,15 @@ void Teardown() {
|
||||
lastLocationChecked = RC_UNKNOWN_CHECK;
|
||||
}
|
||||
|
||||
bool IsAreaSpoiled(RandomizerCheckArea rcArea) {
|
||||
return areasSpoiled & (1 << rcArea);
|
||||
}
|
||||
|
||||
void SetAreaSpoiled(RandomizerCheckArea rcArea) {
|
||||
areasSpoiled |= (1 << rcArea);
|
||||
SaveManager::Instance->SaveSection(gSaveContext.fileNum, sectionId, true);
|
||||
}
|
||||
|
||||
void UpdateCheck(uint32_t check, RandomizerCheckTrackerData data) {
|
||||
auto area = RandomizerCheckObjects::GetAllRCObjects().find(static_cast<RandomizerCheck>(check))->second.rcArea;
|
||||
if ((!gSaveContext.checkTrackerData[check].skipped && data.skipped) ||
|
||||
@ -827,10 +868,6 @@ void UpdateCheck(uint32_t check, RandomizerCheckTrackerData data) {
|
||||
void CheckTrackerWindow::DrawElement() {
|
||||
ImGui::SetNextWindowSize(ImVec2(400, 540), ImGuiCond_FirstUseEver);
|
||||
|
||||
if (!initialized && (gPlayState == nullptr || gSaveContext.fileNum < 0 || gSaveContext.fileNum > 2)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (CVarGetInteger("gCheckTrackerWindowType", TRACKER_WINDOW_WINDOW) == TRACKER_WINDOW_FLOATING) {
|
||||
if (CVarGetInteger("gCheckTrackerShowOnlyPaused", 0) && (gPlayState == nullptr || gPlayState->pauseCtx.state == 0)) {
|
||||
return;
|
||||
@ -851,7 +888,7 @@ void CheckTrackerWindow::DrawElement() {
|
||||
|
||||
BeginFloatWindows("Check Tracker", mIsVisible, ImGuiWindowFlags_NoScrollbar);
|
||||
|
||||
if (!GameInteractor::IsSaveLoaded()) {
|
||||
if (!GameInteractor::IsSaveLoaded() || !initialized) {
|
||||
ImGui::Text("Waiting for file load..."); //TODO Language
|
||||
EndFloatWindows();
|
||||
return;
|
||||
@ -862,8 +899,6 @@ void CheckTrackerWindow::DrawElement() {
|
||||
sceneId = (SceneID)gPlayState->sceneNum;
|
||||
}
|
||||
|
||||
areasSpoiled |= (1 << currentArea);
|
||||
|
||||
//Quick Options
|
||||
#ifdef __WIIU__
|
||||
float headerHeight = 40.0f;
|
||||
@ -925,7 +960,6 @@ void CheckTrackerWindow::DrawElement() {
|
||||
Color_RGBA8 mainColor;
|
||||
Color_RGBA8 extraColor;
|
||||
std::string stemp;
|
||||
s32 areaMask = 1;
|
||||
|
||||
for (auto& [rcArea, objs] : checksByArea) {
|
||||
RandomizerCheckArea thisArea = currentArea;
|
||||
@ -978,11 +1012,7 @@ void CheckTrackerWindow::DrawElement() {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(extraColor.r / 255.0f, extraColor.g / 255.0f,
|
||||
extraColor.b / 255.0f, extraColor.a / 255.0f));
|
||||
|
||||
isThisAreaSpoiled = areasSpoiled & areaMask || CVarGetInteger("gCheckTrackerOptionMQSpoilers", 0) || !IS_RANDO ||
|
||||
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_NONE ||
|
||||
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_SELECTION ||
|
||||
(OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_RANDOM_MQ_DUNGEONS) == RO_MQ_DUNGEONS_SET_NUMBER &&
|
||||
OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_MQ_DUNGEON_COUNT) == 12);
|
||||
isThisAreaSpoiled = IsAreaSpoiled(rcArea) || CVarGetInteger("gCheckTrackerOptionMQSpoilers", 0);
|
||||
|
||||
if (isThisAreaSpoiled) {
|
||||
if (showVOrMQ && RandomizerCheckObjects::AreaIsDungeon(rcArea)) {
|
||||
@ -1015,7 +1045,6 @@ void CheckTrackerWindow::DrawElement() {
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
areaMask <<= 1;
|
||||
}
|
||||
|
||||
ImGui::EndTable(); //Checks Lead-out
|
||||
@ -1219,10 +1248,10 @@ bool IsVisibleInCheckTracker(RandomizerCheckObject rcObj) {
|
||||
}
|
||||
|
||||
void UpdateInventoryChecks() {
|
||||
//For all the areas with compasses, if you have one, spoil the area
|
||||
//For all the areas with maps, if you have one, spoil the area
|
||||
for (auto [scene, area] : DungeonRCAreasBySceneID) {
|
||||
if (CHECK_DUNGEON_ITEM(DUNGEON_MAP, scene)) {
|
||||
areasSpoiled |= (1 << area);
|
||||
SetAreaSpoiled(area);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1232,9 +1261,6 @@ void UpdateAreaFullyChecked(RandomizerCheckArea area) {
|
||||
|
||||
void UpdateAreas(RandomizerCheckArea area) {
|
||||
areasFullyChecked[area] = areaChecksGotten[area] == checksByArea.find(area)->second.size();
|
||||
if (areaChecksGotten[area] != 0 || RandomizerCheckObjects::AreaIsOverworld(area)) {
|
||||
areasSpoiled |= (1 << area);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAllOrdering() {
|
||||
|
@ -50,6 +50,8 @@ bool IsVisibleInCheckTracker(RandomizerCheckObject rcObj);
|
||||
void InitTrackerData(bool isDebug);
|
||||
RandomizerCheckArea GetCheckArea();
|
||||
void UpdateCheck(uint32_t, RandomizerCheckTrackerData);
|
||||
bool IsAreaSpoiled(RandomizerCheckArea rcArea);
|
||||
void SetAreaSpoiled(RandomizerCheckArea rcArea);
|
||||
} // namespace CheckTracker
|
||||
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <vector>
|
||||
#include <libultraship/libultraship.h>
|
||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||
#include "randomizer_check_tracker.h"
|
||||
#include <algorithm>
|
||||
|
||||
extern "C" {
|
||||
@ -290,15 +291,15 @@ bool IsValidSaveFile() {
|
||||
}
|
||||
|
||||
bool HasSong(ItemTrackerItem item) {
|
||||
return (1 << item.id) & gSaveContext.inventory.questItems;
|
||||
return GameInteractor::IsSaveLoaded() ? ((1 << item.id) & gSaveContext.inventory.questItems) : false;
|
||||
}
|
||||
|
||||
bool HasQuestItem(ItemTrackerItem item) {
|
||||
return (item.data & gSaveContext.inventory.questItems) != 0;
|
||||
return GameInteractor::IsSaveLoaded() ? (item.data & gSaveContext.inventory.questItems) : false;
|
||||
}
|
||||
|
||||
bool HasEquipment(ItemTrackerItem item) {
|
||||
return (item.data & gSaveContext.inventory.equipment) != 0;
|
||||
return GameInteractor::IsSaveLoaded() ? (item.data & gSaveContext.inventory.equipment) : false;
|
||||
}
|
||||
|
||||
ItemTrackerNumbers GetItemCurrentAndMax(ItemTrackerItem item) {
|
||||
@ -406,8 +407,12 @@ ItemTrackerNumbers GetItemCurrentAndMax(ItemTrackerItem item) {
|
||||
#define IM_COL_GREEN IM_COL32(0, 255, 0, 255)
|
||||
#define IM_COL_GRAY IM_COL32(155, 155, 155, 255)
|
||||
#define IM_COL_PURPLE IM_COL32(180, 90, 200, 255)
|
||||
#define IM_COL_LIGHT_YELLOW IM_COL32(255, 255, 130, 255)
|
||||
|
||||
void DrawItemCount(ItemTrackerItem item) {
|
||||
void DrawItemCount(ItemTrackerItem item, bool hideMax) {
|
||||
if (!GameInteractor::IsSaveLoaded()) {
|
||||
return;
|
||||
}
|
||||
int iconSize = CVarGetInteger("gItemTrackerIconSize", 36);
|
||||
ItemTrackerNumbers currentAndMax = GetItemCurrentAndMax(item);
|
||||
ImVec2 p = ImGui::GetCursorScreenPos();
|
||||
@ -416,7 +421,7 @@ void DrawItemCount(ItemTrackerItem item) {
|
||||
|
||||
if (item.id == ITEM_KEY_SMALL && IsValidSaveFile()) {
|
||||
std::string currentString = "";
|
||||
std::string maxString = std::to_string(currentAndMax.maxCapacity);
|
||||
std::string maxString = hideMax ? "???" : std::to_string(currentAndMax.maxCapacity);
|
||||
ImU32 currentColor = IM_COL_WHITE;
|
||||
ImU32 maxColor = IM_COL_GREEN;
|
||||
// "Collected / Max", "Current / Collected / Max", "Current / Max"
|
||||
@ -548,7 +553,7 @@ void DrawQuest(ItemTrackerItem item) {
|
||||
ImVec2(iconSize, iconSize), ImVec2(0, 0), ImVec2(1, 1));
|
||||
|
||||
if (item.id == QUEST_SKULL_TOKEN) {
|
||||
DrawItemCount(item);
|
||||
DrawItemCount(item, false);
|
||||
}
|
||||
|
||||
ImGui::EndGroup();
|
||||
@ -558,7 +563,7 @@ void DrawQuest(ItemTrackerItem item) {
|
||||
|
||||
void DrawItem(ItemTrackerItem item) {
|
||||
|
||||
uint32_t actualItemId = INV_CONTENT(item.id);
|
||||
uint32_t actualItemId = GameInteractor::IsSaveLoaded() ? INV_CONTENT(item.id) : ITEM_NONE;
|
||||
int iconSize = CVarGetInteger("gItemTrackerIconSize", 36);
|
||||
bool hasItem = actualItemId != ITEM_NONE;
|
||||
std::string itemName = "";
|
||||
@ -617,7 +622,7 @@ void DrawItem(ItemTrackerItem item) {
|
||||
ImGui::Image(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(hasItem && IsValidSaveFile() ? item.name : item.nameFaded),
|
||||
ImVec2(iconSize, iconSize), ImVec2(0, 0), ImVec2(1, 1));
|
||||
|
||||
DrawItemCount(item);
|
||||
DrawItemCount(item, false);
|
||||
ImGui::EndGroup();
|
||||
|
||||
if (itemName == "") {
|
||||
@ -628,7 +633,7 @@ void DrawItem(ItemTrackerItem item) {
|
||||
}
|
||||
|
||||
void DrawBottle(ItemTrackerItem item) {
|
||||
uint32_t actualItemId = gSaveContext.inventory.items[SLOT(item.id) + item.data];
|
||||
uint32_t actualItemId = GameInteractor::IsSaveLoaded() ? (gSaveContext.inventory.items[SLOT(item.id) + item.data]) : false;
|
||||
bool hasItem = actualItemId != ITEM_NONE;
|
||||
|
||||
if (GameInteractor::IsSaveLoaded() && (hasItem && item.id != actualItemId && actualItemTrackerItemMap.find(actualItemId) != actualItemTrackerItemMap.end())) {
|
||||
@ -647,8 +652,8 @@ void DrawDungeonItem(ItemTrackerItem item) {
|
||||
ImU32 dungeonColor = IM_COL_WHITE;
|
||||
uint32_t bitMask = 1 << (item.id - ITEM_KEY_BOSS); // Bitset starts at ITEM_KEY_BOSS == 0. the rest are sequential
|
||||
int iconSize = CVarGetInteger("gItemTrackerIconSize", 36);
|
||||
bool hasItem = (bitMask & gSaveContext.inventory.dungeonItems[item.data]) != 0;
|
||||
bool hasSmallKey = (gSaveContext.inventory.dungeonKeys[item.data]) >= 0;
|
||||
bool hasItem = GameInteractor::IsSaveLoaded() ? (bitMask & gSaveContext.inventory.dungeonItems[item.data]) : false;
|
||||
bool hasSmallKey = GameInteractor::IsSaveLoaded() ? ((gSaveContext.inventory.dungeonKeys[item.data]) >= 0) : false;
|
||||
ImGui::BeginGroup();
|
||||
if (itemId == ITEM_KEY_SMALL) {
|
||||
ImGui::Image(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(hasSmallKey && IsValidSaveFile() ? item.name : item.nameFaded),
|
||||
@ -659,16 +664,18 @@ void DrawDungeonItem(ItemTrackerItem item) {
|
||||
ImVec2(iconSize, iconSize), ImVec2(0, 0), ImVec2(1, 1));
|
||||
}
|
||||
|
||||
if (ResourceMgr_IsSceneMasterQuest(item.data) && (CHECK_DUNGEON_ITEM(DUNGEON_MAP, item.data) || item.data == SCENE_GERUDO_TRAINING_GROUND || item.data == SCENE_INSIDE_GANONS_CASTLE)) {
|
||||
dungeonColor = IM_COL_PURPLE;
|
||||
if (CheckTracker::IsAreaSpoiled(RandomizerCheckObjects::GetRCAreaBySceneID(static_cast<SceneID>(item.data))) && GameInteractor::IsSaveLoaded()) {
|
||||
dungeonColor = (ResourceMgr_IsSceneMasterQuest(item.data) ? IM_COL_PURPLE : IM_COL_LIGHT_YELLOW);
|
||||
}
|
||||
|
||||
if (itemId == ITEM_KEY_SMALL) {
|
||||
DrawItemCount(item);
|
||||
DrawItemCount(item, !CheckTracker::IsAreaSpoiled(RandomizerCheckObjects::GetRCAreaBySceneID(static_cast<SceneID>(item.data))));
|
||||
|
||||
ImVec2 p = ImGui::GetCursorScreenPos();
|
||||
// offset puts the text at the correct level. for some reason, if the save is loaded, the margin is 3 pixels higher only for small keys, so we use 16 then. Otherwise, 13 is where everything else is
|
||||
int offset = GameInteractor::IsSaveLoaded() ? 16 : 13;
|
||||
std::string dungeonName = itemTrackerDungeonShortNames[item.data];
|
||||
ImGui::SetCursorScreenPos(ImVec2(p.x + (iconSize / 2) - (ImGui::CalcTextSize(dungeonName.c_str()).x / 2), p.y - (iconSize + 16)));
|
||||
ImGui::SetCursorScreenPos(ImVec2(p.x + (iconSize / 2) - (ImGui::CalcTextSize(dungeonName.c_str()).x / 2), p.y - (iconSize + offset)));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, dungeonColor);
|
||||
ImGui::Text("%s", dungeonName.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
@ -725,13 +732,15 @@ void DrawNotes(bool resizeable = false) {
|
||||
}
|
||||
};
|
||||
ImVec2 size = resizeable ? ImVec2(-FLT_MIN, ImGui::GetContentRegionAvail().y) : ImVec2(((iconSize + iconSpacing) * 6) - 8, 200);
|
||||
if (ItemTrackerNotes::TrackerNotesInputTextMultiline("##ItemTrackerNotes", &itemTrackerNotes, size, ImGuiInputTextFlags_AllowTabInput)) {
|
||||
notesNeedSave = true;
|
||||
notesIdleFrames = 0;
|
||||
}
|
||||
if ((ImGui::IsItemDeactivatedAfterEdit() || (notesNeedSave && notesIdleFrames > notesMaxIdleFrames)) && IsValidSaveFile()) {
|
||||
notesNeedSave = false;
|
||||
SaveManager::Instance->SaveSection(gSaveContext.fileNum, itemTrackerSectionId, true);
|
||||
if (GameInteractor::IsSaveLoaded()) {
|
||||
if (ItemTrackerNotes::TrackerNotesInputTextMultiline("##ItemTrackerNotes", &itemTrackerNotes, size, ImGuiInputTextFlags_AllowTabInput)) {
|
||||
notesNeedSave = true;
|
||||
notesIdleFrames = 0;
|
||||
}
|
||||
if ((ImGui::IsItemDeactivatedAfterEdit() || (notesNeedSave && notesIdleFrames > notesMaxIdleFrames)) && IsValidSaveFile()) {
|
||||
notesNeedSave = false;
|
||||
SaveManager::Instance->SaveSection(gSaveContext.fileNum, itemTrackerSectionId, true);
|
||||
}
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user